diff options
436 files changed, 17356 insertions, 8356 deletions
diff --git a/.travis.yml b/.travis.yml index 7f8bc638ff..83b1e93c8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,79 +8,100 @@ cache: - depends/built - depends/sdk-sources - $HOME/.ccache +stages: + - lint + - test env: global: - MAKEJOBS=-j3 - RUN_TESTS=false - - CHECK_DOC=0 - BOOST_TEST_RANDOM=1$TRAVIS_BUILD_ID - CCACHE_SIZE=100M - CCACHE_TEMPDIR=/tmp/.ccache-temp - CCACHE_COMPRESS=1 + - CCACHE_DIR=$HOME/.ccache - BASE_OUTDIR=$TRAVIS_BUILD_DIR/out - SDK_URL=https://bitcoincore.org/depends-sources/sdks - WINEDEBUG=fixme-all + - DOCKER_PACKAGES="build-essential libtool autotools-dev automake pkg-config bsdmainutils curl git ca-certificates ccache" matrix: # ARM - - HOST=arm-linux-gnueabihf PACKAGES="g++-arm-linux-gnueabihf python3-pip shellcheck" DEP_OPTS="NO_QT=1" CHECK_DOC=1 GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" + - HOST=arm-linux-gnueabihf PACKAGES="g++-arm-linux-gnueabihf" DEP_OPTS="NO_QT=1" GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" # Win32 - - HOST=i686-w64-mingw32 DPKG_ADD_ARCH="i386" DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-i686 wine1.6" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" + - HOST=i686-w64-mingw32 DPKG_ADD_ARCH="i386" DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-i686 wine-binfmt wine32" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" # Win64 - - HOST=x86_64-w64-mingw32 DPKG_ADD_ARCH="i386" DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine1.6" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" + - HOST=x86_64-w64-mingw32 DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine-binfmt wine64" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" # 32-bit + dash - - HOST=i686-pc-linux-gnu PACKAGES="g++-multilib python3-zmq" DEP_OPTS="NO_QT=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" USE_SHELL="/bin/dash" + - HOST=i686-pc-linux-gnu PACKAGES="g++-multilib python3-zmq" DEP_OPTS="NO_QT=1" RUN_TESTS=true 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) - - HOST=x86_64-unknown-linux-gnu PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools protobuf-compiler libdbus-1-dev libharfbuzz-dev" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports CPPFLAGS=-DDEBUG_LOCKORDER" -# Qt4 & system libs - - HOST=x86_64-unknown-linux-gnu PACKAGES="python3-zmq qt4-dev-tools libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-program-options-dev libboost-test-dev libboost-thread-dev libdb5.1++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev xvfb" NO_DEPENDS=1 NEED_XVFB=1 RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt4 CPPFLAGS=-DDEBUG_LOCKORDER" + - 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" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug" +# x86_64 Linux (Qt5 & system libs) + - 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-program-options-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 RUN_TESTS=true 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, No wallet - HOST=x86_64-unknown-linux-gnu PACKAGES="python3" DEP_OPTS="NO_WALLET=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" # Cross-Mac - - HOST=x86_64-apple-darwin11 PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror" OSX_SDK=10.11 GOAL="deploy" + - HOST=x86_64-apple-darwin11 PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev python3-setuptools-git" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror" OSX_SDK=10.11 GOAL="all deploy" before_install: - export PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/python/d' | tr "\n" ":" | sed "s|::|:|g") install: - - if [ -n "$DPKG_ADD_ARCH" ]; then sudo dpkg --add-architecture "$DPKG_ADD_ARCH" ; fi - - if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get update; fi - - if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get install --no-install-recommends --no-upgrade -qq $PACKAGES; fi - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then travis_retry pip3 install flake8 --user; fi + - env | grep -E '^(CCACHE_|WINEDEBUG|BOOST_TEST_RANDOM|CONFIG_SHELL)' | tee /tmp/env + - if [[ $HOST = *-mingw32 ]]; then DOCKER_ADMIN="--cap-add SYS_ADMIN"; 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 ubuntu:18.04) + - DOCKER_EXEC () { docker exec $DOCKER_ID bash -c "cd $PWD && $*"; } + - if [ -n "$DPKG_ADD_ARCH" ]; then DOCKER_EXEC dpkg --add-architecture "$DPKG_ADD_ARCH" ; fi + - travis_retry DOCKER_EXEC apt-get update + - travis_retry DOCKER_EXEC apt-get install --no-install-recommends --no-upgrade -qq $PACKAGES $DOCKER_PACKAGES before_script: - - if [ "$CHECK_DOC" = 1 ]; then git fetch --unshallow; fi - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then contrib/devtools/commit-script-check.sh $TRAVIS_COMMIT_RANGE; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/crypto/ctaes; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/secp256k1; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/univalue; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/leveldb; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/check-doc.py; fi - - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/check-rpc-mappings.py .; fi - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then contrib/devtools/lint-all.sh; fi - - unset CC; unset CXX + - DOCKER_EXEC echo \> \$HOME/.bitcoin # Make sure default datadir does not exist and is never read by creating a dummy file - mkdir -p depends/SDKs depends/sdk-sources - if [ -n "$OSX_SDK" -a ! -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then curl --location --fail $SDK_URL/MacOSX${OSX_SDK}.sdk.tar.gz -o depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi - if [ -n "$OSX_SDK" -a -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then tar -C depends/SDKs -xf depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi - - if [ -z "$NO_DEPENDS" ]; then make $MAKEJOBS -C depends HOST=$HOST $DEP_OPTS; fi - # Start xvfb if needed, as documented at https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI - - if [ "$NEED_XVFB" = 1 ]; then export DISPLAY=:99.0; /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac; fi + - if [[ $HOST = *-mingw32 ]]; then DOCKER_EXEC update-alternatives --set $HOST-g++ \$\(which $HOST-g++-posix\); fi + - if [ -z "$NO_DEPENDS" ]; then DOCKER_EXEC CONFIG_SHELL= make $MAKEJOBS -C depends HOST=$HOST $DEP_OPTS; fi script: - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then while read LINE; do travis_retry gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys; fi - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_EVENT_TYPE" = "cron" ]; then travis_wait 30 contrib/verify-commits/verify-commits.sh; fi - export TRAVIS_COMMIT_LOG=`git log --format=fuller -1` - - if [ -n "$USE_SHELL" ]; then export CONFIG_SHELL="$USE_SHELL"; fi - OUTDIR=$BASE_OUTDIR/$TRAVIS_PULL_REQUEST/$TRAVIS_JOB_NUMBER-$HOST - BITCOIN_CONFIG_ALL="--disable-dependency-tracking --prefix=$TRAVIS_BUILD_DIR/depends/$HOST --bindir=$OUTDIR/bin --libdir=$OUTDIR/lib" - - if [ -z "$NO_DEPENDS" ]; then ccache --max-size=$CCACHE_SIZE; fi - - test -n "$USE_SHELL" && eval '"$USE_SHELL" -c "./autogen.sh"' || ./autogen.sh + - if [ -z "$NO_DEPENDS" ]; then DOCKER_EXEC ccache --max-size=$CCACHE_SIZE; fi + - test -n "$CONFIG_SHELL" && DOCKER_EXEC "$CONFIG_SHELL" -c "./autogen.sh" || DOCKER_EXEC ./autogen.sh - mkdir build && cd build - - ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) - - make distdir VERSION=$HOST + - DOCKER_EXEC ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) + - DOCKER_EXEC make distdir VERSION=$HOST - cd bitcoin-$HOST - - ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) - - make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL V=1 ; false ) - - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib - - if [ "$RUN_TESTS" = "true" ]; then travis_wait 50 make $MAKEJOBS check VERBOSE=1; fi + - DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) + - DOCKER_EXEC make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && DOCKER_EXEC make $GOAL V=1 ; false ) + - if [ "$RUN_TESTS" = "true" ]; then DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib make $MAKEJOBS check VERBOSE=1; fi - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude feature_pruning,feature_dbcrash"; fi - - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet ${extended}; fi + - if [ "$RUN_TESTS" = "true" ]; then DOCKER_EXEC test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended}; fi after_script: - echo $TRAVIS_COMMIT_RANGE - echo $TRAVIS_COMMIT_LOG + +jobs: + include: + - stage: lint + sudo: false + cache: false + addons: + apt: + packages: + - python3-pip + - shellcheck + install: + - travis_retry pip3 install flake8 --user + before_script: + - git fetch --unshallow + script: + - if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then test/lint/commit-script-check.sh $TRAVIS_COMMIT_RANGE; fi + - test/lint/git-subtree-check.sh src/crypto/ctaes + - test/lint/git-subtree-check.sh src/secp256k1 + - test/lint/git-subtree-check.sh src/univalue + - test/lint/git-subtree-check.sh src/leveldb + - test/lint/check-doc.py + - test/lint/check-rpc-mappings.py . + - test/lint/lint-all.sh + - if [ "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_EVENT_TYPE" = "cron" ]; then + while read 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; + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea475f8cfb..3ee5a04796 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,9 @@ purposes. As such there are repository "maintainers" who are responsible for merging pull requests as well as a "lead maintainer" who is responsible for the release cycle, overall merging, moderation and appointment of maintainers. +If you're looking for somewhere to start contributing, check out the +[good first issue](https://github.com/bitcoin/bitcoin/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +list. Communication Channels ---------------------- @@ -124,10 +127,10 @@ before it will be merged. The basic squashing workflow is shown below. git checkout your_branch_name git rebase -i HEAD~n - # n is normally the number of commits in the pull - # set commits from 'pick' to 'squash', save and quit - # on the next screen, edit/refine commit messages - # save and quit + # n is normally the number of commits in the pull request. + # Set commits (except the one in the first line) from 'pick' to 'squash', save and quit. + # On the next screen, edit/refine commit messages. + # Save and quit. git push -f # (force push to GitHub) If you have problems with squashing (or other workflows with `git`), you can diff --git a/Makefile.am b/Makefile.am index f554501b2e..f3f3302fce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -95,9 +95,9 @@ $(OSX_APP)/Contents/Resources/bitcoin.icns: $(OSX_INSTALLER_ICONS) $(MKDIR_P) $(@D) $(INSTALL_DATA) $< $@ -$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(BITCOIN_QT_BIN) +$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: all-recursive $(MKDIR_P) $(@D) - STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $< $@ + STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_QT_BIN) $@ $(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings: $(MKDIR_P) $(@D) @@ -269,7 +269,8 @@ EXTRA_DIST += \ test/util/data/txcreatescript4.json \ test/util/data/txcreatesignv1.hex \ test/util/data/txcreatesignv1.json \ - test/util/data/txcreatesignv2.hex + test/util/data/txcreatesignv2.hex \ + test/util/rpcauth-test.py CLEANFILES = $(OSX_DMG) $(BITCOIN_WIN_INSTALLER) @@ -293,5 +294,6 @@ clean-docs: rm -rf doc/doxygen clean-local: clean-docs - rm -rf coverage_percent.txt test_bitcoin.coverage/ total.coverage/ test/tmp/ cache/ $(OSX_APP) src/qt/moc_* + 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 + @@ -52,7 +52,7 @@ There are also [regression and integration tests](/test), written in Python, that are run automatically on the build server. These tests can be run (if the [test dependencies](/test) are installed) with: `test/functional/test_runner.py` -The Travis CI system makes sure that every pull request is built for Windows, Linux, and OS X, and that unit/sanity tests are run automatically. +The Travis CI system makes sure that every pull request is built for Windows, Linux, and macOS, and that unit/sanity tests are run automatically. ### Manual Quality Assurance (QA) Testing diff --git a/autogen.sh b/autogen.sh index 27417daf76..0c05626ccc 100755 --- a/autogen.sh +++ b/autogen.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C set -e srcdir="$(dirname $0)" cd "$srcdir" diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index f41508336c..05df8621d2 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -53,8 +53,8 @@ dnl CAUTION: Do not use this inside of a conditional. AC_DEFUN([BITCOIN_QT_INIT],[ dnl enable qt support AC_ARG_WITH([gui], - [AS_HELP_STRING([--with-gui@<:@=no|qt4|qt5|auto@:>@], - [build bitcoin-qt GUI (default=auto, qt5 tried first)])], + [AS_HELP_STRING([--with-gui@<:@=no|qt5|auto@:>@], + [build bitcoin-qt GUI (default=auto)])], [ bitcoin_qt_want_version=$withval if test "x$bitcoin_qt_want_version" = xyes; then @@ -94,18 +94,17 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ fi if test "x$use_pkgconfig" = xyes; then - BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG([$2])]) + BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG]) else BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG]) fi dnl This is ugly and complicated. Yuck. Works as follows: - dnl We can't discern whether Qt4 builds are static or not. For Qt5, we can - dnl check a header to find out. When Qt is built statically, some plugins must - dnl be linked into the final binary as well. These plugins have changed between - dnl Qt4 and Qt5. With Qt5, languages moved into core and the WindowsIntegration - dnl plugin was added. Since we can't tell if Qt4 is static or not, it is - dnl assumed for windows builds. + dnl For Qt5, we can check a header to find out whether Qt is build + dnl statically. When Qt is built statically, some plugins must be linked into + dnl the final binary as well. + dnl With Qt5, languages moved into core and the WindowsIntegration plugin was + dnl added. dnl _BITCOIN_QT_CHECK_STATIC_PLUGINS does a quick link-check and appends the dnl results to QT_LIBS. BITCOIN_QT_CHECK([ @@ -113,53 +112,40 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ TEMP_CXXFLAGS=$CXXFLAGS CPPFLAGS="$QT_INCLUDES $CPPFLAGS" CXXFLAGS="$PIC_FLAGS $CXXFLAGS" - if test "x$bitcoin_qt_got_major_vers" = x5; then - _BITCOIN_QT_IS_STATIC - if test "x$bitcoin_cv_static_qt" = xyes; then - _BITCOIN_QT_FIND_STATIC_PLUGINS - AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) - AC_CACHE_CHECK(for Qt < 5.4, bitcoin_cv_need_acc_widget,[ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - #include <QtCore/qconfig.h> - #ifndef QT_VERSION - # include <QtCore/qglobal.h> - #endif - ]], - [[ - #if QT_VERSION >= 0x050400 - choke - #endif - ]])], - [bitcoin_cv_need_acc_widget=yes], - [bitcoin_cv_need_acc_widget=no]) - ]) - if test "x$bitcoin_cv_need_acc_widget" = xyes; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets]) - fi - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) - AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) - if test "x$TARGET_OS" = xwindows; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) - AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) - elif test "x$TARGET_OS" = xlinux; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static]) - AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb]) - elif test "x$TARGET_OS" = xdarwin; then - AX_CHECK_LINK_FLAG([[-framework IOKit]],[QT_LIBS="$QT_LIBS -framework IOKit"],[AC_MSG_ERROR(could not iokit framework)]) - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa]) - AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa]) - fi + _BITCOIN_QT_IS_STATIC + if test "x$bitcoin_cv_static_qt" = xyes; then + _BITCOIN_QT_FIND_STATIC_PLUGINS + AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) + AC_CACHE_CHECK(for Qt < 5.4, bitcoin_cv_need_acc_widget,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <QtCore/qconfig.h> + #ifndef QT_VERSION + # include <QtCore/qglobal.h> + #endif + ]], + [[ + #if QT_VERSION >= 0x050400 + choke + #endif + ]])], + [bitcoin_cv_need_acc_widget=yes], + [bitcoin_cv_need_acc_widget=no]) + ]) + if test "x$bitcoin_cv_need_acc_widget" = xyes; then + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets]) fi - else + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) + AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) if test "x$TARGET_OS" = xwindows; then - AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) - _BITCOIN_QT_CHECK_STATIC_PLUGINS([ - Q_IMPORT_PLUGIN(qcncodecs) - Q_IMPORT_PLUGIN(qjpcodecs) - Q_IMPORT_PLUGIN(qtwcodecs) - Q_IMPORT_PLUGIN(qkrcodecs) - Q_IMPORT_PLUGIN(AccessibleFactory)], - [-lqcncodecs -lqjpcodecs -lqtwcodecs -lqkrcodecs -lqtaccessiblewidgets]) + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) + AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) + elif test "x$TARGET_OS" = xlinux; then + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static]) + AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb]) + elif test "x$TARGET_OS" = xdarwin; then + AX_CHECK_LINK_FLAG([[-framework IOKit]],[QT_LIBS="$QT_LIBS -framework IOKit"],[AC_MSG_ERROR(could not iokit framework)]) + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa]) + AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa]) fi fi CPPFLAGS=$TEMP_CPPFLAGS @@ -167,9 +153,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ ]) if test "x$use_pkgconfig$qt_bin_path" = xyes; then - if test "x$bitcoin_qt_got_major_vers" = x5; then - qt_bin_path="`$PKG_CONFIG --variable=host_bins Qt5Core 2>/dev/null`" - fi + qt_bin_path="`$PKG_CONFIG --variable=host_bins Qt5Core 2>/dev/null`" fi if test "x$use_hardening" != xno; then @@ -219,11 +203,11 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ ]) fi - BITCOIN_QT_PATH_PROGS([MOC], [moc-qt${bitcoin_qt_got_major_vers} moc${bitcoin_qt_got_major_vers} moc], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([UIC], [uic-qt${bitcoin_qt_got_major_vers} uic${bitcoin_qt_got_major_vers} uic], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([RCC], [rcc-qt${bitcoin_qt_got_major_vers} rcc${bitcoin_qt_got_major_vers} rcc], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([LRELEASE], [lrelease-qt${bitcoin_qt_got_major_vers} lrelease${bitcoin_qt_got_major_vers} lrelease], $qt_bin_path) - BITCOIN_QT_PATH_PROGS([LUPDATE], [lupdate-qt${bitcoin_qt_got_major_vers} lupdate${bitcoin_qt_got_major_vers} lupdate],$qt_bin_path, yes) + BITCOIN_QT_PATH_PROGS([MOC], [moc-qt5 moc5 moc], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([UIC], [uic-qt5 uic5 uic], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([RCC], [rcc-qt5 rcc5 rcc], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([LRELEASE], [lrelease-qt5 lrelease5 lrelease], $qt_bin_path) + BITCOIN_QT_PATH_PROGS([LUPDATE], [lupdate-qt5 lupdate5 lupdate],$qt_bin_path, yes) MOC_DEFS='-DHAVE_CONFIG_H -I$(srcdir)' case $host in @@ -262,7 +246,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ ],[ bitcoin_enable_qt=no ]) - AC_MSG_RESULT([$bitcoin_enable_qt (Qt${bitcoin_qt_got_major_vers})]) + AC_MSG_RESULT([$bitcoin_enable_qt (Qt5)]) AC_SUBST(QT_PIE_FLAGS) AC_SUBST(QT_INCLUDES) @@ -272,7 +256,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ AC_SUBST(QT_DBUS_LIBS) AC_SUBST(QT_TEST_INCLUDES) AC_SUBST(QT_TEST_LIBS) - AC_SUBST(QT_SELECT, qt${bitcoin_qt_got_major_vers}) + AC_SUBST(QT_SELECT, qt5) AC_SUBST(MOC_DEFS) ]) @@ -292,7 +276,7 @@ AC_DEFUN([_BITCOIN_QT_CHECK_QT5],[ #endif ]], [[ - #if QT_VERSION < 0x050000 + #if QT_VERSION < 0x050000 || QT_VERSION_MAJOR < 5 choke #endif ]])], @@ -300,13 +284,11 @@ AC_DEFUN([_BITCOIN_QT_CHECK_QT5],[ [bitcoin_cv_qt5=no]) ])]) -dnl Internal. Check if the linked version of Qt was built as static libs. -dnl Requires: Qt5. This check cannot determine if Qt4 is static. -dnl Requires: INCLUDES and LIBS must be populated as necessary. -dnl Output: bitcoin_cv_static_qt=yes|no -dnl Output: Defines QT_STATICPLUGIN if plugins are static. -AC_DEFUN([_BITCOIN_QT_IS_STATIC],[ - AC_CACHE_CHECK(for static Qt, bitcoin_cv_static_qt,[ +dnl Internal. Check if the included version of Qt is greater than Qt58. +dnl Requires: INCLUDES must be populated as necessary. +dnl Output: bitcoin_cv_qt5=yes|no +AC_DEFUN([_BITCOIN_QT_CHECK_QT58],[ + AC_CACHE_CHECK(for > Qt 5.7, bitcoin_cv_qt58,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <QtCore/qconfig.h> #ifndef QT_VERSION @@ -314,13 +296,36 @@ AC_DEFUN([_BITCOIN_QT_IS_STATIC],[ #endif ]], [[ - #if !defined(QT_STATIC) + #if QT_VERSION_MINOR < 8 choke #endif ]])], - [bitcoin_cv_static_qt=yes], - [bitcoin_cv_static_qt=no]) - ]) + [bitcoin_cv_qt58=yes], + [bitcoin_cv_qt58=no]) +])]) + + +dnl Internal. Check if the linked version of Qt was built as static libs. +dnl Requires: Qt5. +dnl Requires: INCLUDES and LIBS must be populated as necessary. +dnl Output: bitcoin_cv_static_qt=yes|no +dnl Output: Defines QT_STATICPLUGIN if plugins are static. +AC_DEFUN([_BITCOIN_QT_IS_STATIC],[ + AC_CACHE_CHECK(for static Qt, bitcoin_cv_static_qt,[ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <QtCore/qconfig.h> + #ifndef QT_VERSION OR QT_VERSION_STR + # include <QtCore/qglobal.h> + #endif + ]], + [[ + #if !defined(QT_STATIC) + choke + #endif + ]])], + [bitcoin_cv_static_qt=yes], + [bitcoin_cv_static_qt=no]) + ]) if test "x$bitcoin_cv_static_qt" = xyes; then AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol for static Qt plugins]) fi @@ -346,28 +351,36 @@ AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_PLUGINS],[ ]) dnl Internal. Find paths necessary for linking qt static plugins -dnl Inputs: bitcoin_qt_got_major_vers. 4 or 5. dnl Inputs: qt_plugin_path. optional. dnl Outputs: QT_LIBS is appended AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ - if test "x$bitcoin_qt_got_major_vers" = x5; then - if test "x$qt_plugin_path" != x; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" - if test -d "$qt_plugin_path/accessible"; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" - fi + if test "x$qt_plugin_path" != x; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" + if test -d "$qt_plugin_path/accessible"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" fi if test "x$use_pkgconfig" = xyes; then : dnl m4_ifdef([PKG_CHECK_MODULES],[ - PKG_CHECK_MODULES([QTPLATFORM], [Qt5PlatformSupport], [QT_LIBS="$QTPLATFORM_LIBS $QT_LIBS"]) + if test x$bitcoin_cv_qt58 = xno; then + PKG_CHECK_MODULES([QTPLATFORM], [Qt5PlatformSupport], [QT_LIBS="$QTPLATFORM_LIBS $QT_LIBS"]) + else + PKG_CHECK_MODULES([QTFONTDATABASE], [Qt5FontDatabaseSupport], [QT_LIBS="-lQt5FontDatabaseSupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTEVENTDISPATCHER], [Qt5EventDispatcherSupport], [QT_LIBS="-lQt5EventDispatcherSupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTTHEME], [Qt5ThemeSupport], [QT_LIBS="-lQt5ThemeSupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTDEVICEDISCOVERY], [Qt5DeviceDiscoverySupport], [QT_LIBS="-lQt5DeviceDiscoverySupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTACCESSIBILITY], [Qt5AccessibilitySupport], [QT_LIBS="-lQt5AccessibilitySupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTFB], [Qt5FbSupport], [QT_LIBS="-lQt5FbSupport $QT_LIBS"]) + fi if test "x$TARGET_OS" = xlinux; then PKG_CHECK_MODULES([X11XCB], [x11-xcb], [QT_LIBS="$X11XCB_LIBS $QT_LIBS"]) if ${PKG_CONFIG} --exists "Qt5Core >= 5.5" 2>/dev/null; then PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) fi elif test "x$TARGET_OS" = xdarwin; then - PKG_CHECK_MODULES([QTPRINT], [Qt5PrintSupport], [QT_LIBS="$QTPRINT_LIBS $QT_LIBS"]) + PKG_CHECK_MODULES([QTCLIPBOARD], [Qt5ClipboardSupport], [QT_LIBS="-lQt5ClipboardSupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTGRAPHICS], [Qt5GraphicsSupport], [QT_LIBS="-lQt5GraphicsSupport $QT_LIBS"]) + PKG_CHECK_MODULES([QTCGL], [Qt5CglSupport], [QT_LIBS="-lQt5CglSupport $QT_LIBS"]) fi ]) else @@ -380,7 +393,7 @@ AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ #endif ]], [[ - #if QT_VERSION < 0x050600 + #if QT_VERSION < 0x050600 || QT_VERSION_MINOR < 6 choke #endif ]])], @@ -388,16 +401,21 @@ AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ [bitcoin_cv_need_platformsupport=no]) ]) if test "x$bitcoin_cv_need_platformsupport" = xyes; then - BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}PlatformSupport],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}PlatformSupport not found))) + if test x$bitcoin_cv_qt58 = xno; then + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}PlatformSupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXPlatformSupport not found))) + else + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}FontDatabaseSupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXFontDatabaseSupport not found))) + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}EventDispatcherSupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXEventDispatcherSupport not found))) + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}ThemeSupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXThemeSupport not found))) + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}FbSupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXFbSupport not found))) + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}DeviceDiscoverySupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXDeviceDiscoverySupport not found))) + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}AccessibilitySupport],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXAccessibilitySupport not found))) + QT_LIBS="$QT_LIBS -lversion -ldwmapi -luxtheme" + fi fi fi fi - else - if test "x$qt_plugin_path" != x; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" - QT_LIBS="$QT_LIBS -L$qt_plugin_path/codecs" - fi - fi + fi ]) dnl Internal. Find Qt libraries using pkg-config. @@ -406,38 +424,14 @@ dnl first. dnl Inputs: $1: If bitcoin_qt_want_version is "auto", check for this version dnl first. dnl Outputs: All necessary QT_* variables are set. -dnl Outputs: bitcoin_qt_got_major_vers is set to "4" or "5". dnl Outputs: have_qt_test and have_qt_dbus are set (if applicable) to yes|no. AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG],[ m4_ifdef([PKG_CHECK_MODULES],[ - auto_priority_version=$1 - if test "x$auto_priority_version" = x; then - auto_priority_version=qt5 - fi - if test "x$bitcoin_qt_want_version" = xqt5 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt5 ); then - QT_LIB_PREFIX=Qt5 - bitcoin_qt_got_major_vers=5 - else - QT_LIB_PREFIX=Qt - bitcoin_qt_got_major_vers=4 - fi + QT_LIB_PREFIX=Qt5 qt5_modules="Qt5Core Qt5Gui Qt5Network Qt5Widgets" - qt4_modules="QtCore QtGui QtNetwork" BITCOIN_QT_CHECK([ - if test "x$bitcoin_qt_want_version" = xqt5 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt5 ); then - PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" have_qt=yes],[have_qt=no]) - elif test "x$bitcoin_qt_want_version" = xqt4 || ( test "x$bitcoin_qt_want_version" = xauto && test "x$auto_priority_version" = xqt4 ); then - PKG_CHECK_MODULES([QT4], [$qt4_modules], [QT_INCLUDES="$QT4_CFLAGS"; QT_LIBS="$QT4_LIBS" ; have_qt=yes], [have_qt=no]) - fi + PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" have_qt=yes],[have_qt=no]) - dnl qt version is set to 'auto' and the preferred version wasn't found. Now try the other. - if test "x$have_qt" = xno && test "x$bitcoin_qt_want_version" = xauto; then - if test "x$auto_priority_version" = xqt5; then - PKG_CHECK_MODULES([QT4], [$qt4_modules], [QT_INCLUDES="$QT4_CFLAGS"; QT_LIBS="$QT4_LIBS" ; have_qt=yes; QT_LIB_PREFIX=Qt; bitcoin_qt_got_major_vers=4], [have_qt=no]) - else - PKG_CHECK_MODULES([QT5], [$qt5_modules], [QT_INCLUDES="$QT5_CFLAGS"; QT_LIBS="$QT5_LIBS" ; have_qt=yes; QT_LIB_PREFIX=Qt5; bitcoin_qt_got_major_vers=5], [have_qt=no]) - fi - fi if test "x$have_qt" != xyes; then have_qt=no BITCOIN_QT_FAIL([Qt dependencies not found]) @@ -458,7 +452,6 @@ dnl from the discovered headers. dnl Inputs: bitcoin_qt_want_version (from --with-gui=). The version to use. dnl If "auto", the version will be discovered by _BITCOIN_QT_CHECK_QT5. dnl Outputs: All necessary QT_* variables are set. -dnl Outputs: bitcoin_qt_got_major_vers is set to "4" or "5". dnl Outputs: have_qt_test and have_qt_dbus are set (if applicable) to yes|no. AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[ TEMP_CPPFLAGS="$CPPFLAGS" @@ -479,14 +472,9 @@ AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[ BITCOIN_QT_CHECK([ if test "x$bitcoin_qt_want_version" = xauto; then _BITCOIN_QT_CHECK_QT5 + _BITCOIN_QT_CHECK_QT58 fi - if test "x$bitcoin_cv_qt5" = xyes || test "x$bitcoin_qt_want_version" = xqt5; then - QT_LIB_PREFIX=Qt5 - bitcoin_qt_got_major_vers=5 - else - QT_LIB_PREFIX=Qt - bitcoin_qt_got_major_vers=4 - fi + QT_LIB_PREFIX=Qt5 ]) BITCOIN_QT_CHECK([ @@ -501,16 +489,19 @@ AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[ ]) BITCOIN_QT_CHECK(AC_CHECK_LIB([z] ,[main],,AC_MSG_WARN([zlib not found. Assuming qt has it built-in]))) - BITCOIN_QT_CHECK(AC_SEARCH_LIBS([png_error] ,[qtpng png],,AC_MSG_WARN([libpng not found. Assuming qt has it built-in]))) BITCOIN_QT_CHECK(AC_SEARCH_LIBS([jpeg_create_decompress] ,[qtjpeg jpeg],,AC_MSG_WARN([libjpeg not found. Assuming qt has it built-in]))) - BITCOIN_QT_CHECK(AC_SEARCH_LIBS([pcre16_exec], [qtpcre pcre16],,AC_MSG_WARN([libpcre16 not found. Assuming qt has it built-in]))) - BITCOIN_QT_CHECK(AC_SEARCH_LIBS([hb_ot_tags_from_script] ,[qtharfbuzzng harfbuzz],,AC_MSG_WARN([libharfbuzz not found. Assuming qt has it built-in or support is disabled]))) + if test x$bitcoin_cv_qt58 = xno; then + BITCOIN_QT_CHECK(AC_SEARCH_LIBS([png_error] ,[qtpng png],,AC_MSG_WARN([libpng not found. Assuming qt has it built-in]))) + BITCOIN_QT_CHECK(AC_SEARCH_LIBS([pcre16_exec], [qtpcre pcre16],,AC_MSG_WARN([libpcre16 not found. Assuming qt has it built-in]))) + else + BITCOIN_QT_CHECK(AC_SEARCH_LIBS([png_error] ,[qtlibpng png],,AC_MSG_WARN([libpng not found. Assuming qt has it built-in]))) + BITCOIN_QT_CHECK(AC_SEARCH_LIBS([pcre2_match_16], [qtpcre2 libqtpcre2],,AC_MSG_WARN([libqtpcre2 not found. Assuming qt has it built-in]))) + fi + BITCOIN_QT_CHECK(AC_SEARCH_LIBS([hb_ot_tags_from_script] ,[qtharfbuzzng qtharfbuzz harfbuzz],,AC_MSG_WARN([libharfbuzz not found. Assuming qt has it built-in or support is disabled]))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Core] ,[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Core not found))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Gui] ,[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Gui not found))) BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Network],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Network not found))) - if test "x$bitcoin_qt_got_major_vers" = x5; then - BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Widgets],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Widgets not found))) - fi + BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Widgets],[main],,BITCOIN_QT_FAIL(lib${QT_LIB_PREFIX}Widgets not found))) QT_LIBS="$LIBS" LIBS="$TEMP_LIBS" diff --git a/configure.ac b/configure.ac index c4bc061d74..2b4bc43194 100644 --- a/configure.ac +++ b/configure.ac @@ -84,7 +84,7 @@ AC_PATH_TOOL(STRIP, strip) AC_PATH_TOOL(GCOV, gcov) AC_PATH_PROG(LCOV, lcov) dnl Python 3.x is supported from 3.4 on (see https://github.com/bitcoin/bitcoin/issues/7893) -AC_PATH_PROGS([PYTHON], [python3.6 python3.5 python3.4 python3 python2.7 python2 python]) +AC_PATH_PROGS([PYTHON], [python3.7 python3.6 python3.5 python3.4 python3 python]) AC_PATH_PROG(GENHTML, genhtml) AC_PATH_PROG([GIT], [git]) AC_PATH_PROG(CCACHE,ccache) @@ -243,14 +243,27 @@ AC_LANG_PUSH([C++]) AX_CHECK_COMPILE_FLAG([-Werror],[CXXFLAG_WERROR="-Werror"],[CXXFLAG_WERROR=""]) if test "x$enable_debug" = xyes; then - CPPFLAGS="$CPPFLAGS -DDEBUG -DDEBUG_LOCKORDER" - if test "x$GCC" = xyes; then - CFLAGS="$CFLAGS -g3 -O0" - fi + # Clear default -g -O2 flags + if test "x$CXXFLAGS_overridden" = xno; then + CXXFLAGS="" + fi + # Prefer -Og, fall back to -O0 if that is unavailable. + AX_CHECK_COMPILE_FLAG( + [-Og], + [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -Og"]], + [AX_CHECK_COMPILE_FLAG([-O0],[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]])], + [[$CXXFLAG_WERROR]]) - if test "x$GXX" = xyes; then - CXXFLAGS="$CXXFLAGS -g3 -O0" - fi + # Prefer -g3, fall back to -g if that is unavailable. + AX_CHECK_COMPILE_FLAG( + [-g3], + [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -g3"]], + [AX_CHECK_COMPILE_FLAG([-g],[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -g"]],,[[$CXXFLAG_WERROR]])], + [[$CXXFLAG_WERROR]]) + + AX_CHECK_PREPROC_FLAG([-DDEBUG],[[DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DDEBUG"]],,[[$CXXFLAG_WERROR]]) + AX_CHECK_PREPROC_FLAG([-DDEBUG_LOCKORDER],[[DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DDEBUG_LOCKORDER"]],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-ftrapv],[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -ftrapv"],,[[$CXXFLAG_WERROR]]) fi if test x$use_sanitizers != x; then @@ -283,27 +296,31 @@ if test "x$enable_werror" = "xyes"; then fi if test "x$CXXFLAGS_overridden" = "xno"; then - AX_CHECK_COMPILE_FLAG([-Wall],[CXXFLAGS="$CXXFLAGS -Wall"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wextra],[CXXFLAGS="$CXXFLAGS -Wextra"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wformat],[CXXFLAGS="$CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wvla],[CXXFLAGS="$CXXFLAGS -Wvla"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wformat-security],[CXXFLAGS="$CXXFLAGS -Wformat-security"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wthread-safety-analysis],[CXXFLAGS="$CXXFLAGS -Wthread-safety-analysis"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wall],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wall"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wextra],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wextra"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wformat],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wvla],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wvla"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wformat-security],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wformat-security"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wthread-safety-analysis],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wthread-safety-analysis"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wrange-loop-analysis],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wrange-loop-analysis"],,[[$CXXFLAG_WERROR]]) ## Some compilers (gcc) ignore unknown -Wno-* options, but warn about all ## unknown options if any other warning is produced. Test the -Wfoo case, and ## set the -Wno-foo case if it works. - AX_CHECK_COMPILE_FLAG([-Wunused-parameter],[CXXFLAGS="$CXXFLAGS -Wno-unused-parameter"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wself-assign],[CXXFLAGS="$CXXFLAGS -Wno-self-assign"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wunused-local-typedef],[CXXFLAGS="$CXXFLAGS -Wno-unused-local-typedef"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wdeprecated-register],[CXXFLAGS="$CXXFLAGS -Wno-deprecated-register"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wimplicit-fallthrough],[CXXFLAGS="$CXXFLAGS -Wno-implicit-fallthrough"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wunused-parameter],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-unused-parameter"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wself-assign],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-self-assign"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wunused-local-typedef],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-unused-local-typedef"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wdeprecated-register],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-deprecated-register"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Wimplicit-fallthrough],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-implicit-fallthrough"],,[[$CXXFLAG_WERROR]]) fi # Check for optional instruction set support. Enabling these does _not_ imply that all code will # be compiled with them, rather that specific objects/libs may use them after checking for runtime # compatibility. AX_CHECK_COMPILE_FLAG([-msse4.2],[[SSE42_CXXFLAGS="-msse4.2"]],,[[$CXXFLAG_WERROR]]) +AX_CHECK_COMPILE_FLAG([-msse4.1],[[SSE41_CXXFLAGS="-msse4.1"]],,[[$CXXFLAG_WERROR]]) +AX_CHECK_COMPILE_FLAG([-mavx -mavx2],[[AVX2_CXXFLAGS="-mavx -mavx2"]],,[[$CXXFLAG_WERROR]]) +AX_CHECK_COMPILE_FLAG([-msse4 -msha],[[SHANI_CXXFLAGS="-msse4 -msha"]],,[[$CXXFLAG_WERROR]]) TEMP_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $SSE42_CXXFLAGS" @@ -327,6 +344,53 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ) CXXFLAGS="$TEMP_CXXFLAGS" +TEMP_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $SSE41_CXXFLAGS" +AC_MSG_CHECKING(for SSE4.1 intrinsics) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <stdint.h> + #include <immintrin.h> + ]],[[ + __m128i l = _mm_set1_epi32(0); + return _mm_extract_epi32(l, 3); + ]])], + [ AC_MSG_RESULT(yes); enable_sse41=yes; AC_DEFINE(ENABLE_SSE41, 1, [Define this symbol to build code that uses SSE4.1 intrinsics]) ], + [ AC_MSG_RESULT(no)] +) +CXXFLAGS="$TEMP_CXXFLAGS" + +TEMP_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $AVX2_CXXFLAGS" +AC_MSG_CHECKING(for AVX2 intrinsics) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <stdint.h> + #include <immintrin.h> + ]],[[ + __m256i l = _mm256_set1_epi32(0); + return _mm256_extract_epi32(l, 7); + ]])], + [ AC_MSG_RESULT(yes); enable_avx2=yes; AC_DEFINE(ENABLE_AVX2, 1, [Define this symbol to build code that uses AVX2 intrinsics]) ], + [ AC_MSG_RESULT(no)] +) +CXXFLAGS="$TEMP_CXXFLAGS" + +TEMP_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $SHANI_CXXFLAGS" +AC_MSG_CHECKING(for SHA-NI intrinsics) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <stdint.h> + #include <immintrin.h> + ]],[[ + __m128i i = _mm_set1_epi32(0); + __m128i j = _mm_set1_epi32(1); + __m128i k = _mm_set1_epi32(2); + return _mm_extract_epi32(_mm_sha256rnds2_epu32(i, i, k), 0); + ]])], + [ AC_MSG_RESULT(yes); enable_shani=yes; AC_DEFINE(ENABLE_SHANI, 1, [Define this symbol to build code that uses SHA-NI intrinsics]) ], + [ AC_MSG_RESULT(no)] +) +CXXFLAGS="$TEMP_CXXFLAGS" + CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], @@ -355,25 +419,25 @@ case $host in use_pkgconfig=no TARGET_OS=windows - AC_CHECK_LIB([mingwthrd], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([kernel32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([user32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([gdi32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([comdlg32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([winspool], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([winmm], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([shell32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([comctl32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([ole32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([oleaut32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([uuid], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([rpcrt4], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([advapi32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([ws2_32], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([mswsock], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([shlwapi], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([iphlpapi], [main],, AC_MSG_ERROR(lib missing)) - AC_CHECK_LIB([crypt32], [main],, AC_MSG_ERROR(lib missing)) + AC_CHECK_LIB([mingwthrd], [main],, AC_MSG_ERROR(libmingwthrd missing)) + AC_CHECK_LIB([kernel32], [main],, AC_MSG_ERROR(libkernel32 missing)) + AC_CHECK_LIB([user32], [main],, AC_MSG_ERROR(libuser32 missing)) + AC_CHECK_LIB([gdi32], [main],, AC_MSG_ERROR(libgdi32 missing)) + AC_CHECK_LIB([comdlg32], [main],, AC_MSG_ERROR(libcomdlg32 missing)) + AC_CHECK_LIB([winspool], [main],, AC_MSG_ERROR(libwinspool missing)) + AC_CHECK_LIB([winmm], [main],, AC_MSG_ERROR(libwinmm missing)) + AC_CHECK_LIB([shell32], [main],, AC_MSG_ERROR(libshell32 missing)) + AC_CHECK_LIB([comctl32], [main],, AC_MSG_ERROR(libcomctl32 missing)) + AC_CHECK_LIB([ole32], [main],, AC_MSG_ERROR(libole32 missing)) + AC_CHECK_LIB([oleaut32], [main],, AC_MSG_ERROR(liboleaut32 missing)) + AC_CHECK_LIB([uuid], [main],, AC_MSG_ERROR(libuuid missing)) + AC_CHECK_LIB([rpcrt4], [main],, AC_MSG_ERROR(librpcrt4 missing)) + AC_CHECK_LIB([advapi32], [main],, AC_MSG_ERROR(libadvapi32 missing)) + AC_CHECK_LIB([ws2_32], [main],, AC_MSG_ERROR(libws2_32 missing)) + AC_CHECK_LIB([mswsock], [main],, AC_MSG_ERROR(libmswsock missing)) + AC_CHECK_LIB([shlwapi], [main],, AC_MSG_ERROR(libshlwapi missing)) + AC_CHECK_LIB([iphlpapi], [main],, AC_MSG_ERROR(libiphlpapi missing)) + AC_CHECK_LIB([crypt32], [main],, AC_MSG_ERROR(libcrypt32 missing)) # -static is interpreted by libtool, where it has a different meaning. # In libtool-speak, it's -all-static. @@ -477,10 +541,17 @@ case $host in CPPFLAGS="$CPPFLAGS -DMAC_OSX" OBJCXXFLAGS="$CXXFLAGS" ;; + *android*) + dnl make sure android stays above linux for hosts like *linux-android* + LEVELDB_TARGET_FLAGS="-DOS_ANDROID" + ;; *linux*) TARGET_OS=linux LEVELDB_TARGET_FLAGS="-DOS_LINUX" ;; + *kfreebsd*) + LEVELDB_TARGET_FLAGS="-DOS_KFREEBSD" + ;; *freebsd*) LEVELDB_TARGET_FLAGS="-DOS_FREEBSD" ;; @@ -490,10 +561,17 @@ case $host in *netbsd*) LEVELDB_TARGET_FLAGS="-DOS_NETBSD" ;; + *dragonfly*) + LEVELDB_TARGET_FLAGS="-DOS_DRAGONFLYBSD" + ;; + *solaris*) + LEVELDB_TARGET_FLAGS="-DOS_SOLARIS" + ;; + *hpux*) + LEVELDB_TARGET_FLAGS="-DOS_HPUX" + ;; *) - OTHER_OS=`echo ${host_os} | awk '{print toupper($0)}'` - AC_MSG_WARN([Guessing LevelDB OS as OS_${OTHER_OS}, please check whether this is correct, if not add an entry to configure.ac.]) - LEVELDB_TARGET_FLAGS="-DOS_${OTHER_OS}" + AC_MSG_ERROR(Cannot build leveldb for $host. Please file a bug report.) ;; esac @@ -572,7 +650,7 @@ if test x$use_glibc_compat != xno; then #glibc absorbed clock_gettime in 2.17. librt (its previous location) is safe to link #in anyway for back-compat. - AC_CHECK_LIB([rt],[clock_gettime],, AC_MSG_ERROR(lib missing)) + AC_CHECK_LIB([rt],[clock_gettime],, AC_MSG_ERROR(librt missing)) #__fdelt_chk's params and return type have changed from long unsigned int to long int. # See which one is present here. @@ -587,6 +665,8 @@ if test x$use_glibc_compat != xno; then [ fdelt_type="long int"]) AC_MSG_RESULT($fdelt_type) AC_DEFINE_UNQUOTED(FDELT_TYPE, $fdelt_type,[parameter and return value type for __fdelt_chk]) + AX_CHECK_LINK_FLAG([[-Wl,--wrap=__divmoddi4]], [COMPAT_LDFLAGS="$COMPAT_LDFLAGS -Wl,--wrap=__divmoddi4"]) + AX_CHECK_LINK_FLAG([[-Wl,--wrap=log2f]], [COMPAT_LDFLAGS="$COMPAT_LDFLAGS -Wl,--wrap=log2f"]) else AC_SEARCH_LIBS([clock_gettime],[rt]) fi @@ -634,7 +714,7 @@ if test x$use_hardening != xno; then case $host in *mingw*) - AC_CHECK_LIB([ssp], [main],, AC_MSG_ERROR(lib missing)) + AC_CHECK_LIB([ssp], [main],, AC_MSG_ERROR(libssp missing)) ;; esac fi @@ -754,6 +834,14 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h> [ AC_MSG_RESULT(no)] ) +AC_MSG_CHECKING(for if type char equals int8_t) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <stdint.h> + #include <type_traits>]], + [[ static_assert(std::is_same<int8_t, char>::value, ""); ]])], + [ AC_MSG_RESULT(yes); AC_DEFINE(CHAR_EQUALS_INT8, 1,[Define this symbol if type char equals int8_t]) ], + [ AC_MSG_RESULT(no)] +) + # Check for reduced exports if test x$use_reduce_exports = xyes; then AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"], @@ -785,7 +873,7 @@ fi BITCOIN_QT_INIT dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus -BITCOIN_QT_CONFIGURE([$use_pkgconfig], [qt5]) +BITCOIN_QT_CONFIGURE([$use_pkgconfig]) if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononono; then use_boost=no @@ -1245,6 +1333,9 @@ AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) AM_CONDITIONAL([GLIBC_BACK_COMPAT],[test x$use_glibc_compat = xyes]) AM_CONDITIONAL([HARDEN],[test x$use_hardening = xyes]) AM_CONDITIONAL([ENABLE_HWCRC32],[test x$enable_hwcrc32 = xyes]) +AM_CONDITIONAL([ENABLE_SSE41],[test x$enable_sse41 = xyes]) +AM_CONDITIONAL([ENABLE_AVX2],[test x$enable_avx2 = xyes]) +AM_CONDITIONAL([ENABLE_SHANI],[test x$enable_shani = xyes]) AM_CONDITIONAL([USE_ASM],[test x$use_asm = xyes]) AC_DEFINE(CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MAJOR, [Major version]) @@ -1272,6 +1363,11 @@ AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) AC_SUBST(RELDFLAGS) +AC_SUBST(DEBUG_CPPFLAGS) +AC_SUBST(WARN_CXXFLAGS) +AC_SUBST(NOWARN_CXXFLAGS) +AC_SUBST(DEBUG_CXXFLAGS) +AC_SUBST(COMPAT_LDFLAGS) AC_SUBST(ERROR_CXXFLAGS) AC_SUBST(GPROF_CXXFLAGS) AC_SUBST(GPROF_LDFLAGS) @@ -1283,6 +1379,9 @@ AC_SUBST(PIE_FLAGS) AC_SUBST(SANITIZER_CXXFLAGS) AC_SUBST(SANITIZER_LDFLAGS) AC_SUBST(SSE42_CXXFLAGS) +AC_SUBST(SSE41_CXXFLAGS) +AC_SUBST(AVX2_CXXFLAGS) +AC_SUBST(SHANI_CXXFLAGS) AC_SUBST(LIBTOOL_APP_LDFLAGS) AC_SUBST(USE_UPNP) AC_SUBST(USE_QRCODE) @@ -1304,6 +1403,7 @@ AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([doc/Doxyfile])]) AC_CONFIG_LINKS([contrib/filter-lcov.py:contrib/filter-lcov.py]) AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py]) AC_CONFIG_LINKS([test/util/bitcoin-util-test.py:test/util/bitcoin-util-test.py]) +AC_CONFIG_LINKS([test/util/rpcauth-test.py:test/util/rpcauth-test.py]) dnl boost's m4 checks do something really nasty: they export these vars. As a dnl result, they leak into secp256k1's configure and crazy things happen. @@ -1361,7 +1461,6 @@ 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 " qt version = $bitcoin_qt_got_major_vers" echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" @@ -1379,9 +1478,9 @@ echo " build os = $BUILD_OS" echo echo " CC = $CC" echo " CFLAGS = $CFLAGS" -echo " CPPFLAGS = $CPPFLAGS" +echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CPPFLAGS" echo " CXX = $CXX" -echo " CXXFLAGS = $CXXFLAGS" -echo " LDFLAGS = $LDFLAGS" +echo " CXXFLAGS = $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CXXFLAGS" +echo " LDFLAGS = $PTHREAD_CFLAGS $HARDENED_LDFLAGS $GPROF_LDFLAGS $LDFLAGS" echo " ARFLAGS = $ARFLAGS" echo diff --git a/contrib/debian/copyright b/contrib/debian/copyright index c6484157a5..21cca7d9ac 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -76,8 +76,8 @@ Comment: Files: src/qt/res/icons/clock*.png src/qt/res/icons/eye_*.png - src/qt/res/icons/verify.png src/qt/res/icons/tx_in*.png + src/qt/res/icons/verify.png src/qt/res/src/clock_*.svg src/qt/res/src/tx_*.svg src/qt/res/src/verify.svg @@ -93,6 +93,11 @@ Copyright: Bitboy, Jonas Schnelli License: public-domain Comment: Site: https://bitcointalk.org/?topic=1756.0 +Files: src/qt/res/icons/proxy.png + src/qt/res/src/proxy.svg +Copyright: Cristian Mircea Messel +Licese: public-domain + License: Expat Permission is hereby granted, free of charge, to any person obtaining a diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md index 15ee8a3959..a0b6225345 100644 --- a/contrib/devtools/README.md +++ b/contrib/devtools/README.md @@ -2,12 +2,6 @@ Contents ======== This directory contains tools for developers working on this repository. -check-doc.py -============ - -Check if all command line args are documented. The return value indicates the -number of undocumented args. - clang-format-diff.py =================== @@ -93,23 +87,6 @@ example: BUILDDIR=$PWD/build contrib/devtools/gen-manpages.sh ``` -git-subtree-check.sh -==================== - -Run this script from the root of the repository to verify that a subtree matches the contents of -the commit it claims to have been updated to. - -To use, make sure that you have fetched the upstream repository branch in which the subtree is -maintained: -* for `src/secp256k1`: https://github.com/bitcoin-core/secp256k1.git (branch master) -* for `src/leveldb`: https://github.com/bitcoin-core/leveldb.git (branch bitcoin-fork) -* for `src/univalue`: https://github.com/bitcoin-core/univalue.git (branch master) -* for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master) - -Usage: `git-subtree-check.sh DIR (COMMIT)` - -`COMMIT` may be omitted, in which case `HEAD` is used. - github-merge.py =============== @@ -194,3 +171,14 @@ It will do the following automatically: - add missing translations to the build system (TODO) See doc/translation-process.md for more information. + +circular-dependencies.py +======================== + +Run this script from the root of the source tree (`src/`) to find circular dependencies in the source code. +This looks only at which files include other files, treating the `.cpp` and `.h` file as one unit. + +Example usage: + + cd .../src + ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} diff --git a/contrib/devtools/circular-dependencies.py b/contrib/devtools/circular-dependencies.py new file mode 100755 index 0000000000..abfa5ed5ae --- /dev/null +++ b/contrib/devtools/circular-dependencies.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import sys +import re + +MAPPING = { + 'core_read.cpp': 'core_io.cpp', + 'core_write.cpp': 'core_io.cpp', +} + +def module_name(path): + if path in MAPPING: + path = MAPPING[path] + if path.endswith(".h"): + return path[:-2] + if path.endswith(".c"): + return path[:-2] + if path.endswith(".cpp"): + return path[:-4] + return None + +files = dict() +deps = dict() + +RE = re.compile("^#include <(.*)>") + +# Iterate over files, and create list of modules +for arg in sys.argv[1:]: + module = module_name(arg) + if module is None: + print("Ignoring file %s (does not constitute module)\n" % arg) + else: + files[arg] = module + deps[module] = set() + +# Iterate again, and build list of direct dependencies for each module +# TODO: implement support for multiple include directories +for arg in sorted(files.keys()): + module = files[arg] + with open(arg, 'r', encoding="utf8") as f: + for line in f: + match = RE.match(line) + if match: + include = match.group(1) + included_module = module_name(include) + if included_module is not None and included_module in deps and included_module != module: + deps[module].add(included_module) + +# Loop to find the shortest (remaining) circular dependency +have_cycle = False +while True: + shortest_cycle = None + for module in sorted(deps.keys()): + # Build the transitive closure of dependencies of module + closure = dict() + for dep in deps[module]: + closure[dep] = [] + while True: + old_size = len(closure) + old_closure_keys = sorted(closure.keys()) + for src in old_closure_keys: + for dep in deps[src]: + if dep not in closure: + closure[dep] = closure[src] + [src] + if len(closure) == old_size: + break + # If module is in its own transitive closure, it's a circular dependency; check if it is the shortest + if module in closure and (shortest_cycle is None or len(closure[module]) + 1 < len(shortest_cycle)): + shortest_cycle = [module] + closure[module] + if shortest_cycle is None: + break + # We have the shortest circular dependency; report it + module = shortest_cycle[0] + print("Circular dependency: %s" % (" -> ".join(shortest_cycle + [module]))) + # And then break the dependency to avoid repeating in other cycles + deps[shortest_cycle[-1]] = deps[shortest_cycle[-1]] - set([module]) + have_cycle = True + +sys.exit(1 if have_cycle else 0) diff --git a/contrib/devtools/clang-format-diff.py b/contrib/devtools/clang-format-diff.py index 5402870fba..77e845a9b4 100755 --- a/contrib/devtools/clang-format-diff.py +++ b/contrib/devtools/clang-format-diff.py @@ -152,7 +152,7 @@ def main(): sys.exit(p.returncode) if not args.i: - with open(filename) as f: + with open(filename, encoding="utf8") as f: code = f.readlines() formatted_code = io.StringIO(stdout).readlines() diff = difflib.unified_diff(code, formatted_code, diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index e7cccaab03..da7d74bdc4 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -146,7 +146,7 @@ def file_has_without_c_style_copyright_for_holder(contents, holder_name): ################################################################################ def read_file(filename): - return open(os.path.abspath(filename), 'r').read() + return open(os.path.abspath(filename), 'r', encoding="utf8").read() def gather_file_info(filename): info = {} @@ -325,13 +325,13 @@ def get_most_recent_git_change_year(filename): ################################################################################ def read_file_lines(filename): - f = open(os.path.abspath(filename), 'r') + f = open(os.path.abspath(filename), 'r', encoding="utf8") file_lines = f.readlines() f.close() return file_lines def write_file_lines(filename, file_lines): - f = open(os.path.abspath(filename), 'w') + f = open(os.path.abspath(filename), 'w', encoding="utf8") f.write(''.join(file_lines)) f.close() @@ -506,7 +506,7 @@ def file_has_hashbang(file_lines): def insert_python_header(filename, file_lines, start_year, end_year): if file_has_hashbang(file_lines): - insert_idx = 1 + insert_idx = 1 else: insert_idx = 0 header_lines = get_python_header_lines_to_insert(start_year, end_year) @@ -571,7 +571,7 @@ def insert_cmd(argv): if extension not in ['.h', '.cpp', '.cc', '.c', '.py']: sys.exit("*** cannot insert for file extension %s" % extension) - if extension == '.py': + if extension == '.py': style = 'python' else: style = 'cpp' diff --git a/contrib/devtools/gen-manpages.sh b/contrib/devtools/gen-manpages.sh index 27c80548c1..b5de5a395f 100755 --- a/contrib/devtools/gen-manpages.sh +++ b/contrib/devtools/gen-manpages.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +export LC_ALL=C TOPDIR=${TOPDIR:-$(git rev-parse --show-toplevel)} BUILDDIR=${BUILDDIR:-$TOPDIR} diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py index 187ef75fb7..4e90f85f50 100755 --- a/contrib/devtools/github-merge.py +++ b/contrib/devtools/github-merge.py @@ -191,7 +191,7 @@ def main(): merge_branch = 'pull/'+pull+'/merge' local_merge_branch = 'pull/'+pull+'/local-merge' - devnull = open(os.devnull,'w') + devnull = open(os.devnull, 'w', encoding="utf8") try: subprocess.check_call([GIT,'checkout','-q',branch]) except subprocess.CalledProcessError: diff --git a/contrib/devtools/lint-includes.sh b/contrib/devtools/lint-includes.sh deleted file mode 100755 index f54be46b52..0000000000 --- a/contrib/devtools/lint-includes.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2018 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -# Check for duplicate includes. - -filter_suffix() { - git ls-files | grep -E "^src/.*\.${1}"'$' | grep -Ev "/(leveldb|secp256k1|univalue)/" -} - -EXIT_CODE=0 -for HEADER_FILE in $(filter_suffix h); do - DUPLICATE_INCLUDES_IN_HEADER_FILE=$(grep -E "^#include " < "${HEADER_FILE}" | sort | uniq -d) - if [[ ${DUPLICATE_INCLUDES_IN_HEADER_FILE} != "" ]]; then - echo "Duplicate include(s) in ${HEADER_FILE}:" - echo "${DUPLICATE_INCLUDES_IN_HEADER_FILE}" - echo - EXIT_CODE=1 - fi -done -for CPP_FILE in $(filter_suffix cpp); do - DUPLICATE_INCLUDES_IN_CPP_FILE=$(grep -E "^#include " < "${CPP_FILE}" | sort | uniq -d) - if [[ ${DUPLICATE_INCLUDES_IN_CPP_FILE} != "" ]]; then - echo "Duplicate include(s) in ${CPP_FILE}:" - echo "${DUPLICATE_INCLUDES_IN_CPP_FILE}" - echo - EXIT_CODE=1 - fi -done -exit ${EXIT_CODE} diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 0f2099953f..47195f73c8 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -97,7 +97,7 @@ def check_ELF_RELRO(executable): raise IOError('Error opening file') for line in stdout.splitlines(): tokens = line.split() - if len(tokens)>1 and tokens[1] == '(BIND_NOW)' or (len(tokens)>2 and tokens[1] == '(FLAGS)' and 'BIND_NOW' in tokens[2]): + if len(tokens)>1 and tokens[1] == '(BIND_NOW)' or (len(tokens)>2 and tokens[1] == '(FLAGS)' and 'BIND_NOW' in tokens[2:]): have_bindnow = True return have_gnu_relro and have_bindnow @@ -150,7 +150,7 @@ def check_PE_DYNAMIC_BASE(executable): def check_PE_HIGH_ENTROPY_VA(executable): '''PIE: DllCharacteristics bit 0x20 signifies high-entropy ASLR''' (arch,bits) = get_PE_dll_characteristics(executable) - if arch == 'i386:x86-64': + if arch == 'i386:x86-64': reqbits = IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA else: # Unnecessary on 32-bit assert(arch == 'i386') diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 3a67319eaa..6808e77da7 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -46,7 +46,7 @@ MAX_VERSIONS = { # Ignore symbols that are exported as part of every executable IGNORE_EXPORTS = { -'_edata', '_end', '_init', '__bss_start', '_fini', '_IO_stdin_used' +'_edata', '_end', '_init', '__bss_start', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr' } READELF_CMD = os.getenv('READELF', '/usr/bin/readelf') CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt') diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index ee87c8bab4..9b6d6bf665 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -9,7 +9,7 @@ import subprocess import unittest def write_testcode(filename): - with open(filename, 'w') as f: + with open(filename, 'w', encoding="utf8") as f: f.write(''' #include <stdio.h> int main() @@ -32,29 +32,39 @@ class TestSecurityChecks(unittest.TestCase): cc = 'gcc' write_testcode(source) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro']), (1, executable+': failed PIE NX RELRO Canary')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fno-stack-protector','-Wl,-znorelro']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fno-stack-protector','-Wl,-znorelro']), (1, executable+': failed PIE RELRO Canary')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro']), (1, executable+': failed PIE RELRO')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-pie','-fPIE']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-pie','-fPIE']), (1, executable+': failed RELRO')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE']), (0, '')) - def test_PE(self): + def test_32bit_PE(self): source = 'test1.c' executable = 'test1.exe' cc = 'i686-w64-mingw32-gcc' write_testcode(source) - self.assertEqual(call_security_check(cc, source, executable, []), - (1, executable+': failed PIE NX')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat']), - (1, executable+': failed PIE')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase']), + self.assertEqual(call_security_check(cc, source, executable, []), + (1, executable+': failed DYNAMIC_BASE NX')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat']), + (1, executable+': failed DYNAMIC_BASE')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase']), (0, '')) + def test_64bit_PE(self): + source = 'test1.c' + executable = 'test1.exe' + cc = 'x86_64-w64-mingw32-gcc' + write_testcode(source) + + self.assertEqual(call_security_check(cc, source, executable, []), (1, executable+': failed DYNAMIC_BASE NX\n'+executable+': warning HIGH_ENTROPY_VA')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat']), (1, executable+': failed DYNAMIC_BASE\n'+executable+': warning HIGH_ENTROPY_VA')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase']), (0, executable+': warning HIGH_ENTROPY_VA')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-Wl,--high-entropy-va']), (0, '')) if __name__ == '__main__': unittest.main() diff --git a/contrib/devtools/update-translations.py b/contrib/devtools/update-translations.py index b36e6968bf..f0098cfcdf 100755 --- a/contrib/devtools/update-translations.py +++ b/contrib/devtools/update-translations.py @@ -30,6 +30,8 @@ SOURCE_LANG = 'bitcoin_en.ts' LOCALE_DIR = 'src/qt/locale' # Minimum number of messages for translation to be considered at all MIN_NUM_MESSAGES = 10 +# Regexp to check for Bitcoin addresses +ADDRESS_REGEXP = re.compile('([13]|bc1)[a-zA-Z0-9]{30,}') def check_at_repository_root(): if not os.path.exists('.git'): @@ -122,6 +124,12 @@ def escape_cdata(text): text = text.replace('"', '"') return text +def contains_bitcoin_addr(text, errors): + if text != None and ADDRESS_REGEXP.search(text) != None: + errors.append('Translation "%s" contains a bitcoin address. This will be removed.' % (text)) + return True + return False + def postprocess_translations(reduce_diff_hacks=False): print('Checking and postprocessing...') @@ -160,7 +168,7 @@ def postprocess_translations(reduce_diff_hacks=False): if translation is None: continue errors = [] - valid = check_format_specifiers(source, translation, errors, numerus) + valid = check_format_specifiers(source, translation, errors, numerus) and not contains_bitcoin_addr(translation, errors) for error in errors: print('%s: %s' % (filename, error)) diff --git a/contrib/filter-lcov.py b/contrib/filter-lcov.py index 299377d691..df1db76e92 100755 --- a/contrib/filter-lcov.py +++ b/contrib/filter-lcov.py @@ -13,8 +13,8 @@ pattern = args.pattern outfile = args.outfile in_remove = False -with open(tracefile, 'r') as f: - with open(outfile, 'w') as wf: +with open(tracefile, 'r', encoding="utf8") as f: + with open(outfile, 'w', encoding="utf8") as wf: for line in f: for p in pattern: if line.startswith("SF:") and p in line: diff --git a/contrib/gitian-build.py b/contrib/gitian-build.py new file mode 100755 index 0000000000..1da9e43896 --- /dev/null +++ b/contrib/gitian-build.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +import argparse +import os +import subprocess +import sys + +def setup(): + global args, workdir + programs = ['ruby', 'git', 'apt-cacher-ng', 'make', 'wget'] + if args.kvm: + programs += ['python-vm-builder', 'qemu-kvm', 'qemu-utils'] + elif args.docker: + programs += ['docker.io'] + else: + programs += ['lxc', 'debootstrap'] + subprocess.check_call(['sudo', 'apt-get', 'install', '-qq'] + programs) + if not os.path.isdir('gitian.sigs'): + subprocess.check_call(['git', 'clone', 'https://github.com/bitcoin-core/gitian.sigs.git']) + if not os.path.isdir('bitcoin-detached-sigs'): + subprocess.check_call(['git', 'clone', 'https://github.com/bitcoin-core/bitcoin-detached-sigs.git']) + if not os.path.isdir('gitian-builder'): + subprocess.check_call(['git', 'clone', 'https://github.com/devrandom/gitian-builder.git']) + if not os.path.isdir('bitcoin'): + subprocess.check_call(['git', 'clone', 'https://github.com/bitcoin/bitcoin.git']) + os.chdir('gitian-builder') + make_image_prog = ['bin/make-base-vm', '--suite', 'bionic', '--arch', 'amd64'] + if args.docker: + make_image_prog += ['--docker'] + elif not args.kvm: + make_image_prog += ['--lxc'] + subprocess.check_call(make_image_prog) + os.chdir(workdir) + +def build(): + global args, workdir + + os.makedirs('bitcoin-binaries/' + args.version, exist_ok=True) + print('\nBuilding Dependencies\n') + 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://bitcoincore.org/cfields/osslsigncode-Backports-to-1.7.1.patch']) + subprocess.check_call(['make', '-C', '../bitcoin/depends', 'download', 'SOURCES_PATH=' + os.getcwd() + '/cache/common']) + + if args.linux: + print('\nCompiling ' + args.version + ' Linux') + subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'bitcoin='+args.commit, '--url', 'bitcoin='+args.url, '../bitcoin/contrib/gitian-descriptors/gitian-linux.yml']) + subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-linux', '--destination', '../gitian.sigs/', '../bitcoin/contrib/gitian-descriptors/gitian-linux.yml']) + subprocess.check_call('mv build/out/bitcoin-*.tar.gz build/out/src/bitcoin-*.tar.gz ../bitcoin-binaries/'+args.version, shell=True) + + if args.windows: + 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-*.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-*.tar.gz build/out/bitcoin-*.dmg ../bitcoin-binaries/'+args.version, shell=True) + + os.chdir(workdir) + + if args.commit_files: + print('\nCommitting '+args.version+' Unsigned Sigs\n') + os.chdir('gitian.sigs') + subprocess.check_call(['git', 'add', args.version+'-linux/'+args.signer]) + subprocess.check_call(['git', 'add', args.version+'-win-unsigned/'+args.signer]) + subprocess.check_call(['git', 'add', args.version+'-osx-unsigned/'+args.signer]) + subprocess.check_call(['git', 'commit', '-m', 'Add '+args.version+' unsigned sigs for '+args.signer]) + os.chdir(workdir) + +def sign(): + global args, workdir + os.chdir('gitian-builder') + + if args.windows: + print('\nSigning ' + args.version + ' Windows') + 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) + subprocess.check_call('mv build/out/bitcoin-*win32-setup.exe ../bitcoin-binaries/'+args.version, shell=True) + + if args.macos: + print('\nSigning ' + args.version + ' MacOS') + 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) + + os.chdir(workdir) + + if args.commit_files: + print('\nCommitting '+args.version+' Signed Sigs\n') + os.chdir('gitian.sigs') + subprocess.check_call(['git', 'add', args.version+'-win-signed/'+args.signer]) + subprocess.check_call(['git', 'add', args.version+'-osx-signed/'+args.signer]) + subprocess.check_call(['git', 'commit', '-a', '-m', 'Add '+args.version+' signed binary sigs for '+args.signer]) + os.chdir(workdir) + +def verify(): + global args, workdir + os.chdir('gitian-builder') + + print('\nVerifying v'+args.version+' Linux\n') + subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-linux', '../bitcoin/contrib/gitian-descriptors/gitian-linux.yml']) + print('\nVerifying v'+args.version+' Windows\n') + subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-unsigned', '../bitcoin/contrib/gitian-descriptors/gitian-win.yml']) + print('\nVerifying v'+args.version+' MacOS\n') + subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-unsigned', '../bitcoin/contrib/gitian-descriptors/gitian-osx.yml']) + print('\nVerifying v'+args.version+' Signed Windows\n') + subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-signed', '../bitcoin/contrib/gitian-descriptors/gitian-win-signer.yml']) + print('\nVerifying v'+args.version+' Signed MacOS\n') + subprocess.check_call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-signed', '../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml']) + + os.chdir(workdir) + +def main(): + global args, workdir + + parser = argparse.ArgumentParser(usage='%(prog)s [options] signer version') + parser.add_argument('-c', '--commit', action='store_true', dest='commit', help='Indicate that the version argument is for a commit or branch') + parser.add_argument('-u', '--url', dest='url', default='https://github.com/bitcoin/bitcoin', help='Specify the URL of the repository. Default is %(default)s') + parser.add_argument('-v', '--verify', action='store_true', dest='verify', help='Verify the Gitian build') + parser.add_argument('-b', '--build', action='store_true', dest='build', help='Do a Gitian build') + parser.add_argument('-s', '--sign', action='store_true', dest='sign', help='Make signed binaries for Windows and MacOS') + parser.add_argument('-B', '--buildsign', action='store_true', dest='buildsign', help='Build both signed and unsigned binaries') + parser.add_argument('-o', '--os', dest='os', default='lwm', help='Specify which Operating Systems the build is for. Default is %(default)s. l for Linux, w for Windows, m for MacOS') + parser.add_argument('-j', '--jobs', dest='jobs', default='2', help='Number of processes to use. Default %(default)s') + parser.add_argument('-m', '--memory', dest='memory', default='2000', help='Memory to allocate in MiB. Default %(default)s') + parser.add_argument('-k', '--kvm', action='store_true', dest='kvm', help='Use KVM instead of LXC') + parser.add_argument('-d', '--docker', action='store_true', dest='docker', help='Use Docker instead of LXC') + parser.add_argument('-S', '--setup', action='store_true', dest='setup', help='Set up the Gitian building environment. Uses LXC. If you want to use KVM, use the --kvm option. Only works on Debian-based systems (Ubuntu, Debian)') + parser.add_argument('-D', '--detach-sign', action='store_true', dest='detach_sign', help='Create the assert file for detached signing. Will not commit anything.') + parser.add_argument('-n', '--no-commit', action='store_false', dest='commit_files', help='Do not commit anything to git') + parser.add_argument('signer', help='GPG signer to sign each build assert file') + parser.add_argument('version', help='Version number, commit, or branch to build. If building a commit or branch, the -c option must be specified') + + args = parser.parse_args() + workdir = os.getcwd() + + args.linux = 'l' in args.os + args.windows = 'w' in args.os + args.macos = 'm' in args.os + + if args.buildsign: + args.build=True + args.sign=True + + if args.kvm and args.docker: + raise Exception('Error: cannot have both kvm and docker') + + args.sign_prog = 'true' if args.detach_sign else 'gpg --detach-sign' + + # Set enviroment variable USE_LXC or USE_DOCKER, let gitian-builder know that we use lxc or docker + if args.docker: + os.environ['USE_DOCKER'] = '1' + elif not args.kvm: + os.environ['USE_LXC'] = '1' + + # Disable for MacOS if no SDK found + if args.macos and not os.path.isfile('gitian-builder/inputs/MacOSX10.11.sdk.tar.gz'): + print('Cannot build for MacOS, SDK does not exist. Will build for other OSes') + args.macos = False + + script_name = os.path.basename(sys.argv[0]) + # Signer and version shouldn't be empty + if args.signer == '': + print(script_name+': Missing signer.') + print('Try '+script_name+' --help for more information') + exit(1) + if args.version == '': + print(script_name+': Missing version.') + print('Try '+script_name+' --help for more information') + exit(1) + + # Add leading 'v' for tags + args.commit = ('' if args.commit else 'v') + args.version + print(args.commit) + + if args.setup: + setup() + + os.chdir('bitcoin') + subprocess.check_call(['git', 'fetch']) + subprocess.check_call(['git', 'checkout', args.commit]) + os.chdir(workdir) + + if args.build: + build() + + if args.sign: + sign() + + if args.verify: + verify() + +if __name__ == '__main__': + main() diff --git a/contrib/gitian-build.sh b/contrib/gitian-build.sh deleted file mode 100755 index 94d6a89c7b..0000000000 --- a/contrib/gitian-build.sh +++ /dev/null @@ -1,389 +0,0 @@ -# Copyright (c) 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. - -# What to do -sign=false -verify=false -build=false - -# Systems to build -linux=true -windows=true -osx=true - -# Other Basic variables -SIGNER= -VERSION= -commit=false -url=https://github.com/bitcoin/bitcoin -proc=2 -mem=2000 -lxc=true -osslTarUrl=http://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz -osslPatchUrl=https://bitcoincore.org/cfields/osslsigncode-Backports-to-1.7.1.patch -scriptName=$(basename -- "$0") -signProg="gpg --detach-sign" -commitFiles=true - -# Help Message -read -d '' usage <<- EOF -Usage: $scriptName [-c|u|v|b|s|B|o|h|j|m|] signer version - -Run this script from the directory containing the bitcoin, gitian-builder, gitian.sigs, and bitcoin-detached-sigs. - -Arguments: -signer GPG signer to sign each build assert file -version Version number, commit, or branch to build. If building a commit or branch, the -c option must be specified - -Options: --c|--commit Indicate that the version argument is for a commit or branch --u|--url Specify the URL of the repository. Default is https://github.com/bitcoin/bitcoin --v|--verify Verify the Gitian build --b|--build Do a Gitian build --s|--sign Make signed binaries for Windows and Mac OSX --B|--buildsign Build both signed and unsigned binaries --o|--os Specify which Operating Systems the build is for. Default is lwx. l for linux, w for windows, x for osx --j Number of processes to use. Default 2 --m Memory to allocate in MiB. Default 2000 ---kvm Use KVM instead of LXC ---setup Set up the Gitian building environment. Uses LXC. If you want to use KVM, use the --kvm option. Only works on Debian-based systems (Ubuntu, Debian) ---detach-sign Create the assert file for detached signing. Will not commit anything. ---no-commit Do not commit anything to git --h|--help Print this help message -EOF - -# Get options and arguments -while :; do - case $1 in - # Verify - -v|--verify) - verify=true - ;; - # Build - -b|--build) - build=true - ;; - # Sign binaries - -s|--sign) - sign=true - ;; - # Build then Sign - -B|--buildsign) - sign=true - build=true - ;; - # PGP Signer - -S|--signer) - if [ -n "$2" ] - then - SIGNER="$2" - shift - else - echo 'Error: "--signer" requires a non-empty argument.' - exit 1 - fi - ;; - # Operating Systems - -o|--os) - if [ -n "$2" ] - then - linux=false - windows=false - osx=false - if [[ "$2" = *"l"* ]] - then - linux=true - fi - if [[ "$2" = *"w"* ]] - then - windows=true - fi - if [[ "$2" = *"x"* ]] - then - osx=true - fi - shift - else - echo 'Error: "--os" requires an argument containing an l (for linux), w (for windows), or x (for Mac OSX)' - exit 1 - fi - ;; - # Help message - -h|--help) - echo "$usage" - exit 0 - ;; - # Commit or branch - -c|--commit) - commit=true - ;; - # Number of Processes - -j) - if [ -n "$2" ] - then - proc=$2 - shift - else - echo 'Error: "-j" requires an argument' - exit 1 - fi - ;; - # Memory to allocate - -m) - if [ -n "$2" ] - then - mem=$2 - shift - else - echo 'Error: "-m" requires an argument' - exit 1 - fi - ;; - # URL - -u) - if [ -n "$2" ] - then - url=$2 - shift - else - echo 'Error: "-u" requires an argument' - exit 1 - fi - ;; - # kvm - --kvm) - lxc=false - ;; - # Detach sign - --detach-sign) - signProg="true" - commitFiles=false - ;; - # Commit files - --no-commit) - commitFiles=false - ;; - # Setup - --setup) - setup=true - ;; - *) # Default case: If no more options then break out of the loop. - break - esac - shift -done - -# Set up LXC -if [[ $lxc = true ]] -then - export USE_LXC=1 -fi - -# Check for OSX SDK -if [[ ! -e "gitian-builder/inputs/MacOSX10.11.sdk.tar.gz" && $osx == true ]] -then - echo "Cannot build for OSX, SDK does not exist. Will build for other OSes" - osx=false -fi - -# Get signer -if [[ -n "$1" ]] -then - SIGNER="$1" - shift -fi - -# Get version -if [[ -n "$1" ]] -then - VERSION=$1 - COMMIT=$VERSION - shift -fi - -# Check that a signer is specified -if [[ "$SIGNER" == "" ]] -then - echo "$scriptName: Missing signer." - echo "Try $scriptName --help for more information" - exit 1 -fi - -# Check that a version is specified -if [[ $VERSION == "" ]] -then - echo "$scriptName: Missing version." - echo "Try $scriptName --help for more information" - exit 1 -fi - -# Add a "v" if no -c -if [[ $commit = false ]] -then - COMMIT="v${VERSION}" -fi -echo ${COMMIT} - -# Setup build environment -if [[ $setup = true ]] -then - sudo apt-get install ruby apache2 git apt-cacher-ng python-vm-builder qemu-kvm qemu-utils - git clone https://github.com/bitcoin-core/gitian.sigs.git - git clone https://github.com/bitcoin-core/bitcoin-detached-sigs.git - git clone https://github.com/devrandom/gitian-builder.git - pushd ./gitian-builder - if [[ -n "$USE_LXC" ]] - then - sudo apt-get install lxc - bin/make-base-vm --suite trusty --arch amd64 --lxc - else - bin/make-base-vm --suite trusty --arch amd64 - fi - popd -fi - -# Set up build -pushd ./bitcoin -git fetch -git checkout ${COMMIT} -popd - -# Build -if [[ $build = true ]] -then - # Make output folder - mkdir -p ./bitcoin-binaries/${VERSION} - - # Build Dependencies - echo "" - echo "Building Dependencies" - echo "" - pushd ./gitian-builder - mkdir -p inputs - wget -N -P inputs $osslPatchUrl - wget -N -P inputs $osslTarUrl - make -C ../bitcoin/depends download SOURCES_PATH=`pwd`/cache/common - - # Linux - if [[ $linux = true ]] - then - echo "" - echo "Compiling ${VERSION} Linux" - echo "" - ./bin/gbuild -j ${proc} -m ${mem} --commit bitcoin=${COMMIT} --url bitcoin=${url} ../bitcoin/contrib/gitian-descriptors/gitian-linux.yml - ./bin/gsign -p "$signProg" --signer "$SIGNER" --release ${VERSION}-linux --destination ../gitian.sigs/ ../bitcoin/contrib/gitian-descriptors/gitian-linux.yml - mv build/out/bitcoin-*.tar.gz build/out/src/bitcoin-*.tar.gz ../bitcoin-binaries/${VERSION} - fi - # Windows - if [[ $windows = true ]] - then - echo "" - echo "Compiling ${VERSION} Windows" - echo "" - ./bin/gbuild -j ${proc} -m ${mem} --commit bitcoin=${COMMIT} --url bitcoin=${url} ../bitcoin/contrib/gitian-descriptors/gitian-win.yml - ./bin/gsign -p "$signProg" --signer "$SIGNER" --release ${VERSION}-win-unsigned --destination ../gitian.sigs/ ../bitcoin/contrib/gitian-descriptors/gitian-win.yml - mv build/out/bitcoin-*-win-unsigned.tar.gz inputs/bitcoin-win-unsigned.tar.gz - mv build/out/bitcoin-*.zip build/out/bitcoin-*.exe ../bitcoin-binaries/${VERSION} - fi - # Mac OSX - if [[ $osx = true ]] - then - echo "" - echo "Compiling ${VERSION} Mac OSX" - echo "" - ./bin/gbuild -j ${proc} -m ${mem} --commit bitcoin=${COMMIT} --url bitcoin=${url} ../bitcoin/contrib/gitian-descriptors/gitian-osx.yml - ./bin/gsign -p "$signProg" --signer "$SIGNER" --release ${VERSION}-osx-unsigned --destination ../gitian.sigs/ ../bitcoin/contrib/gitian-descriptors/gitian-osx.yml - mv build/out/bitcoin-*-osx-unsigned.tar.gz inputs/bitcoin-osx-unsigned.tar.gz - mv build/out/bitcoin-*.tar.gz build/out/bitcoin-*.dmg ../bitcoin-binaries/${VERSION} - fi - popd - - if [[ $commitFiles = true ]] - then - # Commit to gitian.sigs repo - echo "" - echo "Committing ${VERSION} Unsigned Sigs" - echo "" - pushd gitian.sigs - git add ${VERSION}-linux/"${SIGNER}" - git add ${VERSION}-win-unsigned/"${SIGNER}" - git add ${VERSION}-osx-unsigned/"${SIGNER}" - git commit -a -m "Add ${VERSION} unsigned sigs for ${SIGNER}" - popd - fi -fi - -# Verify the build -if [[ $verify = true ]] -then - # Linux - pushd ./gitian-builder - echo "" - echo "Verifying v${VERSION} Linux" - echo "" - ./bin/gverify -v -d ../gitian.sigs/ -r ${VERSION}-linux ../bitcoin/contrib/gitian-descriptors/gitian-linux.yml - # Windows - echo "" - echo "Verifying v${VERSION} Windows" - echo "" - ./bin/gverify -v -d ../gitian.sigs/ -r ${VERSION}-win-unsigned ../bitcoin/contrib/gitian-descriptors/gitian-win.yml - # Mac OSX - echo "" - echo "Verifying v${VERSION} Mac OSX" - echo "" - ./bin/gverify -v -d ../gitian.sigs/ -r ${VERSION}-osx-unsigned ../bitcoin/contrib/gitian-descriptors/gitian-osx.yml - # Signed Windows - echo "" - echo "Verifying v${VERSION} Signed Windows" - echo "" - ./bin/gverify -v -d ../gitian.sigs/ -r ${VERSION}-osx-signed ../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml - # Signed Mac OSX - echo "" - echo "Verifying v${VERSION} Signed Mac OSX" - echo "" - ./bin/gverify -v -d ../gitian.sigs/ -r ${VERSION}-osx-signed ../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml - popd -fi - -# Sign binaries -if [[ $sign = true ]] -then - - pushd ./gitian-builder - # Sign Windows - if [[ $windows = true ]] - then - echo "" - echo "Signing ${VERSION} Windows" - echo "" - ./bin/gbuild -i --commit signature=${COMMIT} ../bitcoin/contrib/gitian-descriptors/gitian-win-signer.yml - ./bin/gsign -p "$signProg" --signer "$SIGNER" --release ${VERSION}-win-signed --destination ../gitian.sigs/ ../bitcoin/contrib/gitian-descriptors/gitian-win-signer.yml - mv build/out/bitcoin-*win64-setup.exe ../bitcoin-binaries/${VERSION} - mv build/out/bitcoin-*win32-setup.exe ../bitcoin-binaries/${VERSION} - fi - # Sign Mac OSX - if [[ $osx = true ]] - then - echo "" - echo "Signing ${VERSION} Mac OSX" - echo "" - ./bin/gbuild -i --commit signature=${COMMIT} ../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml - ./bin/gsign -p "$signProg" --signer "$SIGNER" --release ${VERSION}-osx-signed --destination ../gitian.sigs/ ../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml - mv build/out/bitcoin-osx-signed.dmg ../bitcoin-binaries/${VERSION}/bitcoin-${VERSION}-osx.dmg - fi - popd - - if [[ $commitFiles = true ]] - then - # Commit Sigs - pushd gitian.sigs - echo "" - echo "Committing ${VERSION} Signed Sigs" - echo "" - git add ${VERSION}-win-signed/"${SIGNER}" - git add ${VERSION}-osx-signed/"${SIGNER}" - git commit -a -m "Add ${VERSION} signed binary sigs for ${SIGNER}" - popd - fi -fi diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index 3e9ee0495a..1c8aca6f65 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -2,23 +2,23 @@ name: "bitcoin-linux-0.17" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: - "curl" - "g++-aarch64-linux-gnu" -- "g++-4.8-aarch64-linux-gnu" -- "gcc-4.8-aarch64-linux-gnu" +- "g++-7-aarch64-linux-gnu" +- "gcc-7-aarch64-linux-gnu" - "binutils-aarch64-linux-gnu" - "g++-arm-linux-gnueabihf" -- "g++-4.8-arm-linux-gnueabihf" -- "gcc-4.8-arm-linux-gnueabihf" +- "g++-7-arm-linux-gnueabihf" +- "gcc-7-arm-linux-gnueabihf" - "binutils-arm-linux-gnueabihf" -- "g++-4.8-multilib" -- "gcc-4.8-multilib" +- "g++-7-multilib" +- "gcc-7-multilib" - "binutils-gold" -- "git-core" +- "git" - "pkg-config" - "autoconf" - "libtool" @@ -56,7 +56,7 @@ script: | function create_global_faketime_wrappers { for prog in ${FAKETIME_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${prog} echo "REAL=\`which -a ${prog} | grep -v ${WRAP_DIR}/${prog} | head -1\`" >> ${WRAP_DIR}/${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${prog} @@ -68,7 +68,7 @@ script: | function create_per-host_faketime_wrappers { for i in $HOSTS; do for prog in ${FAKETIME_HOST_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}-${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${i}-${prog} echo "REAL=\`which -a ${i}-${prog} | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${i}-${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog} @@ -98,7 +98,7 @@ script: | for prog in gcc g++; do rm -f ${WRAP_DIR}/${prog} cat << EOF > ${WRAP_DIR}/${prog} - #!/bin/bash + #!/usr/bin/env bash REAL="`which -a ${prog} | grep -v ${WRAP_DIR}/${prog} | head -1`" for var in "\$@" do diff --git a/contrib/gitian-descriptors/gitian-osx-signer.yml b/contrib/gitian-descriptors/gitian-osx-signer.yml index f6e9414ab1..297a136fae 100644 --- a/contrib/gitian-descriptors/gitian-osx-signer.yml +++ b/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -1,7 +1,7 @@ --- name: "bitcoin-dmg-signer" suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: @@ -19,7 +19,7 @@ script: | # Create global faketime wrappers for prog in ${FAKETIME_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${prog} echo "REAL=\`which -a ${prog} | grep -v ${WRAP_DIR}/${prog} | head -1\`" >> ${WRAP_DIR}/${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${prog} echo "export FAKETIME=\"${REFERENCE_DATETIME}\"" >> ${WRAP_DIR}/${prog} diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index a84dce3e3a..7d4793b97d 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -2,14 +2,14 @@ name: "bitcoin-osx-0.17" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: - "ca-certificates" - "curl" - "g++" -- "git-core" +- "git" - "pkg-config" - "autoconf" - "librsvg2-bin" @@ -55,7 +55,7 @@ script: | function create_global_faketime_wrappers { for prog in ${FAKETIME_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${prog} echo "REAL=\`which -a ${prog} | grep -v ${WRAP_DIR}/${prog} | head -1\`" >> ${WRAP_DIR}/${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${prog} @@ -67,7 +67,7 @@ script: | function create_per-host_faketime_wrappers { for i in $HOSTS; do for prog in ${FAKETIME_HOST_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}-${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${i}-${prog} echo "REAL=\`which -a ${i}-${prog} | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${i}-${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog} diff --git a/contrib/gitian-descriptors/gitian-win-signer.yml b/contrib/gitian-descriptors/gitian-win-signer.yml index 3c1e0214a0..2f3ec3e8ff 100644 --- a/contrib/gitian-descriptors/gitian-win-signer.yml +++ b/contrib/gitian-descriptors/gitian-win-signer.yml @@ -1,7 +1,7 @@ --- name: "bitcoin-win-signer" suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 8a87d91754..9c588afcda 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -2,13 +2,13 @@ name: "bitcoin-win-0.17" enable_cache: true suites: -- "trusty" +- "bionic" architectures: - "amd64" packages: - "curl" - "g++" -- "git-core" +- "git" - "pkg-config" - "autoconf" - "libtool" @@ -21,6 +21,7 @@ packages: - "zip" - "ca-certificates" - "python" +- "rename" remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" @@ -29,7 +30,7 @@ script: | WRAP_DIR=$HOME/wrapped HOSTS="i686-w64-mingw32 x86_64-w64-mingw32" CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests" - FAKETIME_HOST_PROGS="g++ ar ranlib nm windres strip objcopy" + FAKETIME_HOST_PROGS="ar ranlib nm windres strip objcopy" FAKETIME_PROGS="date makensis zip" HOST_CFLAGS="-O2 -g" HOST_CXXFLAGS="-O2 -g" @@ -48,7 +49,7 @@ script: | function create_global_faketime_wrappers { for prog in ${FAKETIME_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${prog} echo "REAL=\`which -a ${prog} | grep -v ${WRAP_DIR}/${prog} | head -1\`" >> ${WRAP_DIR}/${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${prog} @@ -60,7 +61,7 @@ script: | function create_per-host_faketime_wrappers { for i in $HOSTS; do for prog in ${FAKETIME_HOST_PROGS}; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}-${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${i}-${prog} echo "REAL=\`which -a ${i}-${prog} | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${i}-${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog} @@ -76,15 +77,15 @@ script: | for i in $HOSTS; do mkdir -p ${WRAP_DIR}/${i} for prog in collect2; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}/${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${i}/${prog} REAL=$(${i}-gcc -print-prog-name=${prog}) echo "export MALLOC_PERTURB_=255" >> ${WRAP_DIR}/${i}/${prog} echo "${REAL} \$@" >> $WRAP_DIR/${i}/${prog} chmod +x ${WRAP_DIR}/${i}/${prog} done for prog in gcc g++; do - echo '#!/bin/bash' > ${WRAP_DIR}/${i}-${prog} - echo "REAL=\`which -a ${i}-${prog} | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} + echo '#!/usr/bin/env bash' > ${WRAP_DIR}/${i}-${prog} + echo "REAL=\`which -a ${i}-${prog}-posix | grep -v ${WRAP_DIR}/${i}-${prog} | head -1\`" >> ${WRAP_DIR}/${i}-${prog} echo 'export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1' >> ${WRAP_DIR}/${i}-${prog} echo "export FAKETIME=\"$1\"" >> ${WRAP_DIR}/${i}-${prog} echo "export COMPILER_PATH=${WRAP_DIR}/${i}" >> ${WRAP_DIR}/${i}-${prog} diff --git a/contrib/gitian-keys/README.md b/contrib/gitian-keys/README.md index a9339c8bda..ffe4fb144b 100644 --- a/contrib/gitian-keys/README.md +++ b/contrib/gitian-keys/README.md @@ -1,9 +1,10 @@ ## PGP keys of Gitian builders and Developers -The keys.txt contains the public keys of Gitian builders and active developers. +The file `keys.txt` contains fingerprints of the public keys of Gitian builders +and active developers. -The keys are mainly used to sign git commits or the build results of Gitian -builds. +The associated keys are mainly used to sign git commits or the build results +of Gitian builds. The most recent version of each pgp key can be found on most pgp key servers. diff --git a/contrib/init/README.md b/contrib/init/README.md index 1a949f3c07..8d3e57c526 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 -OS X: 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/init/bitcoind.init b/contrib/init/bitcoind.init index db5061874b..0c95baf3a1 100644 --- a/contrib/init/bitcoind.init +++ b/contrib/init/bitcoind.init @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # bitcoind The bitcoin core server. # diff --git a/contrib/install_db4.sh b/contrib/install_db4.sh index d315a7d3b7..4f74e67f2f 100755 --- a/contrib/install_db4.sh +++ b/contrib/install_db4.sh @@ -2,6 +2,7 @@ # Install libdb4.8 (Berkeley DB). +export LC_ALL=C set -e if [ -z "${1}" ]; then diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index c609e9b336..b501388fd2 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -22,300 +22,300 @@ from binascii import hexlify, unhexlify settings = {} def hex_switchEndian(s): - """ Switches the endianness of a hex string (in pairs of hex chars) """ - pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] - return b''.join(pairList[::-1]).decode() + """ Switches the endianness of a hex string (in pairs of hex chars) """ + pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] + return b''.join(pairList[::-1]).decode() def uint32(x): - return x & 0xffffffff + return x & 0xffffffff def bytereverse(x): - return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | - (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) + return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | + (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) def bufreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - word = struct.unpack('@I', in_buf[i:i+4])[0] - out_words.append(struct.pack('@I', bytereverse(word))) - return b''.join(out_words) + out_words = [] + for i in range(0, len(in_buf), 4): + word = struct.unpack('@I', in_buf[i:i+4])[0] + out_words.append(struct.pack('@I', bytereverse(word))) + return b''.join(out_words) def wordreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - out_words.append(in_buf[i:i+4]) - out_words.reverse() - return b''.join(out_words) + out_words = [] + for i in range(0, len(in_buf), 4): + out_words.append(in_buf[i:i+4]) + out_words.reverse() + return b''.join(out_words) def calc_hdr_hash(blk_hdr): - hash1 = hashlib.sha256() - hash1.update(blk_hdr) - hash1_o = hash1.digest() + hash1 = hashlib.sha256() + hash1.update(blk_hdr) + hash1_o = hash1.digest() - hash2 = hashlib.sha256() - hash2.update(hash1_o) - hash2_o = hash2.digest() + hash2 = hashlib.sha256() + hash2.update(hash1_o) + hash2_o = hash2.digest() - return hash2_o + return hash2_o def calc_hash_str(blk_hdr): - hash = calc_hdr_hash(blk_hdr) - hash = bufreverse(hash) - hash = wordreverse(hash) - hash_str = hexlify(hash).decode('utf-8') - return hash_str + hash = calc_hdr_hash(blk_hdr) + hash = bufreverse(hash) + hash = wordreverse(hash) + hash_str = hexlify(hash).decode('utf-8') + return hash_str def get_blk_dt(blk_hdr): - members = struct.unpack("<I", blk_hdr[68:68+4]) - nTime = members[0] - dt = datetime.datetime.fromtimestamp(nTime) - dt_ym = datetime.datetime(dt.year, dt.month, 1) - return (dt_ym, nTime) + members = struct.unpack("<I", blk_hdr[68:68+4]) + nTime = members[0] + dt = datetime.datetime.fromtimestamp(nTime) + dt_ym = datetime.datetime(dt.year, dt.month, 1) + return (dt_ym, nTime) # When getting the list of block hashes, undo any byte reversals. def get_block_hashes(settings): - blkindex = [] - f = open(settings['hashlist'], "r") - for line in f: - line = line.rstrip() - if settings['rev_hash_bytes'] == 'true': - line = hex_switchEndian(line) - blkindex.append(line) + blkindex = [] + f = open(settings['hashlist'], "r", encoding="utf8") + for line in f: + line = line.rstrip() + if settings['rev_hash_bytes'] == 'true': + line = hex_switchEndian(line) + blkindex.append(line) - print("Read " + str(len(blkindex)) + " hashes") + print("Read " + str(len(blkindex)) + " hashes") - return blkindex + return blkindex # The block map shouldn't give or receive byte-reversed hashes. def mkblockmap(blkindex): - blkmap = {} - for height,hash in enumerate(blkindex): - blkmap[hash] = height - return blkmap + blkmap = {} + for height,hash in enumerate(blkindex): + blkmap[hash] = height + return blkmap # Block header and extent on disk BlockExtent = namedtuple('BlockExtent', ['fn', 'offset', 'inhdr', 'blkhdr', 'size']) class BlockDataCopier: - def __init__(self, settings, blkindex, blkmap): - self.settings = settings - self.blkindex = blkindex - self.blkmap = blkmap - - self.inFn = 0 - self.inF = None - self.outFn = 0 - self.outsz = 0 - self.outF = None - self.outFname = None - self.blkCountIn = 0 - self.blkCountOut = 0 - - self.lastDate = datetime.datetime(2000, 1, 1) - self.highTS = 1408893517 - 315360000 - self.timestampSplit = False - self.fileOutput = True - self.setFileTime = False - self.maxOutSz = settings['max_out_sz'] - if 'output' in settings: - self.fileOutput = False - if settings['file_timestamp'] != 0: - self.setFileTime = True - if settings['split_timestamp'] != 0: - self.timestampSplit = True - # Extents and cache for out-of-order blocks - self.blockExtents = {} - self.outOfOrderData = {} - self.outOfOrderSize = 0 # running total size for items in outOfOrderData - - def writeBlock(self, inhdr, blk_hdr, rawblock): - blockSizeOnDisk = len(inhdr) + len(blk_hdr) + len(rawblock) - if not self.fileOutput and ((self.outsz + blockSizeOnDisk) > self.maxOutSz): - self.outF.close() - if self.setFileTime: - os.utime(self.outFname, (int(time.time()), self.highTS)) - self.outF = None - self.outFname = None - self.outFn = self.outFn + 1 - self.outsz = 0 - - (blkDate, blkTS) = get_blk_dt(blk_hdr) - if self.timestampSplit and (blkDate > self.lastDate): - print("New month " + blkDate.strftime("%Y-%m") + " @ " + self.hash_str) - self.lastDate = blkDate - if self.outF: - self.outF.close() - if self.setFileTime: - os.utime(self.outFname, (int(time.time()), self.highTS)) - self.outF = None - self.outFname = None - self.outFn = self.outFn + 1 - self.outsz = 0 - - if not self.outF: - if self.fileOutput: - self.outFname = self.settings['output_file'] - else: - self.outFname = os.path.join(self.settings['output'], "blk%05d.dat" % self.outFn) - print("Output file " + self.outFname) - self.outF = open(self.outFname, "wb") - - self.outF.write(inhdr) - self.outF.write(blk_hdr) - self.outF.write(rawblock) - self.outsz = self.outsz + len(inhdr) + len(blk_hdr) + len(rawblock) - - self.blkCountOut = self.blkCountOut + 1 - if blkTS > self.highTS: - self.highTS = blkTS - - if (self.blkCountOut % 1000) == 0: - print('%i blocks scanned, %i blocks written (of %i, %.1f%% complete)' % - (self.blkCountIn, self.blkCountOut, len(self.blkindex), 100.0 * self.blkCountOut / len(self.blkindex))) - - def inFileName(self, fn): - return os.path.join(self.settings['input'], "blk%05d.dat" % fn) - - def fetchBlock(self, extent): - '''Fetch block contents from disk given extents''' - with open(self.inFileName(extent.fn), "rb") as f: - f.seek(extent.offset) - return f.read(extent.size) - - def copyOneBlock(self): - '''Find the next block to be written in the input, and copy it to the output.''' - extent = self.blockExtents.pop(self.blkCountOut) - if self.blkCountOut in self.outOfOrderData: - # If the data is cached, use it from memory and remove from the cache - rawblock = self.outOfOrderData.pop(self.blkCountOut) - self.outOfOrderSize -= len(rawblock) - else: # Otherwise look up data on disk - rawblock = self.fetchBlock(extent) - - self.writeBlock(extent.inhdr, extent.blkhdr, rawblock) - - def run(self): - while self.blkCountOut < len(self.blkindex): - if not self.inF: - fname = self.inFileName(self.inFn) - print("Input file " + fname) - try: - self.inF = open(fname, "rb") - except IOError: - print("Premature end of block data") - return - - inhdr = self.inF.read(8) - if (not inhdr or (inhdr[0] == "\0")): - self.inF.close() - self.inF = None - self.inFn = self.inFn + 1 - continue - - inMagic = inhdr[:4] - if (inMagic != self.settings['netmagic']): - print("Invalid magic: " + hexlify(inMagic).decode('utf-8')) - return - inLenLE = inhdr[4:] - su = struct.unpack("<I", inLenLE) - inLen = su[0] - 80 # length without header - blk_hdr = self.inF.read(80) - inExtent = BlockExtent(self.inFn, self.inF.tell(), inhdr, blk_hdr, inLen) - - self.hash_str = calc_hash_str(blk_hdr) - if not self.hash_str in blkmap: - # Because blocks can be written to files out-of-order as of 0.10, the script - # may encounter blocks it doesn't know about. Treat as debug output. - if settings['debug_output'] == 'true': - print("Skipping unknown block " + self.hash_str) - self.inF.seek(inLen, os.SEEK_CUR) - continue - - blkHeight = self.blkmap[self.hash_str] - self.blkCountIn += 1 - - if self.blkCountOut == blkHeight: - # If in-order block, just copy - rawblock = self.inF.read(inLen) - self.writeBlock(inhdr, blk_hdr, rawblock) - - # See if we can catch up to prior out-of-order blocks - while self.blkCountOut in self.blockExtents: - self.copyOneBlock() - - else: # If out-of-order, skip over block data for now - self.blockExtents[blkHeight] = inExtent - if self.outOfOrderSize < self.settings['out_of_order_cache_sz']: - # If there is space in the cache, read the data - # Reading the data in file sequence instead of seeking and fetching it later is preferred, - # but we don't want to fill up memory - self.outOfOrderData[blkHeight] = self.inF.read(inLen) - self.outOfOrderSize += inLen - else: # If no space in cache, seek forward - self.inF.seek(inLen, os.SEEK_CUR) - - print("Done (%i blocks written)" % (self.blkCountOut)) + def __init__(self, settings, blkindex, blkmap): + self.settings = settings + self.blkindex = blkindex + self.blkmap = blkmap + + self.inFn = 0 + self.inF = None + self.outFn = 0 + self.outsz = 0 + self.outF = None + self.outFname = None + self.blkCountIn = 0 + self.blkCountOut = 0 + + self.lastDate = datetime.datetime(2000, 1, 1) + self.highTS = 1408893517 - 315360000 + self.timestampSplit = False + self.fileOutput = True + self.setFileTime = False + self.maxOutSz = settings['max_out_sz'] + if 'output' in settings: + self.fileOutput = False + if settings['file_timestamp'] != 0: + self.setFileTime = True + if settings['split_timestamp'] != 0: + self.timestampSplit = True + # Extents and cache for out-of-order blocks + self.blockExtents = {} + self.outOfOrderData = {} + self.outOfOrderSize = 0 # running total size for items in outOfOrderData + + def writeBlock(self, inhdr, blk_hdr, rawblock): + blockSizeOnDisk = len(inhdr) + len(blk_hdr) + len(rawblock) + if not self.fileOutput and ((self.outsz + blockSizeOnDisk) > self.maxOutSz): + self.outF.close() + if self.setFileTime: + os.utime(self.outFname, (int(time.time()), self.highTS)) + self.outF = None + self.outFname = None + self.outFn = self.outFn + 1 + self.outsz = 0 + + (blkDate, blkTS) = get_blk_dt(blk_hdr) + if self.timestampSplit and (blkDate > self.lastDate): + print("New month " + blkDate.strftime("%Y-%m") + " @ " + self.hash_str) + self.lastDate = blkDate + if self.outF: + self.outF.close() + if self.setFileTime: + os.utime(self.outFname, (int(time.time()), self.highTS)) + self.outF = None + self.outFname = None + self.outFn = self.outFn + 1 + self.outsz = 0 + + if not self.outF: + if self.fileOutput: + self.outFname = self.settings['output_file'] + else: + self.outFname = os.path.join(self.settings['output'], "blk%05d.dat" % self.outFn) + print("Output file " + self.outFname) + self.outF = open(self.outFname, "wb") + + self.outF.write(inhdr) + self.outF.write(blk_hdr) + self.outF.write(rawblock) + self.outsz = self.outsz + len(inhdr) + len(blk_hdr) + len(rawblock) + + self.blkCountOut = self.blkCountOut + 1 + if blkTS > self.highTS: + self.highTS = blkTS + + if (self.blkCountOut % 1000) == 0: + print('%i blocks scanned, %i blocks written (of %i, %.1f%% complete)' % + (self.blkCountIn, self.blkCountOut, len(self.blkindex), 100.0 * self.blkCountOut / len(self.blkindex))) + + def inFileName(self, fn): + return os.path.join(self.settings['input'], "blk%05d.dat" % fn) + + def fetchBlock(self, extent): + '''Fetch block contents from disk given extents''' + with open(self.inFileName(extent.fn), "rb") as f: + f.seek(extent.offset) + return f.read(extent.size) + + def copyOneBlock(self): + '''Find the next block to be written in the input, and copy it to the output.''' + extent = self.blockExtents.pop(self.blkCountOut) + if self.blkCountOut in self.outOfOrderData: + # If the data is cached, use it from memory and remove from the cache + rawblock = self.outOfOrderData.pop(self.blkCountOut) + self.outOfOrderSize -= len(rawblock) + else: # Otherwise look up data on disk + rawblock = self.fetchBlock(extent) + + self.writeBlock(extent.inhdr, extent.blkhdr, rawblock) + + def run(self): + while self.blkCountOut < len(self.blkindex): + if not self.inF: + fname = self.inFileName(self.inFn) + print("Input file " + fname) + try: + self.inF = open(fname, "rb") + except IOError: + print("Premature end of block data") + return + + inhdr = self.inF.read(8) + if (not inhdr or (inhdr[0] == "\0")): + self.inF.close() + self.inF = None + self.inFn = self.inFn + 1 + continue + + inMagic = inhdr[:4] + if (inMagic != self.settings['netmagic']): + print("Invalid magic: " + hexlify(inMagic).decode('utf-8')) + return + inLenLE = inhdr[4:] + su = struct.unpack("<I", inLenLE) + inLen = su[0] - 80 # length without header + blk_hdr = self.inF.read(80) + inExtent = BlockExtent(self.inFn, self.inF.tell(), inhdr, blk_hdr, inLen) + + self.hash_str = calc_hash_str(blk_hdr) + if not self.hash_str in blkmap: + # Because blocks can be written to files out-of-order as of 0.10, the script + # may encounter blocks it doesn't know about. Treat as debug output. + if settings['debug_output'] == 'true': + print("Skipping unknown block " + self.hash_str) + self.inF.seek(inLen, os.SEEK_CUR) + continue + + blkHeight = self.blkmap[self.hash_str] + self.blkCountIn += 1 + + if self.blkCountOut == blkHeight: + # If in-order block, just copy + rawblock = self.inF.read(inLen) + self.writeBlock(inhdr, blk_hdr, rawblock) + + # See if we can catch up to prior out-of-order blocks + while self.blkCountOut in self.blockExtents: + self.copyOneBlock() + + else: # If out-of-order, skip over block data for now + self.blockExtents[blkHeight] = inExtent + if self.outOfOrderSize < self.settings['out_of_order_cache_sz']: + # If there is space in the cache, read the data + # Reading the data in file sequence instead of seeking and fetching it later is preferred, + # but we don't want to fill up memory + self.outOfOrderData[blkHeight] = self.inF.read(inLen) + self.outOfOrderSize += inLen + else: # If no space in cache, seek forward + self.inF.seek(inLen, os.SEEK_CUR) + + print("Done (%i blocks written)" % (self.blkCountOut)) if __name__ == '__main__': - if len(sys.argv) != 2: - print("Usage: linearize-data.py CONFIG-FILE") - sys.exit(1) - - f = open(sys.argv[1]) - for line in f: - # skip comment lines - m = re.search('^\s*#', line) - if m: - continue - - # parse key=value lines - m = re.search('^(\w+)\s*=\s*(\S.*)$', line) - if m is None: - continue - settings[m.group(1)] = m.group(2) - f.close() - - # Force hash byte format setting to be lowercase to make comparisons easier. - # Also place upfront in case any settings need to know about it. - if 'rev_hash_bytes' not in settings: - settings['rev_hash_bytes'] = 'false' - settings['rev_hash_bytes'] = settings['rev_hash_bytes'].lower() - - if 'netmagic' not in settings: - settings['netmagic'] = 'f9beb4d9' - if 'genesis' not in settings: - settings['genesis'] = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' - if 'input' not in settings: - settings['input'] = 'input' - if 'hashlist' not in settings: - settings['hashlist'] = 'hashlist.txt' - if 'file_timestamp' not in settings: - settings['file_timestamp'] = 0 - if 'split_timestamp' not in settings: - settings['split_timestamp'] = 0 - if 'max_out_sz' not in settings: - settings['max_out_sz'] = 1000 * 1000 * 1000 - if 'out_of_order_cache_sz' not in settings: - settings['out_of_order_cache_sz'] = 100 * 1000 * 1000 - if 'debug_output' not in settings: - settings['debug_output'] = 'false' - - settings['max_out_sz'] = int(settings['max_out_sz']) - settings['split_timestamp'] = int(settings['split_timestamp']) - settings['file_timestamp'] = int(settings['file_timestamp']) - settings['netmagic'] = unhexlify(settings['netmagic'].encode('utf-8')) - settings['out_of_order_cache_sz'] = int(settings['out_of_order_cache_sz']) - settings['debug_output'] = settings['debug_output'].lower() - - if 'output_file' not in settings and 'output' not in settings: - print("Missing output file / directory") - sys.exit(1) - - blkindex = get_block_hashes(settings) - blkmap = mkblockmap(blkindex) - - # Block hash map won't be byte-reversed. Neither should the genesis hash. - if not settings['genesis'] in blkmap: - print("Genesis block not found in hashlist") - else: - BlockDataCopier(settings, blkindex, blkmap).run() + if len(sys.argv) != 2: + print("Usage: linearize-data.py CONFIG-FILE") + sys.exit(1) + + f = open(sys.argv[1], encoding="utf8") + for line in f: + # skip comment lines + m = re.search('^\s*#', line) + if m: + continue + + # parse key=value lines + m = re.search('^(\w+)\s*=\s*(\S.*)$', line) + if m is None: + continue + settings[m.group(1)] = m.group(2) + f.close() + + # Force hash byte format setting to be lowercase to make comparisons easier. + # Also place upfront in case any settings need to know about it. + if 'rev_hash_bytes' not in settings: + settings['rev_hash_bytes'] = 'false' + settings['rev_hash_bytes'] = settings['rev_hash_bytes'].lower() + + if 'netmagic' not in settings: + settings['netmagic'] = 'f9beb4d9' + if 'genesis' not in settings: + settings['genesis'] = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' + if 'input' not in settings: + settings['input'] = 'input' + if 'hashlist' not in settings: + settings['hashlist'] = 'hashlist.txt' + if 'file_timestamp' not in settings: + settings['file_timestamp'] = 0 + if 'split_timestamp' not in settings: + settings['split_timestamp'] = 0 + if 'max_out_sz' not in settings: + settings['max_out_sz'] = 1000 * 1000 * 1000 + if 'out_of_order_cache_sz' not in settings: + settings['out_of_order_cache_sz'] = 100 * 1000 * 1000 + if 'debug_output' not in settings: + settings['debug_output'] = 'false' + + settings['max_out_sz'] = int(settings['max_out_sz']) + settings['split_timestamp'] = int(settings['split_timestamp']) + settings['file_timestamp'] = int(settings['file_timestamp']) + settings['netmagic'] = unhexlify(settings['netmagic'].encode('utf-8')) + settings['out_of_order_cache_sz'] = int(settings['out_of_order_cache_sz']) + settings['debug_output'] = settings['debug_output'].lower() + + if 'output_file' not in settings and 'output' not in settings: + print("Missing output file / directory") + sys.exit(1) + + blkindex = get_block_hashes(settings) + blkmap = mkblockmap(blkindex) + + # Block hash map won't be byte-reversed. Neither should the genesis hash. + if not settings['genesis'] in blkmap: + print("Genesis block not found in hashlist") + else: + BlockDataCopier(settings, blkindex, blkmap).run() diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py index e1304e26d0..bfd2171947 100755 --- a/contrib/linearize/linearize-hashes.py +++ b/contrib/linearize/linearize-hashes.py @@ -22,135 +22,135 @@ import os.path settings = {} def hex_switchEndian(s): - """ Switches the endianness of a hex string (in pairs of hex chars) """ - pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] - return b''.join(pairList[::-1]).decode() + """ Switches the endianness of a hex string (in pairs of hex chars) """ + pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] + return b''.join(pairList[::-1]).decode() class BitcoinRPC: - def __init__(self, host, port, username, password): - authpair = "%s:%s" % (username, password) - authpair = authpair.encode('utf-8') - self.authhdr = b"Basic " + base64.b64encode(authpair) - self.conn = httplib.HTTPConnection(host, port=port, timeout=30) - - def execute(self, obj): - try: - self.conn.request('POST', '/', json.dumps(obj), - { 'Authorization' : self.authhdr, - 'Content-type' : 'application/json' }) - except ConnectionRefusedError: - print('RPC connection refused. Check RPC settings and the server status.', - file=sys.stderr) - return None - - resp = self.conn.getresponse() - if resp is None: - print("JSON-RPC: no response", file=sys.stderr) - return None - - body = resp.read().decode('utf-8') - resp_obj = json.loads(body) - return resp_obj - - @staticmethod - def build_request(idx, method, params): - obj = { 'version' : '1.1', - 'method' : method, - 'id' : idx } - if params is None: - obj['params'] = [] - else: - obj['params'] = params - return obj - - @staticmethod - def response_is_error(resp_obj): - return 'error' in resp_obj and resp_obj['error'] is not None + def __init__(self, host, port, username, password): + 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) + + def execute(self, obj): + try: + self.conn.request('POST', '/', json.dumps(obj), + { 'Authorization' : self.authhdr, + 'Content-type' : 'application/json' }) + except ConnectionRefusedError: + print('RPC connection refused. Check RPC settings and the server status.', + file=sys.stderr) + return None + + resp = self.conn.getresponse() + if resp is None: + print("JSON-RPC: no response", file=sys.stderr) + return None + + body = resp.read().decode('utf-8') + resp_obj = json.loads(body) + return resp_obj + + @staticmethod + def build_request(idx, method, params): + obj = { 'version' : '1.1', + 'method' : method, + 'id' : idx } + if params is None: + obj['params'] = [] + else: + obj['params'] = params + return obj + + @staticmethod + def response_is_error(resp_obj): + return 'error' in resp_obj and resp_obj['error'] is not None def get_block_hashes(settings, max_blocks_per_call=10000): - rpc = BitcoinRPC(settings['host'], settings['port'], - settings['rpcuser'], settings['rpcpassword']) - - height = settings['min_height'] - while height < settings['max_height']+1: - num_blocks = min(settings['max_height']+1-height, max_blocks_per_call) - batch = [] - for x in range(num_blocks): - batch.append(rpc.build_request(x, 'getblockhash', [height + x])) - - reply = rpc.execute(batch) - if reply is None: - print('Cannot continue. Program will halt.') - return None - - for x,resp_obj in enumerate(reply): - if rpc.response_is_error(resp_obj): - print('JSON-RPC: error at height', height+x, ': ', resp_obj['error'], file=sys.stderr) - sys.exit(1) - assert(resp_obj['id'] == x) # assume replies are in-sequence - if settings['rev_hash_bytes'] == 'true': - resp_obj['result'] = hex_switchEndian(resp_obj['result']) - print(resp_obj['result']) - - height += num_blocks + rpc = BitcoinRPC(settings['host'], settings['port'], + settings['rpcuser'], settings['rpcpassword']) + + height = settings['min_height'] + while height < settings['max_height']+1: + num_blocks = min(settings['max_height']+1-height, max_blocks_per_call) + batch = [] + for x in range(num_blocks): + batch.append(rpc.build_request(x, 'getblockhash', [height + x])) + + reply = rpc.execute(batch) + if reply is None: + print('Cannot continue. Program will halt.') + return None + + for x,resp_obj in enumerate(reply): + if rpc.response_is_error(resp_obj): + print('JSON-RPC: error at height', height+x, ': ', resp_obj['error'], file=sys.stderr) + sys.exit(1) + assert(resp_obj['id'] == x) # assume replies are in-sequence + if settings['rev_hash_bytes'] == 'true': + resp_obj['result'] = hex_switchEndian(resp_obj['result']) + print(resp_obj['result']) + + height += num_blocks def get_rpc_cookie(): - # Open the cookie file - with open(os.path.join(os.path.expanduser(settings['datadir']), '.cookie'), 'r') as f: - combined = f.readline() - combined_split = combined.split(":") - settings['rpcuser'] = combined_split[0] - settings['rpcpassword'] = combined_split[1] + # Open the cookie file + with open(os.path.join(os.path.expanduser(settings['datadir']), '.cookie'), 'r', encoding="ascii") as f: + combined = f.readline() + combined_split = combined.split(":") + settings['rpcuser'] = combined_split[0] + settings['rpcpassword'] = combined_split[1] if __name__ == '__main__': - if len(sys.argv) != 2: - print("Usage: linearize-hashes.py CONFIG-FILE") - sys.exit(1) - - f = open(sys.argv[1]) - for line in f: - # skip comment lines - m = re.search('^\s*#', line) - if m: - continue - - # parse key=value lines - m = re.search('^(\w+)\s*=\s*(\S.*)$', line) - if m is None: - continue - settings[m.group(1)] = m.group(2) - f.close() - - if 'host' not in settings: - settings['host'] = '127.0.0.1' - if 'port' not in settings: - settings['port'] = 8332 - if 'min_height' not in settings: - settings['min_height'] = 0 - if 'max_height' not in settings: - settings['max_height'] = 313000 - if 'rev_hash_bytes' not in settings: - settings['rev_hash_bytes'] = 'false' - - use_userpass = True - use_datadir = False - if 'rpcuser' not in settings or 'rpcpassword' not in settings: - use_userpass = False - if 'datadir' in settings and not use_userpass: - use_datadir = True - if not use_userpass and not use_datadir: - print("Missing datadir or username and/or password in cfg file", file=sys.stderr) - sys.exit(1) - - settings['port'] = int(settings['port']) - settings['min_height'] = int(settings['min_height']) - settings['max_height'] = int(settings['max_height']) - - # Force hash byte format setting to be lowercase to make comparisons easier. - settings['rev_hash_bytes'] = settings['rev_hash_bytes'].lower() - - # Get the rpc user and pass from the cookie if the datadir is set - if use_datadir: - get_rpc_cookie() - - get_block_hashes(settings) + if len(sys.argv) != 2: + print("Usage: linearize-hashes.py CONFIG-FILE") + sys.exit(1) + + f = open(sys.argv[1], encoding="utf8") + for line in f: + # skip comment lines + m = re.search('^\s*#', line) + if m: + continue + + # parse key=value lines + m = re.search('^(\w+)\s*=\s*(\S.*)$', line) + if m is None: + continue + settings[m.group(1)] = m.group(2) + f.close() + + if 'host' not in settings: + settings['host'] = '127.0.0.1' + if 'port' not in settings: + settings['port'] = 8332 + if 'min_height' not in settings: + settings['min_height'] = 0 + if 'max_height' not in settings: + settings['max_height'] = 313000 + if 'rev_hash_bytes' not in settings: + settings['rev_hash_bytes'] = 'false' + + use_userpass = True + use_datadir = False + if 'rpcuser' not in settings or 'rpcpassword' not in settings: + use_userpass = False + if 'datadir' in settings and not use_userpass: + use_datadir = True + if not use_userpass and not use_datadir: + print("Missing datadir or username and/or password in cfg file", file=sys.stderr) + sys.exit(1) + + settings['port'] = int(settings['port']) + settings['min_height'] = int(settings['min_height']) + settings['max_height'] = int(settings['max_height']) + + # Force hash byte format setting to be lowercase to make comparisons easier. + settings['rev_hash_bytes'] = settings['rev_hash_bytes'].lower() + + # Get the rpc user and pass from the cookie if the datadir is set + if use_datadir: + get_rpc_cookie() + + get_block_hashes(settings) diff --git a/contrib/macdeploy/detached-sig-apply.sh b/contrib/macdeploy/detached-sig-apply.sh index 91674a92e6..f8503e4de8 100755 --- a/contrib/macdeploy/detached-sig-apply.sh +++ b/contrib/macdeploy/detached-sig-apply.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C set -e UNSIGNED="$1" diff --git a/contrib/macdeploy/detached-sig-create.sh b/contrib/macdeploy/detached-sig-create.sh index 3379a4599c..5281ebcc47 100755 --- a/contrib/macdeploy/detached-sig-create.sh +++ b/contrib/macdeploy/detached-sig-create.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C set -e ROOTDIR=dist diff --git a/contrib/macdeploy/extract-osx-sdk.sh b/contrib/macdeploy/extract-osx-sdk.sh index ff9fbd58df..4c175156f4 100755 --- a/contrib/macdeploy/extract-osx-sdk.sh +++ b/contrib/macdeploy/extract-osx-sdk.sh @@ -1,8 +1,9 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright (c) 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. +export LC_ALL=C set -e INPUTFILE="Xcode_7.3.1.dmg" diff --git a/contrib/qos/tc.sh b/contrib/qos/tc.sh index 0d1dd65b4f..738ea70dbe 100644 --- a/contrib/qos/tc.sh +++ b/contrib/qos/tc.sh @@ -2,6 +2,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C #network interface on which to limit traffic IF="eth0" #limit of the network interface in question diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index 72eb7255f3..fe7cd1d597 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -11,7 +11,7 @@ argument: nodes_main.txt nodes_test.txt -These files must consist of lines in the format +These files must consist of lines in the format <ip> <ip>:<port> @@ -127,10 +127,10 @@ def main(): g.write(' * Each line contains a 16-byte IPv6 address and a port.\n') g.write(' * IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly.\n') g.write(' */\n') - with open(os.path.join(indir,'nodes_main.txt'),'r') as f: + with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'pnSeed6_main', 8333) g.write('\n') - with open(os.path.join(indir,'nodes_test.txt'),'r') as f: + with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'pnSeed6_test', 18333) g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n') diff --git a/contrib/tidy_datadir.sh b/contrib/tidy_datadir.sh deleted file mode 100755 index b845b34e41..0000000000 --- a/contrib/tidy_datadir.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Copyright (c) 2013 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 [ -d "$1" ]; then - cd "$1" || exit 1 -else - echo "Usage: $0 <datadir>" >&2 - echo "Removes obsolete Bitcoin database files" >&2 - exit 1 -fi - -LEVEL=0 -if [ -f wallet.dat -a -f addr.dat -a -f blkindex.dat -a -f blk0001.dat ]; then LEVEL=1; fi -if [ -f wallet.dat -a -f peers.dat -a -f blkindex.dat -a -f blk0001.dat ]; then LEVEL=2; fi -if [ -f wallet.dat -a -f peers.dat -a -f coins/CURRENT -a -f blktree/CURRENT -a -f blocks/blk00000.dat ]; then LEVEL=3; fi -if [ -f wallet.dat -a -f peers.dat -a -f chainstate/CURRENT -a -f blocks/index/CURRENT -a -f blocks/blk00000.dat ]; then LEVEL=4; fi - -case $LEVEL in - 0) - echo "Error: no Bitcoin datadir detected." - exit 1 - ;; - 1) - echo "Detected old Bitcoin datadir (before 0.7)." - echo "Nothing to do." - exit 0 - ;; - 2) - echo "Detected Bitcoin 0.7 datadir." - ;; - 3) - echo "Detected Bitcoin pre-0.8 datadir." - ;; - 4) - echo "Detected Bitcoin 0.8 datadir." - ;; -esac - -FILES="" -DIRS="" - -if [ $LEVEL -ge 3 ]; then FILES=$(echo $FILES blk????.dat blkindex.dat); fi -if [ $LEVEL -ge 2 ]; then FILES=$(echo $FILES addr.dat); fi -if [ $LEVEL -ge 4 ]; then DIRS=$(echo $DIRS coins blktree); fi - -for FILE in $FILES; do - if [ -f $FILE ]; then - echo "Deleting: $FILE" - rm -f $FILE - fi -done - -for DIR in $DIRS; do - if [ -d $DIR ]; then - echo "Deleting: $DIR/" - rm -rf $DIR - fi -done - -echo "Done." diff --git a/contrib/verify-commits/README.md b/contrib/verify-commits/README.md index fa492fdd27..aa805ad1b9 100644 --- a/contrib/verify-commits/README.md +++ b/contrib/verify-commits/README.md @@ -7,18 +7,18 @@ are PGP signed (nearly always merge commits), as well as a script to verify commits against a trusted keys list. -Using verify-commits.sh safely +Using verify-commits.py safely ------------------------------ Remember that you can't use an untrusted script to verify itself. This means -that checking out code, then running `verify-commits.sh` against `HEAD` is -_not_ safe, because the version of `verify-commits.sh` that you just ran could +that checking out code, then running `verify-commits.py` against `HEAD` is +_not_ safe, because the version of `verify-commits.py` that you just ran could 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.sh origin/master && \ + ./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 @@ -42,6 +42,6 @@ said key. In order to avoid bumping the root-of-trust `trusted-git-root` file, individual commits which were signed by such a key can be added to the `allow-revsig-commits` file. That way, the PGP signatures are still verified but no new commits can be signed by any expired/revoked key. To easily build a -list of commits which need to be added, verify-commits.sh can be edited to test +list of commits which need to be added, verify-commits.py can be edited to test each commit with BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG set to both 1 and 0, and those which need it set to 1 printed. diff --git a/contrib/verify-commits/allow-incorrect-sha512-commits b/contrib/verify-commits/allow-incorrect-sha512-commits new file mode 100644 index 0000000000..c572806f26 --- /dev/null +++ b/contrib/verify-commits/allow-incorrect-sha512-commits @@ -0,0 +1,2 @@ +f8feaa4636260b599294c7285bcf1c8b7737f74e +8040ae6fc576e9504186f2ae3ff2c8125de1095c diff --git a/contrib/verify-commits/allow-unclean-merge-commits b/contrib/verify-commits/allow-unclean-merge-commits new file mode 100644 index 0000000000..7aab274b9a --- /dev/null +++ b/contrib/verify-commits/allow-unclean-merge-commits @@ -0,0 +1,4 @@ +6052d509105790a26b3ad5df43dd61e7f1b24a12 +3798e5de334c3deb5f71302b782f6b8fbd5087f1 +326ffed09bfcc209a2efd6a2ebc69edf6bd200b5 +97d83739db0631be5d4ba86af3616014652c00ec diff --git a/contrib/verify-commits/gpg.sh b/contrib/verify-commits/gpg.sh index 8f3e4b8063..7a10ba7d7d 100755 --- a/contrib/verify-commits/gpg.sh +++ b/contrib/verify-commits/gpg.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C INPUT=$(cat /dev/stdin) VALID=false REVSIG=false @@ -57,7 +58,7 @@ if ! $VALID; then exit 1 fi if $VALID && $REVSIG; then - printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null | grep "\[GNUPG:\] \(NEWSIG\|SIG_ID\|VALIDSIG\)" + printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null | grep "^\[GNUPG:\] \(NEWSIG\|SIG_ID\|VALIDSIG\)" echo "$GOODREVSIG" else printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null diff --git a/contrib/verify-commits/pre-push-hook.sh b/contrib/verify-commits/pre-push-hook.sh index c21febb9e9..4db4a90853 100755 --- a/contrib/verify-commits/pre-push-hook.sh +++ b/contrib/verify-commits/pre-push-hook.sh @@ -1,8 +1,9 @@ -#!/bin/bash +#!/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. +export LC_ALL=C if ! [[ "$2" =~ ^(git@)?(www.)?github.com(:|/)bitcoin/bitcoin(.git)?$ ]]; then exit 0 fi @@ -12,9 +13,9 @@ while read LINE; do if [ "$4" != "refs/heads/master" ]; then continue fi - if ! ./contrib/verify-commits/verify-commits.sh $3 > /dev/null 2>&1; then + if ! ./contrib/verify-commits/verify-commits.py $3 > /dev/null 2>&1; then echo "ERROR: A commit is not signed, can't push" - ./contrib/verify-commits/verify-commits.sh + ./contrib/verify-commits/verify-commits.py exit 1 fi done < /dev/stdin diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py new file mode 100755 index 0000000000..a9e4977715 --- /dev/null +++ b/contrib/verify-commits/verify-commits.py @@ -0,0 +1,155 @@ +#!/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. +"""Verify commits against a trusted keys list.""" +import argparse +import hashlib +import os +import subprocess +import sys +import time + +GIT = os.getenv('GIT', 'git') + +def tree_sha512sum(commit='HEAD'): + """Calculate the Tree-sha512 for the commit. + + This is copied from github-merge.py.""" + + # request metadata for entire tree, recursively + files = [] + blob_by_name = {} + for line in subprocess.check_output([GIT, 'ls-tree', '--full-tree', '-r', commit]).splitlines(): + name_sep = line.index(b'\t') + metadata = line[:name_sep].split() # perms, 'blob', blobid + assert metadata[1] == b'blob' + name = line[name_sep + 1:] + files.append(name) + blob_by_name[name] = metadata[2] + + files.sort() + # open connection to git-cat-file in batch mode to request data for all blobs + # this is much faster than launching it per file + p = subprocess.Popen([GIT, 'cat-file', '--batch'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) + overall = hashlib.sha512() + for f in files: + blob = blob_by_name[f] + # request blob + p.stdin.write(blob + b'\n') + p.stdin.flush() + # read header: blob, "blob", size + reply = p.stdout.readline().split() + assert reply[0] == blob and reply[1] == b'blob' + size = int(reply[2]) + # hash the blob data + intern = hashlib.sha512() + ptr = 0 + while ptr < size: + bs = min(65536, size - ptr) + piece = p.stdout.read(bs) + if len(piece) == bs: + intern.update(piece) + else: + raise IOError('Premature EOF reading git cat-file output') + ptr += bs + dig = intern.hexdigest() + assert p.stdout.read(1) == b'\n' # ignore LF that follows blob data + # update overall hash with file hash + overall.update(dig.encode("utf-8")) + overall.update(" ".encode("utf-8")) + overall.update(f) + overall.update("\n".encode("utf-8")) + p.stdin.close() + if p.wait(): + raise IOError('Non-zero return value executing git cat-file') + return overall.hexdigest() + +def main(): + # Parse arguments + parser = argparse.ArgumentParser(usage='%(prog)s [options] [commit id]') + parser.add_argument('--disable-tree-check', action='store_false', dest='verify_tree', help='disable SHA-512 tree check') + parser.add_argument('--clean-merge', type=float, dest='clean_merge', default=float('inf'), help='Only check clean merge after <NUMBER> days ago (default: %(default)s)', metavar='NUMBER') + parser.add_argument('commit', nargs='?', default='HEAD', help='Check clean merge up to commit <commit>') + args = parser.parse_args() + + # get directory of this program and read data files + dirname = os.path.dirname(os.path.abspath(__file__)) + print("Using verify-commits data from " + dirname) + verified_root = open(dirname + "/trusted-git-root", "r", encoding="utf8").read().splitlines()[0] + verified_sha512_root = open(dirname + "/trusted-sha512-root-commit", "r", encoding="utf8").read().splitlines()[0] + revsig_allowed = open(dirname + "/allow-revsig-commits", "r", encoding="utf-8").read().splitlines() + unclean_merge_allowed = open(dirname + "/allow-unclean-merge-commits", "r", encoding="utf-8").read().splitlines() + incorrect_sha512_allowed = open(dirname + "/allow-incorrect-sha512-commits", "r", encoding="utf-8").read().splitlines() + + # Set commit and branch and set variables + current_commit = args.commit + if ' ' in current_commit: + print("Commit must not contain spaces", file=sys.stderr) + sys.exit(1) + verify_tree = args.verify_tree + no_sha1 = True + prev_commit = "" + initial_commit = current_commit + branch = subprocess.check_output([GIT, 'show', '-s', '--format=%H', initial_commit], universal_newlines=True).splitlines()[0] + + # Iterate through commits + while True: + if current_commit == verified_root: + print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root)) + sys.exit(0) + if current_commit == verified_sha512_root: + if verify_tree: + print("All Tree-SHA512s matched up to {}".format(verified_sha512_root), file=sys.stderr) + verify_tree = False + no_sha1 = False + + os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_SHA1'] = "0" if no_sha1 else "1" + os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG'] = "1" if current_commit in revsig_allowed else "0" + + # Check that the commit (and parents) was signed with a trusted key + if subprocess.call([GIT, '-c', 'gpg.program={}/gpg.sh'.format(dirname), 'verify-commit', current_commit], stdout=subprocess.DEVNULL): + if prev_commit != "": + print("No parent of {} was signed with a trusted key!".format(prev_commit), file=sys.stderr) + print("Parents are:", file=sys.stderr) + parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', prev_commit], universal_newlines=True).splitlines()[0].split(' ') + for parent in parents: + subprocess.call([GIT, 'show', '-s', parent], stdout=sys.stderr) + else: + print("{} was not signed with a trusted key!".format(current_commit), file=sys.stderr) + sys.exit(1) + + # Check the Tree-SHA512 + if (verify_tree or prev_commit == "") and current_commit not in incorrect_sha512_allowed: + tree_hash = tree_sha512sum(current_commit) + if ("Tree-SHA512: {}".format(tree_hash)) not in subprocess.check_output([GIT, 'show', '-s', '--format=format:%B', current_commit], universal_newlines=True).splitlines(): + print("Tree-SHA512 did not match for commit " + current_commit, file=sys.stderr) + sys.exit(1) + + # Merge commits should only have two parents + parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', current_commit], universal_newlines=True).splitlines()[0].split(' ') + if len(parents) > 2: + print("Commit {} is an octopus merge".format(current_commit), file=sys.stderr) + sys.exit(1) + + # Check that the merge commit is clean + commit_time = int(subprocess.check_output([GIT, 'show', '-s', '--format=format:%ct', current_commit], universal_newlines=True).splitlines()[0]) + check_merge = commit_time > time.time() - args.clean_merge * 24 * 60 * 60 # Only check commits in clean_merge days + allow_unclean = current_commit in unclean_merge_allowed + 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).splitlines()[0] + subprocess.call([GIT, 'checkout', '--force', '--quiet', parents[0]]) + subprocess.call([GIT, 'merge', '--no-ff', '--quiet', parents[1]], stdout=subprocess.DEVNULL) + recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD'], universal_newlines=True).splitlines()[0] + if current_tree != recreated_tree: + print("Merge commit {} is not clean".format(current_commit), file=sys.stderr) + subprocess.call([GIT, 'diff', current_commit]) + subprocess.call([GIT, 'checkout', '--force', '--quiet', branch]) + sys.exit(1) + subprocess.call([GIT, 'checkout', '--force', '--quiet', branch]) + + prev_commit = current_commit + current_commit = parents[0] + +if __name__ == '__main__': + main() diff --git a/contrib/verify-commits/verify-commits.sh b/contrib/verify-commits/verify-commits.sh deleted file mode 100755 index 6415eea4d5..0000000000 --- a/contrib/verify-commits/verify-commits.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/sh -# Copyright (c) 2014-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. - -DIR=$(dirname "$0") -[ "/${DIR#/}" != "$DIR" ] && DIR=$(dirname "$(pwd)/$0") - -echo "Using verify-commits data from ${DIR}" - -VERIFIED_ROOT=$(cat "${DIR}/trusted-git-root") -VERIFIED_SHA512_ROOT=$(cat "${DIR}/trusted-sha512-root-commit") -REVSIG_ALLOWED=$(cat "${DIR}/allow-revsig-commits") - -HAVE_GNU_SHA512=1 -[ ! -x "$(which sha512sum)" ] && HAVE_GNU_SHA512=0 - -if [ x"$1" = "x" ]; then - CURRENT_COMMIT="HEAD" -else - CURRENT_COMMIT="$1" -fi - -if [ "${CURRENT_COMMIT#* }" != "$CURRENT_COMMIT" ]; then - echo "Commit must not contain spaces?" > /dev/stderr - exit 1 -fi - -VERIFY_TREE=0 -if [ x"$2" = "x--tree-checks" ]; then - VERIFY_TREE=1 -fi - -NO_SHA1=1 -PREV_COMMIT="" -INITIAL_COMMIT="${CURRENT_COMMIT}" - -BRANCH="$(git rev-parse --abbrev-ref HEAD)" - -while true; do - if [ "$CURRENT_COMMIT" = $VERIFIED_ROOT ]; then - echo "There is a valid path from \"$INITIAL_COMMIT\" to $VERIFIED_ROOT where all commits are signed!" - exit 0 - fi - - if [ "$CURRENT_COMMIT" = $VERIFIED_SHA512_ROOT ]; then - if [ "$VERIFY_TREE" = "1" ]; then - echo "All Tree-SHA512s matched up to $VERIFIED_SHA512_ROOT" > /dev/stderr - fi - VERIFY_TREE=0 - NO_SHA1=0 - fi - - if [ "$NO_SHA1" = "1" ]; then - export BITCOIN_VERIFY_COMMITS_ALLOW_SHA1=0 - else - export BITCOIN_VERIFY_COMMITS_ALLOW_SHA1=1 - fi - - if [ "${REVSIG_ALLOWED#*$CURRENT_COMMIT}" != "$REVSIG_ALLOWED" ]; then - export BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG=1 - else - export BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG=0 - fi - - if ! git -c "gpg.program=${DIR}/gpg.sh" verify-commit "$CURRENT_COMMIT" > /dev/null; then - if [ "$PREV_COMMIT" != "" ]; then - echo "No parent of $PREV_COMMIT was signed with a trusted key!" > /dev/stderr - echo "Parents are:" > /dev/stderr - PARENTS=$(git show -s --format=format:%P $PREV_COMMIT) - for PARENT in $PARENTS; do - git show -s $PARENT > /dev/stderr - done - else - echo "$CURRENT_COMMIT was not signed with a trusted key!" > /dev/stderr - fi - exit 1 - fi - - # We always verify the top of the tree - if [ "$VERIFY_TREE" = 1 -o "$PREV_COMMIT" = "" ]; then - IFS_CACHE="$IFS" - IFS=' -' - for LINE in $(git ls-tree --full-tree -r "$CURRENT_COMMIT"); do - case "$LINE" in - "12"*) - echo "Repo contains symlinks" > /dev/stderr - IFS="$IFS_CACHE" - exit 1 - ;; - esac - done - IFS="$IFS_CACHE" - - FILE_HASHES="" - for FILE in $(git ls-tree --full-tree -r --name-only "$CURRENT_COMMIT" | LC_ALL=C sort); do - if [ "$HAVE_GNU_SHA512" = 1 ]; then - HASH=$(git cat-file blob "$CURRENT_COMMIT":"$FILE" | sha512sum | { read FIRST _; echo $FIRST; } ) - else - HASH=$(git cat-file blob "$CURRENT_COMMIT":"$FILE" | shasum -a 512 | { read FIRST _; echo $FIRST; } ) - fi - [ "$FILE_HASHES" != "" ] && FILE_HASHES="$FILE_HASHES"' -' - FILE_HASHES="$FILE_HASHES$HASH $FILE" - done - - if [ "$HAVE_GNU_SHA512" = 1 ]; then - TREE_HASH="$(echo "$FILE_HASHES" | sha512sum)" - else - TREE_HASH="$(echo "$FILE_HASHES" | shasum -a 512)" - fi - HASH_MATCHES=0 - MSG="$(git show -s --format=format:%B "$CURRENT_COMMIT" | tail -n1)" - - case "$MSG -" in - "Tree-SHA512: $TREE_HASH") - HASH_MATCHES=1;; - esac - - if [ "$HASH_MATCHES" = "0" ]; then - echo "Tree-SHA512 did not match for commit $CURRENT_COMMIT" > /dev/stderr - exit 1 - fi - fi - - PARENTS=$(git show -s --format=format:%P "$CURRENT_COMMIT") - PARENT1=${PARENTS%% *} - PARENT2="" - if [ "x$PARENT1" != "x$PARENTS" ]; then - PARENTX=${PARENTS#* } - PARENT2=${PARENTX%% *} - if [ "x$PARENT2" != "x$PARENTX" ]; then - echo "Commit $CURRENT_COMMIT is an octopus merge" > /dev/stderr - exit 1 - fi - fi - if [ "x$PARENT2" != "x" ]; then - CURRENT_TREE="$(git show --format="%T" "$CURRENT_COMMIT")" - git checkout --force --quiet "$PARENT1" - git merge --no-ff --quiet "$PARENT2" >/dev/null - RECREATED_TREE="$(git show --format="%T" HEAD)" - if [ "$CURRENT_TREE" != "$RECREATED_TREE" ]; then - echo "Merge commit $CURRENT_COMMIT is not clean" > /dev/stderr - git diff "$CURRENT_COMMIT" - git checkout --force --quiet "$BRANCH" - exit 1 - fi - git checkout --force --quiet "$BRANCH" - fi - PREV_COMMIT="$CURRENT_COMMIT" - CURRENT_COMMIT="$PARENT1" -done diff --git a/contrib/verifybinaries/verify.sh b/contrib/verifybinaries/verify.sh index e0266bf08a..fc7492ad3b 100755 --- a/contrib/verifybinaries/verify.sh +++ b/contrib/verifybinaries/verify.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright (c) 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. @@ -11,6 +11,7 @@ ### The script returns 0 if everything passes the checks. It returns 1 if either the ### signature check or the hash check doesn't pass. If an error occurs the return value is 2 +export LC_ALL=C function clean_up { for file in $* do diff --git a/contrib/windeploy/detached-sig-create.sh b/contrib/windeploy/detached-sig-create.sh index bf4978d143..15f8108cf0 100755 --- a/contrib/windeploy/detached-sig-create.sh +++ b/contrib/windeploy/detached-sig-create.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C if [ -z "$OSSLSIGNCODE" ]; then OSSLSIGNCODE=osslsigncode fi diff --git a/contrib/zmq/zmq_sub.py b/contrib/zmq/zmq_sub.py index 60768dc59a..fa9e669308 100644 --- a/contrib/zmq/zmq_sub.py +++ b/contrib/zmq/zmq_sub.py @@ -30,7 +30,7 @@ import signal import struct import sys -if not (sys.version_info.major >= 3 and sys.version_info.minor >= 5): +if (sys.version_info.major, sys.version_info.minor) < (3, 5): print("This example only works with Python 3.5 and greater") sys.exit(1) diff --git a/contrib/zmq/zmq_sub3.4.py b/contrib/zmq/zmq_sub3.4.py index 0df843c9a3..d05ecc2623 100644 --- a/contrib/zmq/zmq_sub3.4.py +++ b/contrib/zmq/zmq_sub3.4.py @@ -34,7 +34,7 @@ import signal import struct import sys -if not (sys.version_info.major >= 3 and sys.version_info.minor >= 4): +if (sys.version_info.major, sys.version_info.minor) < (3, 4): print("This example only works with Python 3.4 and greater") sys.exit(1) diff --git a/depends/.gitignore b/depends/.gitignore index 3cb4b9ac15..72734102c5 100644 --- a/depends/.gitignore +++ b/depends/.gitignore @@ -8,3 +8,5 @@ i686* mips* arm* aarch64* +riscv32* +riscv64* diff --git a/depends/Makefile b/depends/Makefile index 8b67bce9d8..c73ea0f730 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -167,7 +167,7 @@ $(host_prefix)/share/config.site: check-packages check-packages: check-sources clean-all: clean - @rm -rf $(SOURCES_PATH) x86_64* i686* mips* arm* aarch64* + @rm -rf $(SOURCES_PATH) x86_64* i686* mips* arm* aarch64* riscv32* riscv64* clean: @rm -rf $(WORK_PATH) $(BASE_CACHE) $(BUILD) diff --git a/depends/README.md b/depends/README.md index 99eef1952c..226a1cc935 100644 --- a/depends/README.md +++ b/depends/README.md @@ -22,9 +22,11 @@ Common `host-platform-triplets` for cross compilation are: - `i686-w64-mingw32` for Win32 - `x86_64-w64-mingw32` for Win64 -- `x86_64-apple-darwin11` for MacOSX +- `x86_64-apple-darwin11` for macOS - `arm-linux-gnueabihf` for Linux ARM 32 bit - `aarch64-linux-gnu` for Linux ARM 64 bit +- `riscv32-linux-gnu` for Linux RISC-V 32 bit +- `riscv64-linux-gnu` for Linux RISC-V 64 bit No other options are needed, the paths are automatically configured. @@ -43,13 +45,19 @@ 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 +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 + +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: The following can be set when running make: make FOO=bar SOURCES_PATH: downloaded sources will be placed here BASE_CACHE: built packages will be placed here - SDK_PATH: Path where sdk's can be found (used by OSX) + SDK_PATH: Path where sdk's can be found (used by macOS) FALLBACK_DOWNLOAD_PATH: If a source file can't be fetched, try here before giving up NO_QT: Don't download/build/cache qt and its dependencies NO_WALLET: Don't download/build/cache libs needed to enable the wallet @@ -64,7 +72,7 @@ options will be passed to bitcoin's configure. In this case, `--disable-wallet`. 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 osx builds + download-osx: run 'make download-osx' to fetch all sources needed for macOS builds download-win: run 'make download-win' to fetch all sources needed for win builds download-linux: run 'make download-linux' to fetch all sources needed for linux builds diff --git a/depends/config.guess b/depends/config.guess index 9baaa270bf..2b79f6d837 100755 --- a/depends/config.guess +++ b/depends/config.guess @@ -2,7 +2,7 @@ # Attempt to guess a canonical system name. # Copyright 1992-2018 Free Software Foundation, Inc. -timestamp='2018-01-26' +timestamp='2018-07-06' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -101,12 +101,12 @@ trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && e trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || - { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || - { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp 2>/dev/null) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; -case $CC_FOR_BUILD,$HOST_CC,$CC in +case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" ; for c in cc gcc c89 c99 ; do if ($c -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then @@ -237,7 +237,7 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. - echo "$machine-${os}${release}${abi}" + echo "$machine-${os}${release}${abi-}" exit ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` @@ -894,8 +894,8 @@ EOF # other systems with GNU libc and userland echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" exit ;; - i*86:Minix:*:*) - echo "$UNAME_MACHINE"-pc-minix + *:Minix:*:*) + echo "$UNAME_MACHINE"-unknown-minix exit ;; aarch64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" @@ -1469,7 +1469,7 @@ EOF exit 1 # Local variables: -# eval: (add-hook 'write-file-functions 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" diff --git a/depends/config.sub b/depends/config.sub index 818892c1c3..c95acc681d 100755 --- a/depends/config.sub +++ b/depends/config.sub @@ -2,7 +2,7 @@ # Configuration validation subroutine script. # Copyright 1992-2018 Free Software Foundation, Inc. -timestamp='2018-01-15' +timestamp='2018-07-03' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -110,131 +110,455 @@ case $# in exit 1;; esac -# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). -# Here we must recognize all the valid KERNEL-OS combinations. -maybe_os=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` -case $maybe_os in - nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ - linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ - knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ - kopensolaris*-gnu* | cloudabi*-eabi* | \ - storm-chaos* | os2-emx* | rtmk-nova*) - os=-$maybe_os - basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` - ;; - android-linux) - os=-linux-android - basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown - ;; - *) - basic_machine=`echo "$1" | sed 's/-[^-]*$//'` - if [ "$basic_machine" != "$1" ] - then os=`echo "$1" | sed 's/.*-/-/'` - else os=; fi - ;; -esac +# Split fields of configuration type +IFS="-" read -r field1 field2 field3 field4 <<EOF +$1 +EOF -### Let's recognize common machines as not being operating systems so -### that things like config.sub decstation-3100 work. We also -### recognize some manufacturers as not being operating systems, so we -### can provide default operating systems below. -case $os in - -sun*os*) - # Prevent following clause from handling this invalid input. - ;; - -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ - -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ - -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ - -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ - -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ - -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ - -apple | -axis | -knuth | -cray | -microblaze*) - os= - basic_machine=$1 - ;; - -bluegene*) - os=-cnk - ;; - -sim | -cisco | -oki | -wec | -winbond) - os= - basic_machine=$1 - ;; - -scout) - ;; - -wrs) - os=-vxworks - basic_machine=$1 - ;; - -chorusos*) - os=-chorusos - basic_machine=$1 - ;; - -chorusrdb) - os=-chorusrdb - basic_machine=$1 - ;; - -hiux*) - os=-hiuxwe2 - ;; - -sco6) - os=-sco5v6 - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco5) - os=-sco3.2v5 - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco4) - os=-sco3.2v4 - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco3.2.[4-9]*) - os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco3.2v[4-9]*) - # Don't forget version if it is 3.2v4 or newer. - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco5v6*) - # Don't forget version if it is 3.2v4 or newer. - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -sco*) - os=-sco3.2v2 - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -udk*) - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -isc) - os=-isc2.2 - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -clix*) - basic_machine=clipper-intergraph - ;; - -isc*) - basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` - ;; - -lynx*178) - os=-lynxos178 - ;; - -lynx*5) - os=-lynxos5 +# Separate into logical components for further validation +case $1 in + *-*-*-*-*) + echo Invalid configuration \`"$1"\': more than four components >&2 + exit 1 ;; - -lynx*) - os=-lynxos + *-*-*-*) + basic_machine=$field1-$field2 + os=$field3-$field4 ;; - -ptx*) - basic_machine=`echo "$1" | sed -e 's/86-.*/86-sequent/'` + *-*-*) + # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two + # parts + maybe_os=$field2-$field3 + case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc \ + | linux-newlib* | linux-musl* | linux-uclibc* | uclinux-uclibc* \ + | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ + | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ + | storm-chaos* | os2-emx* | rtmk-nova*) + basic_machine=$field1 + os=$maybe_os + ;; + android-linux) + basic_machine=$field1-unknown + os=linux-android + ;; + *) + basic_machine=$field1-$field2 + os=$field3 + ;; + esac ;; - -psos*) - os=-psos + *-*) + # Second component is usually, but not always the OS + case $field2 in + # Prevent following clause from handling this valid os + sun*os*) + basic_machine=$field1 + os=$field2 + ;; + # Manufacturers + dec* | mips* | sequent* | encore* | pc532* | sgi* | sony* \ + | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ + | unicom* | ibm* | next | hp | isi* | apollo | altos* \ + | convergent* | ncr* | news | 32* | 3600* | 3100* | hitachi* \ + | c[123]* | convex* | sun | crds | omron* | dg | ultra | tti* \ + | harris | dolphin | highlevel | gould | cbm | ns | masscomp \ + | apple | axis | knuth | cray | microblaze* \ + | sim | cisco | oki | wec | wrs | winbond) + basic_machine=$field1-$field2 + os= + ;; + *) + basic_machine=$field1 + os=$field2 + ;; + esac ;; - -mint | -mint[0-9]*) - basic_machine=m68k-atari - os=-mint + *) + # Convert single-component short-hands not valid as part of + # multi-component configurations. + case $field1 in + 386bsd) + basic_machine=i386-pc + os=bsd + ;; + a29khif) + basic_machine=a29k-amd + os=udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=scout + ;; + am29k) + basic_machine=a29k-none + os=bsd + ;; + amdahl) + basic_machine=580-amdahl + os=sysv + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=bsd + ;; + aros) + basic_machine=i386-pc + os=aros + ;; + aux) + basic_machine=m68k-apple + os=aux + ;; + balance) + basic_machine=ns32k-sequent + os=dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=linux + ;; + cegcc) + basic_machine=arm-unknown + os=cegcc + ;; + cray) + basic_machine=j90-cray + os=unicos + ;; + craynv) + basic_machine=craynv-cray + os=unicosmp + ;; + delta88) + basic_machine=m88k-motorola + os=sysv3 + ;; + dicos) + basic_machine=i686-pc + os=dicos + ;; + djgpp) + basic_machine=i586-pc + os=msdosdjgpp + ;; + ebmon29k) + basic_machine=a29k-amd + os=ebmon + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=ose + ;; + gmicro) + basic_machine=tron-gmicro + os=sysv + ;; + go32) + basic_machine=i386-pc + os=go32 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=hms + ;; + harris) + basic_machine=m88k-harris + os=sysv3 + ;; + hp300bsd) + basic_machine=m68k-hp + os=bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=hpux + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=proelf + ;; + i386mach) + basic_machine=i386-mach + os=mach + ;; + vsta) + basic_machine=i386-unknown + os=vsta + ;; + isi68 | isi) + basic_machine=m68k-isi + os=sysv + ;; + m68knommu) + basic_machine=m68k-unknown + os=linux + ;; + magnum | m3230) + basic_machine=mips-mips + os=sysv + ;; + merlin) + basic_machine=ns32k-utek + os=sysv + ;; + mingw64) + basic_machine=x86_64-pc + os=mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=mingw32ce + ;; + monitor) + basic_machine=m68k-rom68k + os=coff + ;; + morphos) + basic_machine=powerpc-unknown + os=morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=moxiebox + ;; + msdos) + basic_machine=i386-pc + os=msdos + ;; + msys) + basic_machine=i686-pc + os=msys + ;; + mvs) + basic_machine=i370-ibm + os=mvs + ;; + nacl) + basic_machine=le32-unknown + os=nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=newsos + ;; + news1000) + basic_machine=m68030-sony + os=newsos + ;; + necv70) + basic_machine=v70-nec + os=sysv + ;; + nh3000) + basic_machine=m68k-harris + os=cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=cxux + ;; + nindy960) + basic_machine=i960-intel + os=nindy + ;; + mon960) + basic_machine=i960-intel + os=mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=nonstopux + ;; + os400) + basic_machine=powerpc-ibm + os=os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=ose + ;; + os68k) + basic_machine=m68k-none + os=os68k + ;; + paragon) + basic_machine=i860-intel + os=osf + ;; + parisc) + basic_machine=hppa-unknown + os=linux + ;; + pw32) + basic_machine=i586-unknown + os=pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=rdos + ;; + rdos32) + basic_machine=i386-pc + os=rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=coff + ;; + sa29200) + basic_machine=a29k-amd + os=udi + ;; + sei) + basic_machine=mips-sei + os=seiux + ;; + sps7) + basic_machine=m68k-bull + os=sysv2 + ;; + stratus) + basic_machine=i860-stratus + os=sysv4 + ;; + sun2os3) + basic_machine=m68000-sun + os=sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=solaris2 + ;; + sv1) + basic_machine=sv1-cray + os=unicos + ;; + symmetry) + basic_machine=i386-sequent + os=dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=unicos + ;; + t90) + basic_machine=t90-cray + os=unicos + ;; + toad1) + basic_machine=pdp10-xkl + os=tops20 + ;; + tpf) + basic_machine=s390x-ibm + os=tpf + ;; + udi29k) + basic_machine=a29k-amd + os=udi + ;; + ultra3) + basic_machine=a29k-nyu + os=sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=none + ;; + vaxv) + basic_machine=vax-dec + os=sysv + ;; + vms) + basic_machine=vax-dec + os=vms + ;; + vxworks960) + basic_machine=i960-wrs + os=vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=vxworks + ;; + xbox) + basic_machine=i686-pc + os=mingw32 + ;; + ymp) + basic_machine=ymp-cray + os=unicos + ;; + *) + basic_machine=$1 + os= + ;; + esac ;; esac @@ -249,12 +573,12 @@ case $basic_machine in | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arceb \ - | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv6m | armv[78][arm] \ | avr | avr32 \ | ba \ | be32 | be64 \ | bfin \ - | c4x | c8051 | clipper \ + | c4x | c8051 | clipper | csky \ | d10v | d30v | dlx | dsp16xx \ | e2k | epiphany \ | fido | fr30 | frv | ft32 \ @@ -293,6 +617,7 @@ case $basic_machine in | mt \ | msp430 \ | nds32 | nds32le | nds32be \ + | nfp \ | nios | nios2 | nios2eb | nios2el \ | ns16k | ns32k \ | open8 | or1k | or1knd | or32 \ @@ -300,7 +625,7 @@ case $basic_machine in | powerpc | powerpc64 | powerpc64le | powerpcle \ | pru \ | pyramid \ - | riscv32 | riscv64 \ + | riscv | riscv32 | riscv64 \ | rl78 | rx \ | score \ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ @@ -331,20 +656,23 @@ case $basic_machine in ;; m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) basic_machine=$basic_machine-unknown - os=-none + os=${os:-none} ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65) ;; + m9s12z | m68hcs12z | hcs12z | s12z) + basic_machine=s12z-unknown + os=${os:-none} + ;; ms1) basic_machine=mt-unknown ;; - strongarm | thumb | xscale) basic_machine=arm-unknown ;; xgate) basic_machine=$basic_machine-unknown - os=-none + os=${os:-none} ;; xscaleeb) basic_machine=armeb-unknown @@ -360,11 +688,6 @@ case $basic_machine in i*86 | x86_64) basic_machine=$basic_machine-pc ;; - # Object if more than one company name word. - *-*-*) - echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 - exit 1 - ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ @@ -378,7 +701,7 @@ case $basic_machine in | be32-* | be64-* \ | bfin-* | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* \ - | c8051-* | clipper-* | craynv-* | cydra-* \ + | c8051-* | clipper-* | craynv-* | csky-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | e2k-* | elxsi-* \ | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ @@ -419,6 +742,7 @@ case $basic_machine in | mt-* \ | msp430-* \ | nds32-* | nds32le-* | nds32be-* \ + | nfp-* \ | nios-* | nios2-* | nios2eb-* | nios2el-* \ | none-* | np1-* | ns16k-* | ns32k-* \ | open8-* \ @@ -428,7 +752,7 @@ case $basic_machine in | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ | pru-* \ | pyramid-* \ - | riscv32-* | riscv64-* \ + | riscv-* | riscv32-* | riscv64-* \ | rl78-* | romp-* | rs6000-* | rx-* \ | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ @@ -456,92 +780,40 @@ case $basic_machine in ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. - 386bsd) - basic_machine=i386-pc - os=-bsd - ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; - a29khif) - basic_machine=a29k-amd - os=-udi - ;; abacus) basic_machine=abacus-unknown ;; - adobe68k) - basic_machine=m68010-adobe - os=-scout - ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; - am29k) - basic_machine=a29k-none - os=-bsd - ;; amd64) basic_machine=x86_64-pc ;; amd64-*) basic_machine=x86_64-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; - amdahl) - basic_machine=580-amdahl - os=-sysv - ;; amiga | amiga-*) basic_machine=m68k-unknown ;; - amigaos | amigados) - basic_machine=m68k-unknown - os=-amigaos - ;; - amigaunix | amix) - basic_machine=m68k-unknown - os=-sysv4 - ;; - apollo68) - basic_machine=m68k-apollo - os=-sysv - ;; - apollo68bsd) - basic_machine=m68k-apollo - os=-bsd - ;; - aros) - basic_machine=i386-pc - os=-aros - ;; asmjs) basic_machine=asmjs-unknown ;; - aux) - basic_machine=m68k-apple - os=-aux - ;; - balance) - basic_machine=ns32k-sequent - os=-dynix - ;; - blackfin) - basic_machine=bfin-unknown - os=-linux - ;; blackfin-*) basic_machine=bfin-`echo "$basic_machine" | sed 's/^[^-]*-//'` - os=-linux + os=linux ;; bluegene*) basic_machine=powerpc-ibm - os=-cnk + os=cnk ;; c54x-*) basic_machine=tic54x-`echo "$basic_machine" | sed 's/^[^-]*-//'` @@ -554,43 +826,31 @@ case $basic_machine in ;; c90) basic_machine=c90-cray - os=-unicos - ;; - cegcc) - basic_machine=arm-unknown - os=-cegcc + os=${os:-unicos} ;; convex-c1) basic_machine=c1-convex - os=-bsd + os=bsd ;; convex-c2) basic_machine=c2-convex - os=-bsd + os=bsd ;; convex-c32) basic_machine=c32-convex - os=-bsd + os=bsd ;; convex-c34) basic_machine=c34-convex - os=-bsd + os=bsd ;; convex-c38) basic_machine=c38-convex - os=-bsd - ;; - cray | j90) - basic_machine=j90-cray - os=-unicos - ;; - craynv) - basic_machine=craynv-cray - os=-unicosmp + os=bsd ;; cr16 | cr16-*) basic_machine=cr16-unknown - os=-elf + os=${os:-elf} ;; crds | unos) basic_machine=m68k-crds @@ -603,7 +863,7 @@ case $basic_machine in ;; crx) basic_machine=crx-unknown - os=-elf + os=${os:-elf} ;; da30 | da30-*) basic_machine=m68k-da30 @@ -613,35 +873,23 @@ case $basic_machine in ;; decsystem10* | dec10*) basic_machine=pdp10-dec - os=-tops10 + os=tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec - os=-tops20 + os=tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; - delta88) - basic_machine=m88k-motorola - os=-sysv3 - ;; - dicos) - basic_machine=i686-pc - os=-dicos - ;; - djgpp) - basic_machine=i586-pc - os=-msdosdjgpp - ;; dpx20 | dpx20-*) basic_machine=rs6000-bull - os=-bosx + os=${os:-bosx} ;; dpx2*) basic_machine=m68k-bull - os=-sysv3 + os=sysv3 ;; e500v[12]) basic_machine=powerpc-unknown @@ -651,20 +899,12 @@ case $basic_machine in basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` os=$os"spe" ;; - ebmon29k) - basic_machine=a29k-amd - os=-ebmon - ;; - elxsi) - basic_machine=elxsi-elxsi - os=-bsd - ;; encore | umax | mmax) basic_machine=ns32k-encore ;; - es1800 | OSE68k | ose68k | ose | OSE) - basic_machine=m68k-ericsson - os=-ose + elxsi) + basic_machine=elxsi-elxsi + os=${os:-bsd} ;; fx2800) basic_machine=i860-alliant @@ -672,45 +912,13 @@ case $basic_machine in genix) basic_machine=ns32k-ns ;; - gmicro) - basic_machine=tron-gmicro - os=-sysv - ;; - go32) - basic_machine=i386-pc - os=-go32 - ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi - os=-hiuxwe2 - ;; - h8300hms) - basic_machine=h8300-hitachi - os=-hms - ;; - h8300xray) - basic_machine=h8300-hitachi - os=-xray - ;; - h8500hms) - basic_machine=h8500-hitachi - os=-hms - ;; - harris) - basic_machine=m88k-harris - os=-sysv3 + os=hiuxwe2 ;; hp300-*) basic_machine=m68k-hp ;; - hp300bsd) - basic_machine=m68k-hp - os=-bsd - ;; - hp300hpux) - basic_machine=m68k-hp - os=-hpux - ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; @@ -740,95 +948,55 @@ case $basic_machine in hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; - hppaosf) - basic_machine=hppa1.1-hp - os=-osf - ;; - hppro) - basic_machine=hppa1.1-hp - os=-proelf - ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; i*86v32) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` - os=-sysv32 + os=sysv32 ;; i*86v4*) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` - os=-sysv4 + os=sysv4 ;; i*86v) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` - os=-sysv + os=sysv ;; i*86sol2) basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` - os=-solaris2 - ;; - i386mach) - basic_machine=i386-mach - os=-mach + os=solaris2 ;; - vsta) - basic_machine=i386-unknown - os=-vsta + j90 | j90-cray) + basic_machine=j90-cray + os=${os:-unicos} ;; iris | iris4d) basic_machine=mips-sgi case $os in - -irix*) + irix*) ;; *) - os=-irix4 + os=irix4 ;; esac ;; - isi68 | isi) - basic_machine=m68k-isi - os=-sysv - ;; leon-*|leon[3-9]-*) basic_machine=sparc-`echo "$basic_machine" | sed 's/-.*//'` ;; - m68knommu) - basic_machine=m68k-unknown - os=-linux - ;; m68knommu-*) basic_machine=m68k-`echo "$basic_machine" | sed 's/^[^-]*-//'` - os=-linux - ;; - magnum | m3230) - basic_machine=mips-mips - os=-sysv - ;; - merlin) - basic_machine=ns32k-utek - os=-sysv + os=linux ;; microblaze*) basic_machine=microblaze-xilinx ;; - mingw64) - basic_machine=x86_64-pc - os=-mingw64 - ;; - mingw32) - basic_machine=i686-pc - os=-mingw32 - ;; - mingw32ce) - basic_machine=arm-unknown - os=-mingw32ce - ;; miniframe) basic_machine=m68000-convergent ;; - *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari - os=-mint + os=mint ;; mips3*-*) basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'` @@ -836,98 +1004,26 @@ case $basic_machine in mips3*) basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'`-unknown ;; - monitor) - basic_machine=m68k-rom68k - os=-coff - ;; - morphos) - basic_machine=powerpc-unknown - os=-morphos - ;; - moxiebox) - basic_machine=moxie-unknown - os=-moxiebox - ;; - msdos) - basic_machine=i386-pc - os=-msdos - ;; ms1-*) basic_machine=`echo "$basic_machine" | sed -e 's/ms1-/mt-/'` ;; - msys) - basic_machine=i686-pc - os=-msys - ;; - mvs) - basic_machine=i370-ibm - os=-mvs - ;; - nacl) - basic_machine=le32-unknown - os=-nacl - ;; - ncr3000) - basic_machine=i486-ncr - os=-sysv4 - ;; - netbsd386) - basic_machine=i386-unknown - os=-netbsd - ;; - netwinder) - basic_machine=armv4l-rebel - os=-linux - ;; - news | news700 | news800 | news900) - basic_machine=m68k-sony - os=-newsos - ;; - news1000) - basic_machine=m68030-sony - os=-newsos - ;; news-3600 | risc-news) basic_machine=mips-sony - os=-newsos - ;; - necv70) - basic_machine=v70-nec - os=-sysv + os=newsos ;; next | m*-next) basic_machine=m68k-next case $os in - -nextstep* ) + nextstep* ) ;; - -ns2*) - os=-nextstep2 + ns2*) + os=nextstep2 ;; *) - os=-nextstep3 + os=nextstep3 ;; esac ;; - nh3000) - basic_machine=m68k-harris - os=-cxux - ;; - nh[45]000) - basic_machine=m88k-harris - os=-cxux - ;; - nindy960) - basic_machine=i960-intel - os=-nindy - ;; - mon960) - basic_machine=i960-intel - os=-mon960 - ;; - nonstopux) - basic_machine=mips-compaq - os=-nonstopux - ;; np1) basic_machine=np1-gould ;; @@ -948,38 +1044,18 @@ case $basic_machine in ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki - os=-proelf + os=proelf ;; openrisc | openrisc-*) basic_machine=or32-unknown ;; - os400) - basic_machine=powerpc-ibm - os=-os400 - ;; - OSE68000 | ose68000) - basic_machine=m68000-ericsson - os=-ose - ;; - os68k) - basic_machine=m68k-none - os=-os68k - ;; pa-hitachi) basic_machine=hppa1.1-hitachi - os=-hiuxwe2 - ;; - paragon) - basic_machine=i860-intel - os=-osf - ;; - parisc) - basic_machine=hppa-unknown - os=-linux + os=hiuxwe2 ;; parisc-*) basic_machine=hppa-`echo "$basic_machine" | sed 's/^[^-]*-//'` - os=-linux + os=linux ;; pbd) basic_machine=sparc-tti @@ -1049,22 +1125,6 @@ case $basic_machine in ps2) basic_machine=i386-ibm ;; - pw32) - basic_machine=i586-unknown - os=-pw32 - ;; - rdos | rdos64) - basic_machine=x86_64-pc - os=-rdos - ;; - rdos32) - basic_machine=i386-pc - os=-rdos - ;; - rom68k) - basic_machine=m68k-rom68k - os=-coff - ;; rm[46]00) basic_machine=mips-siemens ;; @@ -1077,10 +1137,6 @@ case $basic_machine in s390x | s390x-*) basic_machine=s390x-ibm ;; - sa29200) - basic_machine=a29k-amd - os=-udi - ;; sb1) basic_machine=mipsisa64sb1-unknown ;; @@ -1089,11 +1145,7 @@ case $basic_machine in ;; sde) basic_machine=mipsisa32-sde - os=-elf - ;; - sei) - basic_machine=mips-sei - os=-seiux + os=${os:-elf} ;; sequent) basic_machine=i386-sequent @@ -1103,11 +1155,7 @@ case $basic_machine in ;; simso-wrs) basic_machine=sparclite-wrs - os=-vxworks - ;; - sps7) - basic_machine=m68k-bull - os=-sysv2 + os=vxworks ;; spur) basic_machine=spur-unknown @@ -1115,44 +1163,12 @@ case $basic_machine in st2000) basic_machine=m68k-tandem ;; - stratus) - basic_machine=i860-stratus - os=-sysv4 - ;; strongarm-* | thumb-*) basic_machine=arm-`echo "$basic_machine" | sed 's/^[^-]*-//'` ;; sun2) basic_machine=m68000-sun ;; - sun2os3) - basic_machine=m68000-sun - os=-sunos3 - ;; - sun2os4) - basic_machine=m68000-sun - os=-sunos4 - ;; - sun3os3) - basic_machine=m68k-sun - os=-sunos3 - ;; - sun3os4) - basic_machine=m68k-sun - os=-sunos4 - ;; - sun4os3) - basic_machine=sparc-sun - os=-sunos3 - ;; - sun4os4) - basic_machine=sparc-sun - os=-sunos4 - ;; - sun4sol2) - basic_machine=sparc-sun - os=-solaris2 - ;; sun3 | sun3-*) basic_machine=m68k-sun ;; @@ -1162,25 +1178,9 @@ case $basic_machine in sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; - sv1) - basic_machine=sv1-cray - os=-unicos - ;; - symmetry) - basic_machine=i386-sequent - os=-dynix - ;; - t3e) - basic_machine=alphaev5-cray - os=-unicos - ;; - t90) - basic_machine=t90-cray - os=-unicos - ;; tile*) basic_machine=$basic_machine-unknown - os=-linux-gnu + os=linux-gnu ;; tx39) basic_machine=mipstx39-unknown @@ -1188,80 +1188,32 @@ case $basic_machine in tx39el) basic_machine=mipstx39el-unknown ;; - toad1) - basic_machine=pdp10-xkl - os=-tops20 - ;; tower | tower-32) basic_machine=m68k-ncr ;; - tpf) - basic_machine=s390x-ibm - os=-tpf - ;; - udi29k) - basic_machine=a29k-amd - os=-udi - ;; - ultra3) - basic_machine=a29k-nyu - os=-sym1 - ;; - v810 | necv810) - basic_machine=v810-nec - os=-none - ;; - vaxv) - basic_machine=vax-dec - os=-sysv - ;; - vms) - basic_machine=vax-dec - os=-vms - ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; - vxworks960) - basic_machine=i960-wrs - os=-vxworks - ;; - vxworks68) - basic_machine=m68k-wrs - os=-vxworks - ;; - vxworks29k) - basic_machine=a29k-wrs - os=-vxworks - ;; w65*) basic_machine=w65-wdc - os=-none + os=none ;; w89k-*) basic_machine=hppa1.1-winbond - os=-proelf + os=proelf ;; x64) basic_machine=x86_64-pc ;; - xbox) - basic_machine=i686-pc - os=-mingw32 - ;; xps | xps100) basic_machine=xps100-honeywell ;; xscale-* | xscalee[bl]-*) basic_machine=`echo "$basic_machine" | sed 's/^xscale/arm/'` ;; - ymp) - basic_machine=ymp-cray - os=-unicos - ;; none) basic_machine=none-none - os=-none + os=${os:-none} ;; # Here we handle the default manufacturer of certain CPU types. It is in @@ -1334,198 +1286,245 @@ esac # Decode manufacturer-specific aliases for certain operating systems. -if [ x"$os" != x"" ] +if [ x$os != x ] then case $os in # First match some system type aliases that might get confused # with valid system types. - # -solaris* is a basic system type, with this one exception. - -auroraux) - os=-auroraux + # solaris* is a basic system type, with this one exception. + auroraux) + os=auroraux + ;; + bluegene*) + os=cnk ;; - -solaris1 | -solaris1.*) + solaris1 | solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; - -solaris) - os=-solaris2 + solaris) + os=solaris2 ;; - -unixware*) - os=-sysv4.2uw + unixware*) + os=sysv4.2uw ;; - -gnu/linux*) + gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # es1800 is here to avoid being matched by es* (a different OS) - -es1800*) - os=-ose + es1800*) + os=ose + ;; + # Some version numbers need modification + chorusos*) + os=chorusos + ;; + isc) + os=isc2.2 + ;; + sco6) + os=sco5v6 + ;; + sco5) + os=sco3.2v5 + ;; + sco4) + os=sco3.2v4 + ;; + sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + ;; + sco3.2v[4-9]* | sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + ;; + scout) + # Don't match below + ;; + sco*) + os=sco3.2v2 + ;; + psos*) + os=psos ;; # Now accept the basic system types. # The portable systems comes first. # Each alternative MUST end in a * to match a version number. - # -sysv* is not here because it comes later, after sysvr4. - -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ - | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ - | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ - | -sym* | -kopensolaris* | -plan9* \ - | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ - | -aos* | -aros* | -cloudabi* | -sortix* \ - | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ - | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ - | -hiux* | -knetbsd* | -mirbsd* | -netbsd* \ - | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \ - | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ - | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ - | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ - | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ - | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \ - | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ - | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ - | -linux-newlib* | -linux-musl* | -linux-uclibc* \ - | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ - | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* \ - | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ - | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ - | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ - | -morphos* | -superux* | -rtmk* | -windiss* \ - | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ - | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ - | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme*) + # sysv* is not here because it comes later, after sysvr4. + gnu* | bsd* | mach* | minix* | genix* | ultrix* | irix* \ + | *vms* | esix* | aix* | cnk* | sunos | sunos[34]*\ + | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ + | sym* | kopensolaris* | plan9* \ + | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ + | aos* | aros* | cloudabi* | sortix* \ + | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \ + | clix* | riscos* | uniplus* | iris* | rtu* | xenix* \ + | knetbsd* | mirbsd* | netbsd* \ + | bitrig* | openbsd* | solidbsd* | libertybsd* \ + | ekkobsd* | kfreebsd* | freebsd* | riscix* | lynxos* \ + | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ + | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ + | udi* | eabi* | lites* | ieee* | go32* | aux* | hcos* \ + | chorusrdb* | cegcc* | glidix* \ + | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ + | midipix* | mingw32* | mingw64* | linux-gnu* | linux-android* \ + | linux-newlib* | linux-musl* | linux-uclibc* \ + | uxpv* | beos* | mpeix* | udk* | moxiebox* \ + | interix* | uwin* | mks* | rhapsody* | darwin* \ + | openstep* | oskit* | conix* | pw32* | nonstopux* \ + | storm-chaos* | tops10* | tenex* | tops20* | its* \ + | os2* | vos* | palmos* | uclinux* | nucleus* \ + | morphos* | superux* | rtmk* | windiss* \ + | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \ + | skyos* | haiku* | rdos* | toppers* | drops* | es* \ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd*) # Remember, each alternative MUST END IN *, to match a version number. ;; - -qnx*) + qnx*) case $basic_machine in x86-* | i*86-*) ;; *) - os=-nto$os + os=nto-$os ;; esac ;; - -nto-qnx*) + hiux*) + os=hiuxwe2 ;; - -nto*) + nto-qnx*) + ;; + nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; - -sim | -xray | -os68k* | -v88r* \ - | -windows* | -osx | -abug | -netware* | -os9* \ - | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + sim | xray | os68k* | v88r* \ + | windows* | osx | abug | netware* | os9* \ + | macos* | mpw* | magic* | mmixware* | mon960* | lnews*) + ;; + linux-dietlibc) + os=linux-dietlibc + ;; + linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + lynx*178) + os=lynxos178 + ;; + lynx*5) + os=lynxos5 + ;; + lynx*) + os=lynxos ;; - -mac*) + mac*) os=`echo "$os" | sed -e 's|mac|macos|'` ;; - -linux-dietlibc) - os=-linux-dietlibc + opened*) + os=openedition ;; - -linux*) - os=`echo $os | sed -e 's|linux|linux-gnu|'` + os400*) + os=os400 ;; - -sunos5*) + sunos5*) os=`echo "$os" | sed -e 's|sunos5|solaris2|'` ;; - -sunos6*) + sunos6*) os=`echo "$os" | sed -e 's|sunos6|solaris3|'` ;; - -opened*) - os=-openedition - ;; - -os400*) - os=-os400 - ;; - -wince*) - os=-wince + wince*) + os=wince ;; - -utek*) - os=-bsd + utek*) + os=bsd ;; - -dynix*) - os=-bsd + dynix*) + os=bsd ;; - -acis*) - os=-aos + acis*) + os=aos ;; - -atheos*) - os=-atheos + atheos*) + os=atheos ;; - -syllable*) - os=-syllable + syllable*) + os=syllable ;; - -386bsd) - os=-bsd + 386bsd) + os=bsd ;; - -ctix* | -uts*) - os=-sysv + ctix* | uts*) + os=sysv ;; - -nova*) - os=-rtmk-nova + nova*) + os=rtmk-nova ;; - -ns2) - os=-nextstep2 + ns2) + os=nextstep2 ;; - -nsk*) - os=-nsk + nsk*) + os=nsk ;; # Preserve the version number of sinix5. - -sinix5.*) + sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; - -sinix*) - os=-sysv4 + sinix*) + os=sysv4 ;; - -tpf*) - os=-tpf + tpf*) + os=tpf ;; - -triton*) - os=-sysv3 + triton*) + os=sysv3 ;; - -oss*) - os=-sysv3 + oss*) + os=sysv3 ;; - -svr4*) - os=-sysv4 + svr4*) + os=sysv4 ;; - -svr3) - os=-sysv3 + svr3) + os=sysv3 ;; - -sysvr4) - os=-sysv4 + sysvr4) + os=sysv4 ;; - # This must come after -sysvr4. - -sysv*) + # This must come after sysvr4. + sysv*) ;; - -ose*) - os=-ose + ose*) + os=ose ;; - -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) - os=-mint + *mint | mint[0-9]* | *MiNT | MiNT[0-9]*) + os=mint ;; - -zvmoe) - os=-zvmoe + zvmoe) + os=zvmoe ;; - -dicos*) - os=-dicos + dicos*) + os=dicos ;; - -pikeos*) + pikeos*) # Until real need of OS specific support for # particular features comes up, bare metal # configurations are quite functional. case $basic_machine in arm*) - os=-eabi + os=eabi ;; *) - os=-elf + os=elf ;; esac ;; - -nacl*) + nacl*) ;; - -ios) + ios) ;; - -none) + none) + ;; + *-eabi) ;; *) - # Get rid of the `-' at the beginning of $os. - os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 exit 1 ;; @@ -1544,173 +1543,179 @@ else case $basic_machine in score-*) - os=-elf + os=elf ;; spu-*) - os=-elf + os=elf ;; *-acorn) - os=-riscix1.2 + os=riscix1.2 ;; arm*-rebel) - os=-linux + os=linux ;; arm*-semi) - os=-aout + os=aout ;; c4x-* | tic4x-*) - os=-coff + os=coff ;; c8051-*) - os=-elf + os=elf + ;; + clipper-intergraph) + os=clix ;; hexagon-*) - os=-elf + os=elf ;; tic54x-*) - os=-coff + os=coff ;; tic55x-*) - os=-coff + os=coff ;; tic6x-*) - os=-coff + os=coff ;; # This must come before the *-dec entry. pdp10-*) - os=-tops20 + os=tops20 ;; pdp11-*) - os=-none + os=none ;; *-dec | vax-*) - os=-ultrix4.2 + os=ultrix4.2 ;; m68*-apollo) - os=-domain + os=domain ;; i386-sun) - os=-sunos4.0.2 + os=sunos4.0.2 ;; m68000-sun) - os=-sunos3 + os=sunos3 ;; m68*-cisco) - os=-aout + os=aout ;; mep-*) - os=-elf + os=elf ;; mips*-cisco) - os=-elf + os=elf ;; mips*-*) - os=-elf + os=elf ;; or32-*) - os=-coff + os=coff ;; *-tti) # must be before sparc entry or we get the wrong os. - os=-sysv3 + os=sysv3 ;; sparc-* | *-sun) - os=-sunos4.1.1 + os=sunos4.1.1 ;; pru-*) - os=-elf + os=elf ;; *-be) - os=-beos + os=beos ;; *-ibm) - os=-aix + os=aix ;; *-knuth) - os=-mmixware + os=mmixware ;; *-wec) - os=-proelf + os=proelf ;; *-winbond) - os=-proelf + os=proelf ;; *-oki) - os=-proelf + os=proelf ;; *-hp) - os=-hpux + os=hpux ;; *-hitachi) - os=-hiux + os=hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) - os=-sysv + os=sysv ;; *-cbm) - os=-amigaos + os=amigaos ;; *-dg) - os=-dgux + os=dgux ;; *-dolphin) - os=-sysv3 + os=sysv3 ;; m68k-ccur) - os=-rtu + os=rtu ;; m88k-omron*) - os=-luna + os=luna ;; *-next) - os=-nextstep + os=nextstep ;; *-sequent) - os=-ptx + os=ptx ;; *-crds) - os=-unos + os=unos ;; *-ns) - os=-genix + os=genix ;; i370-*) - os=-mvs + os=mvs ;; *-gould) - os=-sysv + os=sysv ;; *-highlevel) - os=-bsd + os=bsd ;; *-encore) - os=-bsd + os=bsd ;; *-sgi) - os=-irix + os=irix ;; *-siemens) - os=-sysv4 + os=sysv4 ;; *-masscomp) - os=-rtu + os=rtu ;; f30[01]-fujitsu | f700-fujitsu) - os=-uxpv + os=uxpv ;; *-rom68k) - os=-coff + os=coff ;; *-*bug) - os=-coff + os=coff ;; *-apple) - os=-macos + os=macos ;; *-atari*) - os=-mint + os=mint + ;; + *-wrs) + os=vxworks ;; *) - os=-none + os=none ;; esac fi @@ -1721,67 +1726,70 @@ vendor=unknown case $basic_machine in *-unknown) case $os in - -riscix*) + riscix*) vendor=acorn ;; - -sunos*) + sunos*) vendor=sun ;; - -cnk*|-aix*) + cnk*|-aix*) vendor=ibm ;; - -beos*) + beos*) vendor=be ;; - -hpux*) + hpux*) vendor=hp ;; - -mpeix*) + mpeix*) vendor=hp ;; - -hiux*) + hiux*) vendor=hitachi ;; - -unos*) + unos*) vendor=crds ;; - -dgux*) + dgux*) vendor=dg ;; - -luna*) + luna*) vendor=omron ;; - -genix*) + genix*) vendor=ns ;; - -mvs* | -opened*) + clix*) + vendor=intergraph + ;; + mvs* | opened*) vendor=ibm ;; - -os400*) + os400*) vendor=ibm ;; - -ptx*) + ptx*) vendor=sequent ;; - -tpf*) + tpf*) vendor=ibm ;; - -vxsim* | -vxworks* | -windiss*) + vxsim* | vxworks* | windiss*) vendor=wrs ;; - -aux*) + aux*) vendor=apple ;; - -hms*) + hms*) vendor=hitachi ;; - -mpw* | -macos*) + mpw* | macos*) vendor=apple ;; - -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + *mint | mint[0-9]* | *MiNT | MiNT[0-9]*) vendor=atari ;; - -vos*) + vos*) vendor=stratus ;; esac @@ -1789,11 +1797,11 @@ case $basic_machine in ;; esac -echo "$basic_machine$os" +echo "$basic_machine-$os" exit # Local variables: -# eval: (add-hook 'write-file-functions 'time-stamp) +# eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" diff --git a/depends/description.md b/depends/description.md index 74f9ef3f20..9fc7093be4 100644 --- a/depends/description.md +++ b/depends/description.md @@ -7,7 +7,7 @@ In theory, binaries for any target OS/architecture can be created, from a builder running any OS/architecture. In practice, build-side tools must be specified when the defaults don't fit, and packages must be amended to work on new hosts. For now, a build architecture of x86_64 is assumed, either on -Linux or OSX. +Linux or macOS. ### No reliance on timestamps diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index 37f0c28a52..db47113b2f 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -52,6 +52,8 @@ $(package)_config_opts_aarch64_linux=linux-generic64 $(package)_config_opts_mipsel_linux=linux-generic32 $(package)_config_opts_mips_linux=linux-generic32 $(package)_config_opts_powerpc_linux=linux-generic32 +$(package)_config_opts_riscv32_linux=linux-generic32 +$(package)_config_opts_riscv64_linux=linux-generic64 $(package)_config_opts_x86_64_darwin=darwin64-x86_64-cc $(package)_config_opts_x86_64_mingw32=mingw64 $(package)_config_opts_i686_mingw32=mingw diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 551c9fa70b..69acb19b8f 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -5,6 +5,7 @@ qt_packages = qrencode protobuf zlib qt_x86_64_linux_packages:=qt expat dbus libxcb xcb_proto libXau xproto freetype fontconfig libX11 xextproto libXext xtrans qt_i686_linux_packages:=$(qt_x86_64_linux_packages) +qt_arm_linux_packages:=$(qt_x86_64_linux_packages) qt_darwin_packages=qt qt_mingw32_packages=qt diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 745c9e1157..22cf4f515b 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,22 +1,20 @@ PACKAGE=qt -$(package)_version=5.7.1 -$(package)_download_path=http://download.qt.io/official_releases/qt/5.7/$($(package)_version)/submodules -$(package)_suffix=opensource-src-$($(package)_version).tar.gz +$(package)_version=5.9.6 +$(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=95f83e532d23b3ddbde7973f380ecae1bac13230340557276f75f2e37984e410 +$(package)_sha256_hash=eed620cb268b199bd83b3fc6a471c51d51e1dc2dbb5374fc97a0cc75facbe36f $(package)_dependencies=openssl zlib $(package)_linux_dependencies=freetype fontconfig libxcb libX11 xproto libXext $(package)_build_subdir=qtbase $(package)_qt_libs=corelib network widgets gui plugins testlib -$(package)_patches=mac-qmake.conf mingw-uuidof.patch pidlist_absolute.patch fix-xcb-include-order.patch -$(package)_patches+=fix_qt_pkgconfig.patch fix-cocoahelpers-macos.patch qfixed-coretext.patch +$(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=3a15aebd523c6d89fb97b2d3df866c94149653a26d27a00aac9b6d3020bc5a1d - +$(package)_qttranslations_sha256_hash=9822084f8e2d2939ba39f4af4c0c2320e45d5996762a9423f833055607604ed8 $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=22d67de915cb8cd93e16fdd38fa006224ad9170bd217c2be1e53045a8dd02f0f +$(package)_qttools_sha256_hash=50e75417ec0c74bb8b1989d1d8e981ee83690dce7dfc0c2169f7c00f397e5117 $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -29,25 +27,18 @@ $(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-alsa -$(package)_config_opts += -no-audio-backend $(package)_config_opts += -no-cups $(package)_config_opts += -no-egl $(package)_config_opts += -no-eglfs -$(package)_config_opts += -no-feature-style-windowsmobile -$(package)_config_opts += -no-feature-style-windowsce $(package)_config_opts += -no-freetype $(package)_config_opts += -no-gif $(package)_config_opts += -no-glib -$(package)_config_opts += -no-gstreamer $(package)_config_opts += -no-icu $(package)_config_opts += -no-iconv $(package)_config_opts += -no-kms $(package)_config_opts += -no-linuxfb $(package)_config_opts += -no-libudev -$(package)_config_opts += -no-mitshm $(package)_config_opts += -no-mtdev -$(package)_config_opts += -no-pulseaudio $(package)_config_opts += -no-openvg $(package)_config_opts += -no-reduce-relocations $(package)_config_opts += -no-qml-debug @@ -62,7 +53,6 @@ $(package)_config_opts += -no-sql-sqlite $(package)_config_opts += -no-sql-sqlite2 $(package)_config_opts += -no-use-gold-linker $(package)_config_opts += -no-xinput2 -$(package)_config_opts += -no-xrender $(package)_config_opts += -nomake examples $(package)_config_opts += -nomake tests $(package)_config_opts += -opensource @@ -75,12 +65,13 @@ $(package)_config_opts += -qt-libpng $(package)_config_opts += -qt-libjpeg $(package)_config_opts += -qt-pcre $(package)_config_opts += -system-zlib -$(package)_config_opts += -reduce-exports $(package)_config_opts += -static $(package)_config_opts += -silent $(package)_config_opts += -v $(package)_config_opts += -no-feature-printer $(package)_config_opts += -no-feature-printdialog +$(package)_config_opts += -no-feature-concurrent +$(package)_config_opts += -no-feature-xml ifneq ($(build_os),darwin) $(package)_config_opts_darwin = -xplatform macx-clang-linux @@ -95,11 +86,12 @@ endif $(package)_config_opts_linux = -qt-xkbcommon $(package)_config_opts_linux += -qt-xcb $(package)_config_opts_linux += -system-freetype -$(package)_config_opts_linux += -no-sm +$(package)_config_opts_linux += -no-feature-sessionmanager $(package)_config_opts_linux += -fontconfig $(package)_config_opts_linux += -no-opengl -$(package)_config_opts_arm_linux = -platform linux-g++ -xplatform $(host) +$(package)_config_opts_arm_linux += -platform linux-g++ -xplatform bitcoin-linux-g++ $(package)_config_opts_i686_linux = -xplatform linux-g++-32 +$(package)_config_opts_x86_64_linux = -xplatform linux-g++-64 $(package)_config_opts_mingw32 = -no-opengl -xplatform win32-g++ -device-option CROSS_COMPILE="$(host)-" $(package)_build_env = QT_RCC_TEST=1 endef @@ -124,11 +116,10 @@ define $(package)_extract_cmds tar --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttools_file_name) -C qttools endef - define $(package)_preprocess_cmds sed -i.old "s|updateqm.commands = \$$$$\$$$$LRELEASE|updateqm.commands = $($(package)_extract_dir)/qttools/bin/lrelease|" qttranslations/translations/translations.pro && \ sed -i.old "/updateqm.depends =/d" qttranslations/translations/translations.pro && \ - sed -i.old "s/src_plugins.depends = src_sql src_xml src_network/src_plugins.depends = src_xml src_network/" qtbase/src/src.pro && \ + sed -i.old "s/src_plugins.depends = src_sql src_network/src_plugins.depends = src_network/" qtbase/src/src.pro && \ sed -i.old "s|X11/extensions/XIproto.h|X11/X.h|" qtbase/src/plugins/platforms/xcb/qxcbxsettings.cpp && \ sed -i.old 's/if \[ "$$$$XPLATFORM_MAC" = "yes" \]; then xspecvals=$$$$(macSDKify/if \[ "$$$$BUILD_ON_MAC" = "yes" \]; then xspecvals=$$$$(macSDKify/' qtbase/configure && \ sed -i.old 's/CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, 0)/CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, kCGMouseButtonLeft)/' qtbase/src/plugins/platforms/cocoa/qcocoacursor.mm && \ @@ -137,19 +128,19 @@ define $(package)_preprocess_cmds cp -f qtbase/mkspecs/macx-clang/Info.plist.app qtbase/mkspecs/macx-clang-linux/ &&\ cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\ cp -f $($(package)_patch_dir)/mac-qmake.conf qtbase/mkspecs/macx-clang-linux/qmake.conf && \ - patch -p1 < $($(package)_patch_dir)/mingw-uuidof.patch && \ - patch -p1 < $($(package)_patch_dir)/pidlist_absolute.patch && \ - patch -p1 < $($(package)_patch_dir)/fix-xcb-include-order.patch && \ - patch -p1 < $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ - patch -p1 < $($(package)_patch_dir)/fix-cocoahelpers-macos.patch && \ - patch -p1 < $($(package)_patch_dir)/qfixed-coretext.patch && \ + cp -r qtbase/mkspecs/linux-arm-gnueabi-g++ qtbase/mkspecs/bitcoin-linux-g++ && \ + sed -i s/arm-linux-gnueabi-/$(host)-/g qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch &&\ + patch -p1 -i $($(package)_patch_dir)/fix_configure_mac.patch &&\ + patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch &&\ echo "!host_build: QMAKE_CFLAGS += $($(package)_cflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ echo "!host_build: QMAKE_CXXFLAGS += $($(package)_cxxflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ echo "!host_build: QMAKE_LFLAGS += $($(package)_ldflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ + echo "QMAKE_LINK_OBJECT_MAX = 10" >> qtbase/mkspecs/win32-g++/qmake.conf &&\ + echo "QMAKE_LINK_OBJECT_SCRIPT = object_script" >> qtbase/mkspecs/win32-g++/qmake.conf &&\ sed -i.old "s|QMAKE_CFLAGS = |!host_build: QMAKE_CFLAGS = $($(package)_cflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ sed -i.old "s|QMAKE_LFLAGS = |!host_build: QMAKE_LFLAGS = $($(package)_ldflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ sed -i.old "s|QMAKE_CXXFLAGS = |!host_build: QMAKE_CXXFLAGS = $($(package)_cxxflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf - endef define $(package)_config_cmds @@ -161,19 +152,22 @@ define $(package)_config_cmds echo "CONFIG += force_bootstrap" >> mkspecs/qconfig.pri && \ $(MAKE) sub-src-clean && \ cd ../qttranslations && ../qtbase/bin/qmake qttranslations.pro -o Makefile && \ - cd translations && ../../qtbase/bin/qmake translations.pro -o Makefile && cd ../.. &&\ - cd qttools/src/linguist/lrelease/ && ../../../../qtbase/bin/qmake lrelease.pro -o Makefile + cd translations && ../../qtbase/bin/qmake translations.pro -o Makefile && cd ../.. && \ + cd qttools/src/linguist/lrelease/ && ../../../../qtbase/bin/qmake lrelease.pro -o Makefile && \ + cd ../lupdate/ && ../../../../qtbase/bin/qmake lupdate.pro -o Makefile && cd ../../../.. endef define $(package)_build_cmds $(MAKE) -C src $(addprefix sub-,$($(package)_qt_libs)) && \ $(MAKE) -C ../qttools/src/linguist/lrelease && \ + $(MAKE) -C ../qttools/src/linguist/lupdate && \ $(MAKE) -C ../qttranslations endef define $(package)_stage_cmds - $(MAKE) -C src INSTALL_ROOT=$($(package)_staging_dir) $(addsuffix -install_subtargets,$(addprefix sub-,$($(package)_qt_libs))) && cd .. &&\ + $(MAKE) -C src INSTALL_ROOT=$($(package)_staging_dir) $(addsuffix -install_subtargets,$(addprefix sub-,$($(package)_qt_libs))) && cd .. && \ $(MAKE) -C qttools/src/linguist/lrelease INSTALL_ROOT=$($(package)_staging_dir) install_target && \ + $(MAKE) -C qttools/src/linguist/lupdate INSTALL_ROOT=$($(package)_staging_dir) install_target && \ $(MAKE) -C qttranslations INSTALL_ROOT=$($(package)_staging_dir) install_subtargets && \ if `test -f qtbase/src/plugins/platforms/xcb/xcb-static/libxcb-static.a`; then \ cp qtbase/src/plugins/platforms/xcb/xcb-static/libxcb-static.a $($(package)_staging_prefix_dir)/lib; \ diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index cde523370f..d5fd1f39ab 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -6,14 +6,15 @@ $(package)_sha256_hash=8f1e2b2aade4dbfde98d82366d61baef2f62e812530160d2e6d0a5bb2 $(package)_patches=0001-fix-build-with-older-mingw64.patch 0002-disable-pthread_set_name_np.patch define $(package)_set_vars - $(package)_config_opts=--without-docs --disable-shared --without-libsodium --disable-curve --disable-curve-keygen --disable-perf + $(package)_config_opts=--without-docs --disable-shared --without-libsodium --disable-curve --disable-curve-keygen --disable-perf --disable-Werror $(package)_config_opts_linux=--with-pic $(package)_cxxflags=-std=c++11 endef define $(package)_preprocess_cmds patch -p1 < $($(package)_patch_dir)/0001-fix-build-with-older-mingw64.patch && \ - patch -p1 < $($(package)_patch_dir)/0002-disable-pthread_set_name_np.patch + patch -p1 < $($(package)_patch_dir)/0002-disable-pthread_set_name_np.patch && \ + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub config endef define $(package)_config_cmds diff --git a/depends/patches/qt/fix-cocoahelpers-macos.patch b/depends/patches/qt/fix-cocoahelpers-macos.patch deleted file mode 100644 index 1b43a9eff8..0000000000 --- a/depends/patches/qt/fix-cocoahelpers-macos.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0707260a4f8e64dfadf1df5f935e74cabb7c7d27 Mon Sep 17 00:00:00 2001 -From: Jake Petroules <jake.petroules@qt.io> -Date: Sun, 1 Oct 2017 21:48:17 -0700 -Subject: [PATCH] Fix build error with macOS 10.13 SDK -MIME-Version: 1.0 -Content-Type: text/plain; charset=utf8 -Content-Transfer-Encoding: 8bit - -Several of these variables/macros are no longer defined. We didn't -validate the preconditions on iOS, tvOS, or watchOS, so no -need to bother validating them on macOS either. Nor did we check the -OSStatus result on any platform anyways. - -Task-number: QTBUG-63401 -Change-Id: Ife64dff767cf6d3f4b839fc53ec486181c176bf3 -(cherry-picked from 861544583511d4e6f7745d2339b26ff1cd44132b) -Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> -Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> ---- - src/plugins/platforms/cocoa/qcocoahelpers.h | 2 +- - src/plugins/platforms/cocoa/qcocoahelpers.mm | 13 +------------ - 2 files changed, 2 insertions(+), 13 deletions(-) - -diff --git old/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.h new/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.h -index bbb3793..74371d5 100644 ---- old/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.h -+++ new/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.h -@@ -80,7 +80,7 @@ QColor qt_mac_toQColor(CGColorRef color); - // Creates a mutable shape, it's the caller's responsibility to release. - HIMutableShapeRef qt_mac_QRegionToHIMutableShape(const QRegion ®ion); - --OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage); -+void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage); - - NSDragOperation qt_mac_mapDropAction(Qt::DropAction action); - NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions); -diff --git old/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.mm new/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.mm -index cd73148..3f8429e 100644 ---- old/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.mm -+++ new/qtbase/src/plugins/platforms/cocoa/qcocoahelpers.mm -@@ -544,15 +544,8 @@ NSRect qt_mac_flipRect(const QRect &rect) - return NSMakeRect(rect.x(), flippedY, rect.width(), rect.height()); - } - --OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage) -+void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage) - { -- // Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev) -- OSStatus err = noErr; -- -- require_action(inContext != NULL, InvalidContext, err = paramErr); -- require_action(inBounds != NULL, InvalidBounds, err = paramErr); -- require_action(inImage != NULL, InvalidImage, err = paramErr); -- - CGContextSaveGState( inContext ); - CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds)); - CGContextScaleCTM(inContext, 1, -1); -@@ -560,10 +553,6 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm - CGContextDrawImage(inContext, *inBounds, inImage); - - CGContextRestoreGState(inContext); --InvalidImage: --InvalidBounds: --InvalidContext: -- return err; - } - - Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) --- -2.7.4 diff --git a/depends/patches/qt/fix-xcb-include-order.patch b/depends/patches/qt/fix-xcb-include-order.patch deleted file mode 100644 index ec2bc17d9b..0000000000 --- a/depends/patches/qt/fix-xcb-include-order.patch +++ /dev/null @@ -1,49 +0,0 @@ ---- old/qtbase/src/plugins/platforms/xcb/xcb_qpa_lib.pro 2015-03-17 -+++ new/qtbase/src/plugins/platforms/xcb/xcb_qpa_lib.pro 2015-03-17 -@@ -76,8 +76,6 @@ - - DEFINES += $$QMAKE_DEFINES_XCB - LIBS += $$QMAKE_LIBS_XCB --QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XCB --QMAKE_CFLAGS += $$QMAKE_CFLAGS_XCB - - CONFIG += qpa/genericunixfontdatabase - -@@ -89,7 +87,8 @@ - contains(QT_CONFIG, xcb-qt) { - DEFINES += XCB_USE_RENDER - XCB_DIR = ../../../3rdparty/xcb -- INCLUDEPATH += $$XCB_DIR/include $$XCB_DIR/sysinclude -+ QMAKE_CFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB -+ QMAKE_CXXFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB - LIBS += -lxcb -L$$MODULE_BASE_OUTDIR/lib -lxcb-static$$qtPlatformTargetSuffix() - } else { - LIBS += -lxcb -lxcb-image -lxcb-icccm -lxcb-sync -lxcb-xfixes -lxcb-shm -lxcb-randr -lxcb-shape -lxcb-keysyms -lxcb-xinerama ---- old/qtbase/src/plugins/platforms/xcb/xcb-static/xcb-static.pro -+++ new/qtbase/src/plugins/platforms/xcb/xcb-static/xcb-static.pro -@@ -9,7 +9,8 @@ - - XCB_DIR = ../../../../3rdparty/xcb - --INCLUDEPATH += $$XCB_DIR/include $$XCB_DIR/include/xcb $$XCB_DIR/sysinclude -+QMAKE_CFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/include/xcb -I$$XCB_DIR/sysinclude -+QMAKE_CXXFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/include/xcb -I$$XCB_DIR/sysinclude - - QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XCB - QMAKE_CFLAGS += $$QMAKE_CFLAGS_XCB ---- old/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro -+++ new/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro -@@ -6,6 +6,13 @@ - qxcbmain.cpp - OTHER_FILES += xcb.json README - -+contains(QT_CONFIG, xcb-qt) { -+ DEFINES += XCB_USE_RENDER -+ XCB_DIR = ../../../3rdparty/xcb -+ QMAKE_CFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB -+ QMAKE_CXXFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB -+} -+ - PLUGIN_TYPE = platforms - PLUGIN_CLASS_NAME = QXcbIntegrationPlugin - !equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - diff --git a/depends/patches/qt/fix_configure_mac.patch b/depends/patches/qt/fix_configure_mac.patch new file mode 100644 index 0000000000..0d7dd647de --- /dev/null +++ b/depends/patches/qt/fix_configure_mac.patch @@ -0,0 +1,50 @@ +--- old/qtbase/mkspecs/features/mac/sdk.prf 2018-02-08 10:24:48.000000000 -0800 ++++ new/qtbase/mkspecs/features/mac/sdk.prf 2018-03-23 10:38:56.000000000 -0700 +@@ -8,21 +8,21 @@ + defineReplace(xcodeSDKInfo) { + info = $$1 + equals(info, "Path"): \ +- info = --show-sdk-path ++ infoarg = --show-sdk-path + equals(info, "PlatformPath"): \ +- info = --show-sdk-platform-path ++ infoarg = --show-sdk-platform-path + equals(info, "SDKVersion"): \ +- info = --show-sdk-version ++ infoarg = --show-sdk-version + sdk = $$2 + isEmpty(sdk): \ + sdk = $$QMAKE_MAC_SDK + + isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}) { +- QMAKE_MAC_SDK.$${sdk}.$${info} = $$system("/usr/bin/xcrun --sdk $$sdk $$info 2>/dev/null") ++ QMAKE_MAC_SDK.$${sdk}.$${info} = $$system("/usr/bin/xcrun --sdk $$sdk $$infoarg 2>/dev/null") + # --show-sdk-platform-path won't work for Command Line Tools; this is fine + # only used by the XCTest backend to testlib +- isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}):if(!isEmpty(QMAKE_XCODEBUILD_PATH)|!equals(info, "--show-sdk-platform-path")): \ +- error("Could not resolve SDK $$info for \'$$sdk\'") ++ isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}):if(!isEmpty(QMAKE_XCODEBUILD_PATH)|!equals(infoarg, "--show-sdk-platform-path")): \ ++ error("Could not resolve SDK $$info for \'$$sdk\' using $$infoarg") + cache(QMAKE_MAC_SDK.$${sdk}.$${info}, set stash, QMAKE_MAC_SDK.$${sdk}.$${info}) + } + +--- old/qtbase/configure 2018-02-08 10:24:48.000000000 -0800 ++++ new/qtbase/configure 2018-03-23 05:42:29.000000000 -0700 +@@ -232,8 +232,13 @@ + + sdk=$(getSingleQMakeVariable "QMAKE_MAC_SDK" "$1") + if [ -z "$sdk" ]; then echo "QMAKE_MAC_SDK must be set when building on Mac" >&2; exit 1; fi +- sysroot=$(/usr/bin/xcrun --sdk $sdk --show-sdk-path 2>/dev/null) +- if [ -z "$sysroot" ]; then echo "Failed to resolve SDK path for '$sdk'" >&2; exit 1; fi ++ sysroot=$(getSingleQMakeVariable "QMAKE_MAC_SDK_PATH" "$1") ++ ++ echo "sysroot pre-configured as $sysroot"; ++ if [ -z "$sysroot" ]; then ++ sysroot=$(/usr/bin/xcrun --sdk $sdk --show-sdk-path 2>/dev/null) ++ if [ -z "$sysroot" ]; then echo "Failed to resolve SDK path for '$sdk'" >&2; exit 1; fi ++ fi + + case "$sdk" in + macosx*) + + diff --git a/depends/patches/qt/fix_no_printer.patch b/depends/patches/qt/fix_no_printer.patch new file mode 100644 index 0000000000..f868ca2577 --- /dev/null +++ b/depends/patches/qt/fix_no_printer.patch @@ -0,0 +1,19 @@ +--- x/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h ++++ y/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h +@@ -52,6 +52,7 @@ + // + + #include <QtCore/qglobal.h> ++#include <qpa/qplatformprintdevice.h> + + #ifndef QT_NO_PRINTER + +--- x/qtbase/src/plugins/plugins.pro ++++ y/qtbase/src/plugins/plugins.pro +@@ -8,6 +8,3 @@ qtHaveModule(gui) { + qtConfig(imageformatplugin): SUBDIRS *= imageformats + !android:qtConfig(library): SUBDIRS *= generic + } +- +-!winrt:qtHaveModule(printsupport): \ +- SUBDIRS += printsupport diff --git a/depends/patches/qt/mac-qmake.conf b/depends/patches/qt/mac-qmake.conf index ca70d30b15..337d0eb9ca 100644 --- a/depends/patches/qt/mac-qmake.conf +++ b/depends/patches/qt/mac-qmake.conf @@ -14,6 +14,7 @@ QMAKE_MAC_SDK.macosx.Path = $${MAC_SDK_PATH} QMAKE_MAC_SDK.macosx.platform_name = macosx QMAKE_MAC_SDK.macosx.SDKVersion = $${MAC_SDK_VERSION} QMAKE_MAC_SDK.macosx.PlatformPath = /phony +QMAKE_APPLE_DEVICE_ARCHS=x86_64 !host_build: QMAKE_CFLAGS += -target $${MAC_TARGET} !host_build: QMAKE_OBJECTIVE_CFLAGS += $$QMAKE_CFLAGS !host_build: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS diff --git a/depends/patches/qt/mingw-uuidof.patch b/depends/patches/qt/mingw-uuidof.patch deleted file mode 100644 index fb21923c8c..0000000000 --- a/depends/patches/qt/mingw-uuidof.patch +++ /dev/null @@ -1,44 +0,0 @@ ---- old/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp -+++ new/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp -@@ -77,7 +77,7 @@ - #include <stdlib.h> - #include <stdio.h> - #include <windowsx.h> --#ifndef Q_OS_WINCE -+#if !defined(Q_OS_WINCE) && (!defined(USE___UUIDOF) || (defined(USE___UUIDOF) && USE___UUIDOF == 1)) - # include <comdef.h> - #endif - -@@ -814,7 +814,7 @@ - HWND_MESSAGE, NULL, static_cast<HINSTANCE>(GetModuleHandle(0)), NULL); - } - --#ifndef Q_OS_WINCE -+#if !defined(Q_OS_WINCE) && (!defined(USE___UUIDOF) || (defined(USE___UUIDOF) && USE___UUIDOF == 1)) - // Re-engineered from the inline function _com_error::ErrorMessage(). - // We cannot use it directly since it uses swprintf_s(), which is not - // present in the MSVCRT.DLL found on Windows XP (QTBUG-35617). -@@ -833,7 +833,7 @@ - return QString::asprintf("IDispatch error #%u", uint(wCode)); - return QString::asprintf("Unknown error 0x0%x", uint(comError.Error())); - } --#endif // !Q_OS_WINCE -+#endif // !defined(Q_OS_WINCE) && (!defined(USE___UUIDOF) || (defined(USE___UUIDOF) && USE___UUIDOF == 1)) - - /*! - \brief Common COM error strings. -@@ -901,12 +901,12 @@ - default: - break; - } --#ifndef Q_OS_WINCE -+#if !defined(Q_OS_WINCE) && (!defined(USE___UUIDOF) || (defined(USE___UUIDOF) && USE___UUIDOF == 1)) - _com_error error(hr); - result += QByteArrayLiteral(" ("); - result += errorMessageFromComError(error); - result += ')'; --#endif // !Q_OS_WINCE -+#endif // !defined(Q_OS_WINCE) && (!defined(USE___UUIDOF) || (defined(USE___UUIDOF) && USE___UUIDOF == 1)) - return result; - } - diff --git a/depends/patches/qt/pidlist_absolute.patch b/depends/patches/qt/pidlist_absolute.patch deleted file mode 100644 index c792824179..0000000000 --- a/depends/patches/qt/pidlist_absolute.patch +++ /dev/null @@ -1,37 +0,0 @@ -diff -dur old/qtbase/src/plugins/platforms/windows/qwindowscontext.h new/qtbase/src/plugins/platforms/windows/qwindowscontext.h ---- old/qtbase/src/plugins/platforms/windows/qwindowscontext.h -+++ new/qtbase/src/plugins/platforms/windows/qwindowscontext.h -@@ -136,10 +136,18 @@ - inline void init(); - - typedef HRESULT (WINAPI *SHCreateItemFromParsingName)(PCWSTR, IBindCtx *, const GUID&, void **); -+#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3) -+ typedef HRESULT (WINAPI *SHGetKnownFolderIDList)(const GUID &, DWORD, HANDLE, ITEMIDLIST **); -+#else - typedef HRESULT (WINAPI *SHGetKnownFolderIDList)(const GUID &, DWORD, HANDLE, PIDLIST_ABSOLUTE *); -+#endif - typedef HRESULT (WINAPI *SHGetStockIconInfo)(int , int , _SHSTOCKICONINFO *); - typedef HRESULT (WINAPI *SHGetImageList)(int, REFIID , void **); -+#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3) -+ typedef HRESULT (WINAPI *SHCreateItemFromIDList)(const ITEMIDLIST *, REFIID, void **); -+#else - typedef HRESULT (WINAPI *SHCreateItemFromIDList)(PCIDLIST_ABSOLUTE, REFIID, void **); -+#endif - - SHCreateItemFromParsingName sHCreateItemFromParsingName; - SHGetKnownFolderIDList sHGetKnownFolderIDList; -diff -dur old/qtbase/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp new/qtbase/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp ---- old/qtbase/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp -+++ new/qtbase/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp -@@ -1016,7 +1016,11 @@ - qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path(); - return Q_NULLPTR; - } -+#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3) -+ ITEMIDLIST *idList; -+#else - PIDLIST_ABSOLUTE idList; -+#endif - HRESULT hr = QWindowsContext::shell32dll.sHGetKnownFolderIDList(uuid, 0, 0, &idList); - if (FAILED(hr)) { - qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); diff --git a/depends/patches/qt/qfixed-coretext.patch b/depends/patches/qt/qfixed-coretext.patch deleted file mode 100644 index aa56f1e1de..0000000000 --- a/depends/patches/qt/qfixed-coretext.patch +++ /dev/null @@ -1,34 +0,0 @@ -From dbdd5f0ffbce52c8b789ed09f1aa3f1da6c02e23 Mon Sep 17 00:00:00 2001 -From: Gabriel de Dietrich <gabriel.dedietrich@qt.io> -Date: Fri, 30 Mar 2018 11:58:16 -0700 -Subject: [PATCH] QCoreTextFontEngine: Fix build with Xcode 9.3 - -Apple LLVM version 9.1.0 (clang-902.0.39.1) - -Error message: - -.../qfontengine_coretext.mm:827:20: error: qualified reference to - 'QFixed' is a constructor name rather than a type in this context - return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont))); - -Change-Id: Iebe26b3b087a16b10664208fc8851cbddb47f043 -Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com> ---- - src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git old/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm new/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm -index 25ff69d877d..98b753eff96 100644 ---- old/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm -+++ new/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm -@@ -824,7 +824,7 @@ void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, gl - - QFixed QCoreTextFontEngine::emSquareSize() const - { -- return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont))); -+ return QFixed(int(CTFontGetUnitsPerEm(ctfont))); - } - - QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const --- -2.16.3
\ No newline at end of file diff --git a/doc/README.md b/doc/README.md index ddb239f60c..45762b2374 100644 --- a/doc/README.md +++ b/doc/README.md @@ -22,7 +22,7 @@ Unpack the files into a directory and run: Unpack the files into a directory, and then run bitcoin-qt.exe. -### OS X +### macOS Drag Bitcoin-Core to your applications folder, and then run Bitcoin-Core. @@ -38,7 +38,7 @@ Building The following are developer notes on how to build Bitcoin on your native platform. They are not complete guides, but include notes on the necessary libraries, compile flags, etc. - [Dependencies](dependencies.md) -- [OS X Build Notes](build-osx.md) +- [macOS Build Notes](build-osx.md) - [Unix Build Notes](build-unix.md) - [Windows Build Notes](build-windows.md) - [OpenBSD Build Notes](build-openbsd.md) diff --git a/doc/README_osx.md b/doc/README_osx.md index 975be4be9e..739e22d634 100644 --- a/doc/README_osx.md +++ b/doc/README_osx.md @@ -1,12 +1,12 @@ -Deterministic OS X DMG Notes. +Deterministic macOS DMG Notes. -Working OS X DMGs are created in Linux by combining a recent clang, +Working macOS DMGs are created in Linux by combining a recent clang, the Apple binutils (ld, ar, etc) and DMG authoring tools. Apple uses clang extensively for development and has upstreamed the necessary functionality so that a vanilla clang can take advantage. It supports the use of -F, -target, -mmacosx-version-min, and --sysroot, which are all necessary -when building for OS X. +when building for macOS. Apple's version of binutils (called cctools) contains lots of functionality missing in the FSF's binutils. In addition to extra linker options for @@ -38,7 +38,7 @@ Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.1 Unfortunately, the usual linux tools (7zip, hpmount, loopback mount) are incapable of opening this file. To create a tarball suitable for Gitian input, there are two options: -Using Mac OS X, you can mount the dmg, and then create it with: +Using macOS, you can mount the dmg, and then create it with: ``` $ hdiutil attach Xcode_7.3.1.dmg $ tar -C /Volumes/Xcode/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ -czf MacOSX10.11.sdk.tar.gz MacOSX10.11.sdk @@ -81,7 +81,7 @@ Background images and other features can be added to DMG files by inserting a .DS_Store before creation. This is generated by the script contrib/macdeploy/custom_dsstore.py. -As of OS X Mavericks (10.9), using an Apple-blessed key to sign binaries is a +As of OS X 10.9 Mavericks, using an Apple-blessed key to sign binaries is a requirement in order to satisfy the new Gatekeeper requirements. Because this private key cannot be shared, we'll have to be a bit creative in order for the build process to remain somewhat deterministic. Here's how it works: diff --git a/doc/bips.md b/doc/bips.md index 5b8eabd6df..272cf4de29 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -15,7 +15,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.16.0**): * [`BIP 35`](https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki): The 'mempool' protocol message (and the protocol version bump to 60002) has been implemented since **v0.7.0** ([PR #1641](https://github.com/bitcoin/bitcoin/pull/1641)). * [`BIP 37`](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki): The bloom filtering for transaction relaying, partial merkle trees for blocks, and the protocol version bump to 70001 (enabling low-bandwidth SPV clients) has been implemented since **v0.8.0** ([PR #1795](https://github.com/bitcoin/bitcoin/pull/1795)). * [`BIP 42`](https://github.com/bitcoin/bips/blob/master/bip-0042.mediawiki): The bug that would have caused the subsidy schedule to resume after block 13440000 was fixed in **v0.9.2** ([PR #3842](https://github.com/bitcoin/bitcoin/pull/3842)). -* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). +* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting *v0.17.0*, whether to send reject messages can be configured with the `-enablebip61` option. * [`BIP 65`](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki): The CHECKLOCKTIMEVERIFY softfork was merged in **v0.12.0** ([PR #6351](https://github.com/bitcoin/bitcoin/pull/6351)), and backported to **v0.11.2** and **v0.10.4**. Mempool-only CLTV was added in [PR #6124](https://github.com/bitcoin/bitcoin/pull/6124). * [`BIP 66`](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki): The strict DER rules and associated version 3 blocks have been implemented since **v0.10.0** ([PR #5713](https://github.com/bitcoin/bitcoin/pull/5713)). * [`BIP 68`](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki): Sequence locks have been implemented as of **v0.12.1** ([PR #7184](https://github.com/bitcoin/bitcoin/pull/7184)), and have been activated since *block 419328*. diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md new file mode 100644 index 0000000000..48746ce0c2 --- /dev/null +++ b/doc/build-freebsd.md @@ -0,0 +1,46 @@ +FreeBSD build guide +====================== +(updated for FreeBSD 11.1) + +This guide describes how to build bitcoind and command-line utilities on FreeBSD. + +This guide does not contain instructions for building the GUI. + +## Preparation + +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 +``` + +For the wallet (optional): +``` +./contrib/install_db4.sh `pwd` +export BDB_PREFIX="$PWD/db4" +``` + +See [dependencies.md](dependencies.md) for a complete overview. + +Download the source code: +``` +git clone https://github.com/bitcoin/bitcoin +``` + +## Building Bitcoin Core + +**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 + +gmake +``` + +*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-openbsd.md b/doc/build-openbsd.md index 0817821221..63288acf16 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -1,6 +1,6 @@ OpenBSD build guide ====================== -(updated for OpenBSD 6.2) +(updated for OpenBSD 6.3) This guide describes how to build bitcoind and command-line utilities on OpenBSD. @@ -12,11 +12,10 @@ Preparation Run the following as root to install the base dependencies for building: ```bash -pkg_add git gmake libevent libtool +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 python # (select highest version, e.g. 3.6) -pkg_add boost git clone https://github.com/bitcoin/bitcoin.git ``` @@ -55,8 +54,15 @@ export BDB_PREFIX="$PWD/db4" Preparation: ```bash -export AUTOCONF_VERSION=2.69 # replace this with the autoconf version that you installed -export AUTOMAKE_VERSION=1.15 # replace this with the automake version that you installed + +# Replace this with the autoconf version that you installed. Include only +# the major and minor parts of the version: use "2.69" for "autoconf-2.69p2". +export AUTOCONF_VERSION=2.69 + +# Replace this with the automake version that you installed. Include only +# the major and minor parts of the version: use "1.15" for "automake-1.15.1". +export AUTOMAKE_VERSION=1.15 + ./autogen.sh ``` Make sure `BDB_PREFIX` is set to the appropriate path from the above steps. diff --git a/doc/build-osx.md b/doc/build-osx.md index e52a770ced..a07dbd1e1d 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -1,11 +1,11 @@ -Mac OS X Build Instructions and Notes +macOS Build Instructions and Notes ==================================== The commands in this guide should be executed in a Terminal application. The built-in one is located in `/Applications/Utilities/Terminal.app`. Preparation ----------- -Install the OS X command line tools: +Install the macOS command line tools: `xcode-select --install` @@ -24,8 +24,6 @@ If you want to build the disk image with `make deploy` (.dmg / optional), you ne brew install librsvg -NOTE: Building with Qt4 is still supported, however, could result in a broken UI. Building with Qt5 is recommended. - Berkeley DB ----------- It is recommended to use Berkeley DB 4.8. If you have to build it yourself, @@ -93,6 +91,6 @@ Other commands: Notes ----- -* Tested on OS X 10.8 through 10.13 on 64-bit Intel processors only. +* Tested on OS X 10.8 Mountain Lion through macOS 10.13 High Sierra on 64-bit Intel processors only. * Building with downloaded Qt binaries is not officially supported. See the notes in [#7714](https://github.com/bitcoin/bitcoin/issues/7714) diff --git a/doc/build-unix.md b/doc/build-unix.md index 2d10484a65..e884c0ab67 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -2,8 +2,7 @@ UNIX BUILD NOTES ==================== Some notes on how to build Bitcoin Core in Unix. -(For BSD specific instructions, see [build-openbsd.md](build-openbsd.md) and/or -[build-netbsd.md](build-netbsd.md)) +(For BSD specific instructions, see `build-*bsd.md` in this directory.) Note --------------------- @@ -71,19 +70,7 @@ 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 - -Options when installing required Boost library files: - -1. On at least Ubuntu 14.04+ and Debian 7+ there are generic names for the -individual boost development packages, so the following can be used to only -install necessary parts of boost: - - sudo apt-get install libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-program-options-dev libboost-test-dev libboost-thread-dev - -2. If that doesn't work, you can install all boost development packages with: - - sudo apt-get install libboost-all-dev + 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-program-options-dev libboost-test-dev libboost-thread-dev BerkeleyDB is required for the wallet. @@ -113,18 +100,13 @@ ZMQ dependencies (provides ZMQ API 4.x): #### Dependencies for the GUI If you want to build Bitcoin-Qt, make sure that the required packages for Qt development -are installed. Either Qt 5 or Qt 4 are necessary to build the GUI. -If both Qt 4 and Qt 5 are installed, Qt 5 will be used. Pass `--with-gui=qt4` to configure to choose Qt4. +are installed. Qt 5 is necessary to build the GUI. To build without GUI pass `--without-gui`. -To build with Qt 5 (recommended) you need the following: +To build with Qt 5 you need the following: sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libprotobuf-dev protobuf-compiler -Alternatively, to build with Qt 4 you need the following: - - sudo apt-get install libqt4-dev libprotobuf-dev protobuf-compiler - libqrencode (optional) can be installed with: sudo apt-get install libqrencode-dev @@ -145,7 +127,7 @@ Optional: sudo dnf install miniupnpc-devel -To build with Qt 5 (recommended) you need the following: +To build with Qt 5 you need the following: sudo dnf install qt5-qttools-devel qt5-qtbase-devel protobuf-devel @@ -303,33 +285,3 @@ To build executables for ARM: For further documentation on the depends system see [README.md](../depends/README.md) in the depends directory. -Building on FreeBSD --------------------- - -(Updated as of FreeBSD 11.0) - -Clang is installed by default as `cc` compiler, this makes it easier to get -started than on [OpenBSD](build-openbsd.md). Installing dependencies: - - pkg install autoconf automake libtool pkgconf - pkg install boost-libs openssl libevent - pkg install gmake - -You need to use GNU make (`gmake`) instead of `make`. -(`libressl` instead of `openssl` will also work) - -For the wallet (optional): - - ./contrib/install_db4.sh `pwd` - setenv BDB_PREFIX $PWD/db4 - -Then build using: - - ./autogen.sh - ./configure --disable-wallet # OR - ./configure BDB_CFLAGS="-I${BDB_PREFIX}/include" BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx" - gmake - -*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-windows.md b/doc/build-windows.md index 0a4136173b..935e29ce1c 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -5,7 +5,7 @@ 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 Trusty 14.04 is recommended +* 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 Subsystem for Linux (WSL)](https://msdn.microsoft.com/commandline/wsl/about) and the Mingw-w64 cross compiler tool chain. @@ -38,10 +38,10 @@ To install WSL on Windows 10 with Fall Creators Update installed (version >= 162 * Enable 'Windows Subsystem for Linux' * Click 'OK' and restart if necessary 2. Install Ubuntu - * Open Microsoft Store and search for Ubuntu or use [this link](https://www.microsoft.com/store/productId/9NBLGGH4MSV6) + * Open Microsoft Store and search for "Ubuntu 18.04" or use [this link](https://www.microsoft.com/store/productId/9N9TNGVNDL3Q) * Click Install 3. Complete Installation - * Open a cmd prompt and type "Ubuntu" + * Open a cmd prompt and type "Ubuntu1804" * Create a new UNIX user account (this is a separate account from your Windows account) After the bash shell is active, you can follow the instructions below, starting @@ -51,11 +51,6 @@ recommended but it is possible to compile the 32-bit version. Cross-compilation for Ubuntu and Windows Subsystem for Linux ------------------------------------------------------------ -At the time of writing the Windows Subsystem for Linux installs Ubuntu Xenial 16.04. The Mingw-w64 package -for Ubuntu Xenial does not produce working executables for some of the Bitcoin Core applications. -It is possible to build on Ubuntu Xenial by installing the cross compiler packages from Ubuntu Zesty, see the steps below. -Building on Ubuntu Zesty 17.04 up to 17.10 has been verified to work. - The steps below can be performed on Ubuntu (including in a VM) or WSL. The depends system will also work on other Linux distributions, however the commands for installing the toolchain will be different. @@ -74,26 +69,11 @@ See also: [dependencies.md](dependencies.md). ## Building for 64-bit Windows -The first step is to install the mingw-w64 cross-compilation tool chain. Due to different Ubuntu -packages for each distribution and problems with the Xenial packages the steps for each are different. - -Common steps to install mingw32 cross compiler tool chain: +The first step is to install the mingw-w64 cross-compilation tool chain. sudo apt install g++-mingw-w64-x86-64 -Ubuntu Trusty 14.04: - - No further steps required - -Ubuntu Xenial 16.04 and Windows Subsystem for Linux <sup>[1](#footnote1),[2](#footnote2)</sup>: - - sudo apt install software-properties-common - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu zesty universe" - sudo apt update - sudo apt upgrade - sudo update-alternatives --config x86_64-w64-mingw32-g++ # Set the default mingw32 g++ compiler option to posix. - -Ubuntu Zesty 17.04 <sup>[2](#footnote2)</sup>: +Ubuntu Bionic 18.04 <sup>[1](#footnote1)</sup>: sudo update-alternatives --config x86_64-w64-mingw32-g++ # Set the default mingw32 g++ compiler option to posix. @@ -123,7 +103,7 @@ To build executables for Windows 32-bit, install the following dependencies: sudo apt install g++-mingw-w64-i686 mingw-w64-i686-dev -For Ubuntu Xenial 16.04, Ubuntu Zesty 17.04 and Windows Subsystem for Linux <sup>[2](#footnote2)</sup>: +For Ubuntu Bionic 18.04 and Windows Subsystem for Linux <sup>[1](#footnote1)</sup>: sudo update-alternatives --config i686-w64-mingw32-g++ # Set the default mingw32 g++ compiler option to posix. @@ -162,14 +142,7 @@ way. This will install to `c:\workspace\bitcoin`, for example: Footnotes --------- -<a name="footnote1">1</a>: There is currently a bug in the 64 bit Mingw-w64 cross compiler packaged for WSL/Ubuntu Xenial 16.04 that -causes two of the bitcoin executables to crash shortly after start up. The bug is related to the --fstack-protector-all g++ compiler flag which is used to mitigate buffer overflows. -Installing the Mingw-w64 packages from the Ubuntu 17 distribution solves the issue, however, this is not -an officially supported approach and it's only recommended if you are prepared to reinstall WSL/Ubuntu should -something break. - -<a name="footnote2">2</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. diff --git a/doc/dependencies.md b/doc/dependencies.md index 7aa96c4c9b..793c659419 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -23,7 +23,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | protobuf | [2.6.3](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.7.1](https://download.qt.io/official_releases/qt/) | 4.7+ | No | | | +| Qt | [5.7.1](https://download.qt.io/official_releases/qt/) | 5.x | No | | | | XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L94) (Linux only) | | xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L93) (Linux only) | | ZeroMQ | [4.2.3](https://github.com/zeromq/libzmq/releases) | | No | | | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 1f237b750e..2fa91ecb02 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -499,7 +499,35 @@ Strings and formatting - Use `ParseInt32`, `ParseInt64`, `ParseUInt32`, `ParseUInt64`, `ParseDouble` from `utilstrencodings.h` for number parsing - - *Rationale*: These functions do overflow checking, and avoid pesky locale issues + - *Rationale*: These functions do overflow checking, and avoid pesky locale issues. + +- Avoid using locale dependent functions if possible. You can use the provided + [`lint-locale-dependence.sh`](/contrib/devtools/lint-locale-dependence.sh) + to check for accidental use of locale dependent functions. + + - *Rationale*: Unnecessary locale dependence can cause bugs that are very tricky to isolate and fix. + + - These functions are known to be locale dependent: + `alphasort`, `asctime`, `asprintf`, `atof`, `atoi`, `atol`, `atoll`, `atoq`, + `btowc`, `ctime`, `dprintf`, `fgetwc`, `fgetws`, `fprintf`, `fputwc`, + `fputws`, `fscanf`, `fwprintf`, `getdate`, `getwc`, `getwchar`, `isalnum`, + `isalpha`, `isblank`, `iscntrl`, `isdigit`, `isgraph`, `islower`, `isprint`, + `ispunct`, `isspace`, `isupper`, `iswalnum`, `iswalpha`, `iswblank`, + `iswcntrl`, `iswctype`, `iswdigit`, `iswgraph`, `iswlower`, `iswprint`, + `iswpunct`, `iswspace`, `iswupper`, `iswxdigit`, `isxdigit`, `mblen`, + `mbrlen`, `mbrtowc`, `mbsinit`, `mbsnrtowcs`, `mbsrtowcs`, `mbstowcs`, + `mbtowc`, `mktime`, `putwc`, `putwchar`, `scanf`, `snprintf`, `sprintf`, + `sscanf`, `stoi`, `stol`, `stoll`, `strcasecmp`, `strcasestr`, `strcoll`, + `strfmon`, `strftime`, `strncasecmp`, `strptime`, `strtod`, `strtof`, + `strtoimax`, `strtol`, `strtold`, `strtoll`, `strtoq`, `strtoul`, + `strtoull`, `strtoumax`, `strtouq`, `strxfrm`, `swprintf`, `tolower`, + `toupper`, `towctrans`, `towlower`, `towupper`, `ungetwc`, `vasprintf`, + `vdprintf`, `versionsort`, `vfprintf`, `vfscanf`, `vfwprintf`, `vprintf`, + `vscanf`, `vsnprintf`, `vsprintf`, `vsscanf`, `vswprintf`, `vwprintf`, + `wcrtomb`, `wcscasecmp`, `wcscoll`, `wcsftime`, `wcsncasecmp`, `wcsnrtombs`, + `wcsrtombs`, `wcstod`, `wcstof`, `wcstoimax`, `wcstol`, `wcstold`, + `wcstoll`, `wcstombs`, `wcstoul`, `wcstoull`, `wcstoumax`, `wcswidth`, + `wcsxfrm`, `wctob`, `wctomb`, `wctrans`, `wctype`, `wcwidth`, `wprintf` - For `strprintf`, `LogPrint`, `LogPrintf` formatting characters don't need size specifiers @@ -567,6 +595,12 @@ Source code organization - *Rationale*: Shorter and simpler header files are easier to read, and reduce compile time +- Use only the lowercase alphanumerics (`a-z0-9`), underscore (`_`) and hyphen (`-`) in source code filenames. + + - *Rationale*: `grep`:ing and auto-completing filenames is easier when using a consistent + naming pattern. Potential problems when building on case-insensitive filesystems are + avoided when using only lowercase characters in source code filenames. + - Every `.cpp` and `.h` file should `#include` every header file it directly uses classes, functions or other definitions from, even if those headers are already included indirectly through other headers. @@ -594,8 +628,8 @@ namespace { - *Rationale*: Avoids confusion about the namespace context -- Prefer `#include <primitives/transaction.h>` bracket syntax instead of - `#include "primitives/transactions.h"` quote syntax when possible. +- Use `#include <primitives/transaction.h>` bracket syntax instead of + `#include "primitives/transactions.h"` quote syntax. - *Rationale*: Bracket syntax is less ambiguous because the preprocessor searches a fixed list of include directories without taking location of the @@ -647,7 +681,7 @@ Others are external projects without a tight relationship with our project. Cha be sent upstream but bugfixes may also be prudent to PR against Bitcoin Core so that they can be integrated quickly. Cosmetic changes should be purely taken upstream. -There is a tool in contrib/devtools/git-subtree-check.sh to check a subtree directory for consistency with +There is a tool in `test/lint/git-subtree-check.sh` to check a subtree directory for consistency with its upstream repository. Current subtrees include: @@ -778,7 +812,7 @@ To create a scripted-diff: - `-BEGIN VERIFY SCRIPT-` - `-END VERIFY SCRIPT-` -The scripted-diff is verified by the tool `contrib/devtools/commit-script-check.sh` +The scripted-diff is verified by the tool `test/lint/commit-script-check.sh` 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 2eac7ed664..5657b1e6cb 100644 --- a/doc/files.md +++ b/doc/files.md @@ -10,6 +10,7 @@ * 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 diff --git a/doc/init.md b/doc/init.md index ffd13ae1f9..d04f7d186a 100644 --- a/doc/init.md +++ b/doc/init.md @@ -15,7 +15,7 @@ Service User All three Linux startup configurations assume the existence of a "bitcoin" user and group. They must be created before attempting to use these scripts. -The OS X configuration assumes bitcoind will be set up for the current user. +The macOS configuration assumes bitcoind will be set up for the current user. Configuration --------------------------------- @@ -65,7 +65,7 @@ reasons to make the configuration file and data directory only readable by the bitcoin user and group. Access to bitcoin-cli and other bitcoind rpc clients can then be controlled by group membership. -### Mac OS X +### macOS Binary: `/usr/local/bin/bitcoind` Configuration file: `~/Library/Application Support/Bitcoin/bitcoin.conf` @@ -111,7 +111,7 @@ Using this script, you can adjust the path and flags to the bitcoind program by setting the BITCOIND and FLAGS environment variables in the file /etc/sysconfig/bitcoind. You can also use the DAEMONOPTS environment variable here. -### Mac OS X +### macOS Copy org.bitcoin.bitcoind.plist into ~/Library/LaunchAgents. Load the launch agent by running `launchctl load ~/Library/LaunchAgents/org.bitcoin.bitcoind.plist`. diff --git a/doc/release-notes-pr10267.md b/doc/release-notes-pr10267.md new file mode 100644 index 0000000000..7e1967daf0 --- /dev/null +++ b/doc/release-notes-pr10267.md @@ -0,0 +1,13 @@ +Changed command-line 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`. diff --git a/doc/release-notes-pr10740.md b/doc/release-notes-pr10740.md new file mode 100644 index 0000000000..a2eb8cd837 --- /dev/null +++ b/doc/release-notes-pr10740.md @@ -0,0 +1,10 @@ +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. diff --git a/doc/release-notes-pr12892.md b/doc/release-notes-pr12892.md index 8105eca5c0..f4a95bd40f 100644 --- a/doc/release-notes-pr12892.md +++ b/doc/release-notes-pr12892.md @@ -18,7 +18,7 @@ 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` | `getlabeladdress` | `getlabeladdress` throws an error by default if the label does not already exist, but provides a `force` option for compatibility with existing applications. | +| `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. | diff --git a/doc/release-notes-pr12924.md b/doc/release-notes-pr12924.md new file mode 100644 index 0000000000..92e7e23217 --- /dev/null +++ b/doc/release-notes-pr12924.md @@ -0,0 +1,12 @@ +RPC changes +------------ + +### Low-level changes + +- 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. + +Other API changes +----------------- + +- The `inactivehdmaster` property in the `dumpwallet` output has been corrected to `inactivehdseed` diff --git a/doc/release-notes-pr13033.md b/doc/release-notes-pr13033.md new file mode 100644 index 0000000000..3ab4a984db --- /dev/null +++ b/doc/release-notes-pr13033.md @@ -0,0 +1,11 @@ +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. diff --git a/doc/release-notes.md b/doc/release-notes.md index d4c5b03449..b7eaa6eaf4 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -56,6 +56,11 @@ frequently tested on them. Notable changes =============== +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. + RPC changes ------------ @@ -70,6 +75,12 @@ RPC changes /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. External wallet files --------------------- @@ -100,11 +111,31 @@ Low-level RPC changes 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. ### 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`. + Miner block size removed ------------------------ diff --git a/doc/release-notes/release-notes-0.16.1.md b/doc/release-notes/release-notes-0.16.1.md new file mode 100644 index 0000000000..d99361ae1d --- /dev/null +++ b/doc/release-notes/release-notes-0.16.1.md @@ -0,0 +1,145 @@ +Bitcoin Core version 0.16.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.16.1/> + +This is a new minor version release, with various bugfixes +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). + +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 or higher. Upgrading +directly from 0.7.x and earlier without re-downloading the blockchain is not supported. +However, as usual, old wallet versions are still supported. + +Downgrading warning +------------------- + +Wallets created in 0.16 and later are not compatible with versions prior to 0.16 +and will not work if you try to use newly created wallets in older versions. Existing +wallets that were created with older versions are not affected by this. + +Compatibility +============== + +Bitcoin Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.8+, and Windows Vista and later. Windows XP is not supported. + +Bitcoin Core should also work on most other Unix-like systems but is not +frequently tested on them. + +Notable changes +=============== + +Miner block size removed +------------------------ + +The `-blockmaxsize` option for miners to limit their blocks' sizes was +deprecated in version 0.15.1, and has now been removed. Miners should use the +`-blockmaxweight` option if they want to limit the weight of their blocks' +weights. + +0.16.1 change log +------------------ + +### Policy +- #11423 `d353dd1` [Policy] Several transaction standardness rules (jl2012) + +### Mining +- #12756 `e802c22` [config] Remove blockmaxsize option (jnewbery) + +### Block and transaction handling +- #13199 `c71e535` Bugfix: ensure consistency of m_failed_blocks after reconsiderblock (sdaftuar) +- #13023 `bb79aaf` Fix some concurrency issues in ActivateBestChain() (skeees) + +### P2P protocol and network code +- #12626 `f60e84d` Limit the number of IPs addrman learns from each DNS seeder (EthanHeilman) + +### Wallet +- #13265 `5d8de76` Exit SyncMetaData if there are no transactions to sync (laanwj) +- #13030 `5ff571e` Fix zapwallettxes/multiwallet interaction. (jnewbery) + +### GUI +- #12999 `1720eb3` Show the Window when double clicking the taskbar icon (ken2812221) +- #12650 `f118a7a` Fix issue: "default port not shown correctly in settings dialog" (251Labs) +- #13251 `ea487f9` Rephrase Bech32 checkbox texts, and enable it with legacy address default (fanquake) + +### Build system +- #12474 `b0f692f` Allow depends system to support armv7l (hkjn) +- #12585 `72a3290` depends: Switch to downloading expat from GitHub (fanquake) +- #12648 `46ca8f3` test: Update trusted git root (MarcoFalke) +- #11995 `686cb86` depends: Fix Qt build with Xcode 9 (fanquake) +- #12636 `845838c` backport: #11995 Fix Qt build with Xcode 9 (fanquake) +- #12946 `e055bc0` depends: Fix Qt build with XCode 9.3 (fanquake) +- #12998 `7847b92` Default to defining endian-conversion DECLs in compat w/o config (TheBlueMatt) + +### Tests and QA +- #12447 `01f931b` Add missing signal.h header (laanwj) +- #12545 `1286f3e` Use wait_until to ensure ping goes out (Empact) +- #12804 `4bdb0ce` Fix intermittent rpc_net.py failure. (jnewbery) +- #12553 `0e98f96` Prefer wait_until over polling with time.sleep (Empact) +- #12486 `cfebd40` Round target fee to 8 decimals in assert_fee_amount (kallewoof) +- #12843 `df38b13` Test starting bitcoind with -h and -version (jnewbery) +- #12475 `41c29f6` Fix python TypeError in script.py (MarcoFalke) +- #12638 `0a76ed2` Cache only chain and wallet for regtest datadir (MarcoFalke) +- #12902 `7460945` Handle potential cookie race when starting node (sdaftuar) +- #12904 `6c26df0` Ensure bitcoind processes are cleaned up when tests end (sdaftuar) +- #13049 `9ea62a3` Backports (MarcoFalke) +- #13201 `b8aacd6` Handle disconnect_node race (sdaftuar) + +### Miscellaneous +- #12518 `a17fecf` Bump leveldb subtree (MarcoFalke) +- #12442 `f3b8d85` devtools: Exclude patches from lint-whitespace (MarcoFalke) +- #12988 `acdf433` Hold cs_main while calling UpdatedBlockTip() signal (skeees) +- #12985 `0684cf9` Windows: Avoid launching as admin when NSIS installer ends. (JeremyRand) + +### Documentation +- #12637 `60086dd` backport: #12556 fix version typo in getpeerinfo RPC call help (fanquake) +- #13184 `4087dd0` RPC Docs: `gettxout*`: clarify bestblock and unspent counts (harding) +- #13246 `6de7543` Bump to Ubuntu Bionic 18.04 in build-windows.md (ken2812221) +- #12556 `e730b82` Fix version typo in getpeerinfo RPC call help (tamasblummer) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- 251 +- Ben Woosley +- Chun Kuan Lee +- David A. Harding +- e0 +- fanquake +- Henrik Jonsson +- JeremyRand +- Jesse Cohen +- John Newbery +- Johnson Lau +- Karl-Johan Alm +- Luke Dashjr +- MarcoFalke +- Matt Corallo +- Pieter Wuille +- Suhas Daftuar +- Tamas Blummer +- 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 a988c74ba5..f3fc4bdfdc 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -89,7 +89,7 @@ Ensure gitian-builder is up-to-date: wget -P inputs http://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz popd -Create the OS X SDK tarball, see the [OS X readme](README_osx.md) for details, and copy it into the inputs directory. +Create the macOS SDK tarball, see the [macOS readme](README_osx.md) for details, and copy it into the inputs directory. ### Optional: Seed the Gitian sources cache and offline git repositories @@ -111,7 +111,7 @@ NOTE: Offline builds must use the --url flag to ensure Gitian fetches only from The gbuild invocations below <b>DO NOT DO THIS</b> by default. -### Build and sign Bitcoin Core for Linux, Windows, and OS X: +### Build and sign Bitcoin Core for Linux, Windows, and macOS: pushd ./gitian-builder ./bin/gbuild --num-make 2 --memory 3000 --commit bitcoin=v${VERSION} ../bitcoin/contrib/gitian-descriptors/gitian-linux.yml @@ -134,7 +134,7 @@ Build output expected: 1. source tarball (`bitcoin-${VERSION}.tar.gz`) 2. linux 32-bit and 64-bit dist tarballs (`bitcoin-${VERSION}-linux[32|64].tar.gz`) 3. windows 32-bit and 64-bit unsigned installers and dist zips (`bitcoin-${VERSION}-win[32|64]-setup-unsigned.exe`, `bitcoin-${VERSION}-win[32|64].zip`) - 4. OS X unsigned installer and dist tarball (`bitcoin-${VERSION}-osx-unsigned.dmg`, `bitcoin-${VERSION}-osx64.tar.gz`) + 4. macOS unsigned installer and dist tarball (`bitcoin-${VERSION}-osx-unsigned.dmg`, `bitcoin-${VERSION}-osx64.tar.gz`) 5. Gitian signatures (in `gitian.sigs/${VERSION}-<linux|{win,osx}-unsigned>/(your Gitian key)/`) ### Verify other gitian builders signatures to your own. (Optional) @@ -157,17 +157,17 @@ Commit your signature to gitian.sigs: git add ${VERSION}-linux/"${SIGNER}" git add ${VERSION}-win-unsigned/"${SIGNER}" git add ${VERSION}-osx-unsigned/"${SIGNER}" - git commit -a + git commit -m "Add ${VERSION} unsigned sigs for ${SIGNER}" git push # Assuming you can push to the gitian.sigs tree popd -Codesigner only: Create Windows/OS X detached signatures: +Codesigner only: Create Windows/macOS detached signatures: - Only one person handles codesigning. Everyone else should skip to the next step. -- Only once the Windows/OS X builds each have 3 matching signatures may they be signed with their respective release keys. +- Only once the Windows/macOS builds each have 3 matching signatures may they be signed with their respective release keys. -Codesigner only: Sign the osx binary: +Codesigner only: Sign the macOS binary: - transfer bitcoin-osx-unsigned.tar.gz to osx for signing + transfer bitcoin-osx-unsigned.tar.gz to macOS for signing tar xf bitcoin-osx-unsigned.tar.gz ./detached-sig-create.sh -s "Key ID" Enter the keychain password and authorize the signature @@ -192,12 +192,12 @@ Codesigner only: Commit the detached codesign payloads: git tag -s v${VERSION} HEAD git push the current branch and new tag -Non-codesigners: wait for Windows/OS X detached signatures: +Non-codesigners: wait for Windows/macOS detached signatures: -- Once the Windows/OS X builds each have 3 matching signatures, they will be signed with their respective release keys. +- Once the Windows/macOS builds each have 3 matching signatures, they will be signed with their respective release keys. - Detached signatures will then be committed to the [bitcoin-detached-sigs](https://github.com/bitcoin-core/bitcoin-detached-sigs) repository, which can be combined with the unsigned apps to create signed binaries. -Create (and optionally verify) the signed OS X binary: +Create (and optionally verify) the signed macOS binary: pushd ./gitian-builder ./bin/gbuild -i --commit signature=v${VERSION} ../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -216,7 +216,7 @@ Create (and optionally verify) the signed Windows binaries: mv build/out/bitcoin-*win32-setup.exe ../bitcoin-${VERSION}-win32-setup.exe popd -Commit your signature for the signed OS X/Windows binaries: +Commit your signature for the signed macOS/Windows binaries: pushd gitian.sigs git add ${VERSION}-osx-signed/"${SIGNER}" @@ -276,14 +276,14 @@ bitcoin.org (see below for bitcoin.org update instructions). - Update bitcoin.org version - First, check to see if the Bitcoin.org maintainers have prepared a - release: https://github.com/bitcoin-dot-org/bitcoin.org/labels/Releases + release: https://github.com/bitcoin-dot-org/bitcoin.org/labels/Core - If they have, it will have previously failed their Travis CI checks because the final release files weren't uploaded. Trigger a Travis CI rebuild---if it passes, merge. - If they have not prepared a release, follow the Bitcoin.org release - instructions: https://github.com/bitcoin-dot-org/bitcoin.org#release-notes + instructions: https://github.com/bitcoin-dot-org/bitcoin.org/blob/master/docs/adding-events-release-notes-and-alerts.md#release-notes - After the pull request is merged, the website will automatically show the newest version within 15 minutes, as well as update the OS download links. Ping @saivann/@harding (saivann/harding on Freenode) in case anything goes wrong diff --git a/doc/tor.md b/doc/tor.md index 931c83abdd..f0f98b7d12 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -1,5 +1,4 @@ -TOR SUPPORT IN BITCOIN -====================== +# TOR SUPPORT IN BITCOIN It is possible to run Bitcoin as a Tor hidden service, and connect to such services. @@ -7,8 +6,7 @@ The following directions assume you have a Tor proxy running on port 9050. Many configure Tor. -1. Run bitcoin behind a Tor proxy ---------------------------------- +## 1. Run bitcoin behind a Tor proxy The first step is running Bitcoin behind a Tor proxy. This will already make all outgoing connections be anonymized, but more is possible. @@ -34,12 +32,12 @@ In a typical situation, this suffices to run behind a Tor proxy: ./bitcoind -proxy=127.0.0.1:9050 -2. Run a bitcoin hidden server ------------------------------- +## 2. Run a bitcoin hidden server If you configure your Tor system accordingly, it is possible to make your node also reachable from the Tor network. Add these lines to your /etc/tor/torrc (or equivalent -config file): +config file): *Needed for Tor version 0.2.7.0 and older versions of Tor only. For newer +versions of Tor see [Section 3](#3-automatically-listen-on-tor).* HiddenServiceDir /var/lib/tor/bitcoin-service/ HiddenServicePort 8333 127.0.0.1:8333 @@ -88,8 +86,7 @@ for normal IPv4/IPv6 communication, use: ./bitcoind -onion=127.0.0.1:9050 -externalip=57qr3yd1nyntf5k.onion -discover -3. Automatically listen on Tor --------------------------------- +## 3. Automatically listen on Tor Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket API, to create and destroy 'ephemeral' hidden services programmatically. @@ -115,8 +112,7 @@ which has the appropriate permissions. An alternative authentication method is t of the `-torpassword` flag and a `hash-password` which can be enabled and specified in Tor configuration. -4. Privacy recommendations ---------------------------- +## 4. Privacy recommendations - Do not add anything but bitcoin ports to the hidden service created in section 2. If you run a web service too, create a new hidden service for that. diff --git a/doc/translation_process.md b/doc/translation_process.md index 5a9c59914e..022d7bb00b 100644 --- a/doc/translation_process.md +++ b/doc/translation_process.md @@ -46,9 +46,7 @@ Visit the [Transifex Signup](https://www.transifex.com/signup/) page to create a You can find the Bitcoin translation project at [https://www.transifex.com/projects/p/bitcoin/](https://www.transifex.com/projects/p/bitcoin/). ### Installing the Transifex client command-line tool -The client it used to fetch updated translations. If you are having problems, or need more details, see [http://docs.transifex.com/developer/client/setup](http://docs.transifex.com/developer/client/setup) - -**For Linux and Mac** +The client is used to fetch updated translations. If you are having problems, or need more details, see [https://docs.transifex.com/client/installing-the-client](https://docs.transifex.com/client/installing-the-client) `pip install transifex-client` @@ -64,10 +62,6 @@ token = username = USERNAME ``` -**For Windows** - -Please see [http://docs.transifex.com/developer/client/setup#windows](http://docs.transifex.com/developer/client/setup#windows) for details on installation. - The Transifex Bitcoin project config file is included as part of the repo. It can be found at `.tx/config`, however you shouldn’t need change anything. ### Synchronising translations diff --git a/doc/translation_strings_policy.md b/doc/translation_strings_policy.md index b95259cdc9..737d11f045 100644 --- a/doc/translation_strings_policy.md +++ b/doc/translation_strings_policy.md @@ -21,21 +21,11 @@ On a high level, these strings are to be translated: - GUI strings, anything that appears in a dialog or window -- Command-line option documentation - ### GUI strings Anything that appears to the user in the GUI is to be translated. This includes labels, menu items, button texts, tooltips and window titles. This includes messages passed to the GUI through the UI interface through `InitMessage`, `ThreadSafeMessageBox` or `ShowProgress`. -### Command-line options - -Documentation for the command line options in the output of `--help` should be translated as well. - -Make sure that default values do not end up in the string, but use string formatting like `strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), 100)`. Putting default values in strings has led to accidental translations in the past, and forces the string to be retranslated every time the value changes. - -Do not translate messages that are only shown to developers, such as those that only appear when `--help-debug` is used. - General recommendations ------------------------ diff --git a/share/genbuild.sh b/share/genbuild.sh index 419e0da0fd..38c9ba176c 100755 --- a/share/genbuild.sh +++ b/share/genbuild.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C if [ $# -gt 1 ]; then cd "$2" || exit 1 fi diff --git a/share/qt/extract_strings_qt.py b/share/qt/extract_strings_qt.py index e8f0820ca8..e908071321 100755 --- a/share/qt/extract_strings_qt.py +++ b/share/qt/extract_strings_qt.py @@ -63,7 +63,7 @@ child = Popen([XGETTEXT,'--output=-','-n','--keyword=_'] + files, stdout=PIPE) messages = parse_po(out.decode('utf-8')) -f = open(OUT_CPP, 'w') +f = open(OUT_CPP, 'w', encoding="utf8") f.write(""" #include <QtGlobal> diff --git a/share/rpcauth/README.md b/share/rpcauth/README.md index 389278a125..20d16f0a97 100644 --- a/share/rpcauth/README.md +++ b/share/rpcauth/README.md @@ -8,3 +8,7 @@ Create login credentials for a JSON-RPC user. Usage: ./rpcauth.py <username> + +in which case the script will generate a password. To specify a custom password do: + + ./rpcauth.py <username> <password> diff --git a/share/rpcauth/rpcauth.py b/share/rpcauth/rpcauth.py index f9b9787514..566c55aba9 100755 --- a/share/rpcauth/rpcauth.py +++ b/share/rpcauth/rpcauth.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2017 The Bitcoin Core developers -# Distributed under the MIT 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. import sys @@ -9,26 +9,39 @@ from random import SystemRandom import base64 import hmac -if len(sys.argv) < 2: - sys.stderr.write('Please include username as an argument.\n') - sys.exit(0) +def generate_salt(): + # This uses os.urandom() underneath + cryptogen = SystemRandom() -username = sys.argv[1] + # Create 16 byte hex salt + salt_sequence = [cryptogen.randrange(256) for _ in range(16)] + return ''.join([format(r, 'x') for r in salt_sequence]) -#This uses os.urandom() underneath -cryptogen = SystemRandom() +def generate_password(): + """Create 32 byte b64 password""" + return base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8') -#Create 16 byte hex salt -salt_sequence = [cryptogen.randrange(256) for i in range(16)] -hexseq = list(map(hex, salt_sequence)) -salt = "".join([x[2:] for x in hexseq]) +def password_to_hmac(salt, password): + m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') + return m.hexdigest() -#Create 32 byte b64 password -password = base64.urlsafe_b64encode(os.urandom(32)).decode("utf-8") +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) -m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), "SHA256") -result = m.hexdigest() + username = sys.argv[1] -print("String to be appended to bitcoin.conf:") -print("rpcauth="+username+":"+salt+"$"+result) -print("Your password:\n"+password) + salt = generate_salt() + if len(sys.argv) > 2: + password = sys.argv[2] + else: + password = generate_password() + password_hmac = password_to_hmac(salt, 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)) + +if __name__ == '__main__': + main() diff --git a/src/Makefile.am b/src/Makefile.am index 521687eb45..2603dbae56 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,8 +5,8 @@ DIST_SUBDIRS = secp256k1 univalue AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) -AM_CXXFLAGS = $(HARDENED_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) -AM_CPPFLAGS = $(HARDENED_CPPFLAGS) +AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) +AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS) AM_LIBTOOLFLAGS = --preserve-dup-deps EXTRA_LIBRARIES = @@ -29,7 +29,7 @@ LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a -LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a +LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.a LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la @@ -43,7 +43,21 @@ if ENABLE_WALLET LIBBITCOIN_WALLET=libbitcoin_wallet.a endif -$(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) +LIBBITCOIN_CRYPTO= $(LIBBITCOIN_CRYPTO_BASE) +if ENABLE_SSE41 +LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.a +LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SSE41) +endif +if ENABLE_AVX2 +LIBBITCOIN_CRYPTO_AVX2 = crypto/libbitcoin_crypto_avx2.a +LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_AVX2) +endif +if ENABLE_SHANI +LIBBITCOIN_CRYPTO_SHANI = crypto/libbitcoin_crypto_shani.a +LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SHANI) +endif + +$(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) # Make is not made aware of per-object dependencies to avoid limiting building parallelization @@ -103,6 +117,8 @@ BITCOIN_CORE_H = \ fs.h \ httprpc.h \ httpserver.h \ + index/base.h \ + index/txindex.h \ indirectmap.h \ init.h \ interfaces/handler.h \ @@ -123,6 +139,7 @@ BITCOIN_CORE_H = \ netbase.h \ netmessagemaker.h \ noui.h \ + outputtype.h \ policy/feerate.h \ policy/fees.h \ policy/policy.h \ @@ -136,7 +153,6 @@ BITCOIN_CORE_H = \ rpc/client.h \ rpc/mining.h \ rpc/protocol.h \ - rpc/safemode.h \ rpc/server.h \ rpc/rawtransaction.h \ rpc/register.h \ @@ -146,6 +162,7 @@ BITCOIN_CORE_H = \ script/sigcache.h \ script/sign.h \ script/standard.h \ + shutdown.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ @@ -162,6 +179,7 @@ BITCOIN_CORE_H = \ ui_interface.h \ undo.h \ util.h \ + utilmemory.h \ utilmoneystr.h \ utiltime.h \ validation.h \ @@ -182,7 +200,8 @@ BITCOIN_CORE_H = \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ zmq/zmqnotificationinterface.h \ - zmq/zmqpublishnotifier.h + zmq/zmqpublishnotifier.h \ + zmq/zmqrpc.h obj/build.h: FORCE @@ -204,6 +223,8 @@ libbitcoin_server_a_SOURCES = \ consensus/tx_verify.cpp \ httprpc.cpp \ httpserver.cpp \ + index/base.cpp \ + index/txindex.cpp \ init.cpp \ dbwrapper.cpp \ merkleblock.cpp \ @@ -211,6 +232,7 @@ libbitcoin_server_a_SOURCES = \ net.cpp \ net_processing.cpp \ noui.cpp \ + outputtype.cpp \ policy/fees.cpp \ policy/policy.cpp \ policy/rbf.cpp \ @@ -221,9 +243,10 @@ libbitcoin_server_a_SOURCES = \ rpc/misc.cpp \ rpc/net.cpp \ rpc/rawtransaction.cpp \ - rpc/safemode.cpp \ rpc/server.cpp \ + rpc/util.cpp \ script/sigcache.cpp \ + shutdown.cpp \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ @@ -240,7 +263,8 @@ libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ zmq/zmqabstractnotifier.cpp \ zmq/zmqnotificationinterface.cpp \ - zmq/zmqpublishnotifier.cpp + zmq/zmqpublishnotifier.cpp \ + zmq/zmqrpc.cpp endif @@ -264,9 +288,9 @@ libbitcoin_wallet_a_SOURCES = \ $(BITCOIN_CORE_H) # crypto primitives library -crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_a_SOURCES = \ +crypto_libbitcoin_crypto_base_a_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ crypto/chacha20.h \ @@ -286,9 +310,27 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/sha512.h if USE_ASM -crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp +crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp endif +crypto_libbitcoin_crypto_sse41_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +crypto_libbitcoin_crypto_sse41_a_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_sse41_a_CXXFLAGS += $(SSE41_CXXFLAGS) +crypto_libbitcoin_crypto_sse41_a_CPPFLAGS += -DENABLE_SSE41 +crypto_libbitcoin_crypto_sse41_a_SOURCES = crypto/sha256_sse41.cpp + +crypto_libbitcoin_crypto_avx2_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +crypto_libbitcoin_crypto_avx2_a_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_avx2_a_CXXFLAGS += $(AVX2_CXXFLAGS) +crypto_libbitcoin_crypto_avx2_a_CPPFLAGS += -DENABLE_AVX2 +crypto_libbitcoin_crypto_avx2_a_SOURCES = crypto/sha256_avx2.cpp + +crypto_libbitcoin_crypto_shani_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +crypto_libbitcoin_crypto_shani_a_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_shani_a_CXXFLAGS += $(SHANI_CXXFLAGS) +crypto_libbitcoin_crypto_shani_a_CPPFLAGS += -DENABLE_SHANI +crypto_libbitcoin_crypto_shani_a_SOURCES = crypto/sha256_shani.cpp + # consensus: shared between all executables that validate any consensus rules. libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_consensus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -368,7 +410,6 @@ libbitcoin_util_a_SOURCES = \ logging.cpp \ random.cpp \ rpc/protocol.cpp \ - rpc/util.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ @@ -380,6 +421,7 @@ libbitcoin_util_a_SOURCES = \ if GLIBC_BACK_COMPAT libbitcoin_util_a_SOURCES += compat/glibc_compat.cpp +AM_LDFLAGS += $(COMPAT_LDFLAGS) endif # cli: shared between bitcoin-cli and bitcoin-qt @@ -461,7 +503,7 @@ bitcoin_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) # bitcoinconsensus library # if BUILD_BITCOIN_LIBS include_HEADERS = script/bitcoinconsensus.h -libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) +libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) if GLIBC_BACK_COMPAT libbitcoinconsensus_la_SOURCES += compat/glibc_compat.cpp diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 3306dcf598..f5293585a0 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -15,15 +15,18 @@ bench_bench_bitcoin_SOURCES = \ bench/bench_bitcoin.cpp \ bench/bench.cpp \ bench/bench.h \ + bench/block_assemble.cpp \ bench/checkblock.cpp \ bench/checkqueue.cpp \ - bench/Examples.cpp \ + bench/examples.cpp \ bench/rollingbloom.cpp \ bench/crypto_hash.cpp \ bench/ccoins_caching.cpp \ + bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ bench/verify_script.cpp \ bench/base58.cpp \ + bench/bech32.cpp \ bench/lockedpool.cpp \ bench/prevector.cpp @@ -32,8 +35,8 @@ nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES) bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CLFAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ bench_bench_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bench_bench_bitcoin_LDADD = \ - $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 38eb12ce0d..a84a11ac45 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -278,6 +278,7 @@ RES_ICONS = \ qt/res/icons/network_disabled.png \ qt/res/icons/open.png \ qt/res/icons/overview.png \ + qt/res/icons/proxy.png \ qt/res/icons/quit.png \ qt/res/icons/receive.png \ qt/res/icons/remove.png \ diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 1279152198..4b14212b2e 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -12,14 +12,17 @@ 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 TEST_QT_H = \ + qt/test/addressbooktests.h \ qt/test/compattests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ + qt/test/util.h \ qt/test/paymentrequestdata.h \ qt/test/paymentservertests.h \ qt/test/wallettests.h @@ -38,11 +41,13 @@ qt_test_test_bitcoin_qt_SOURCES = \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ + qt/test/util.cpp \ $(TEST_QT_H) \ $(TEST_BITCOIN_CPP) \ $(TEST_BITCOIN_H) 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 diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c4f18bb371..02f2063504 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -46,7 +46,7 @@ BITCOIN_TESTS =\ test/compress_tests.cpp \ test/crypto_tests.cpp \ test/cuckoocache_tests.cpp \ - test/DoS_tests.cpp \ + test/denialofservice_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ test/key_io_tests.cpp \ @@ -71,7 +71,7 @@ BITCOIN_TESTS =\ test/rpc_tests.cpp \ test/sanity_tests.cpp \ test/scheduler_tests.cpp \ - test/script_P2SH_tests.cpp \ + test/script_p2sh_tests.cpp \ test/script_tests.cpp \ test/script_standard_tests.cpp \ test/scriptnum_tests.cpp \ @@ -83,15 +83,18 @@ BITCOIN_TESTS =\ test/timedata_tests.cpp \ test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ + test/txindex_tests.cpp \ test/txvalidation_tests.cpp \ test/txvalidationcache_tests.cpp \ - test/versionbits_tests.cpp \ test/uint256_tests.cpp \ - test/util_tests.cpp + test/util_tests.cpp \ + test/validation_block_tests.cpp \ + test/versionbits_tests.cpp if ENABLE_WALLET BITCOIN_TESTS += \ wallet/test/accounting_tests.cpp \ + wallet/test/psbt_wallet_tests.cpp \ wallet/test/wallet_tests.cpp \ wallet/test/wallet_crypto_tests.cpp \ wallet/test/coinselector_tests.cpp @@ -107,6 +110,7 @@ test_test_bitcoin_LDADD = if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif + test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ $(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) @@ -132,6 +136,9 @@ test_test_bitcoin_fuzzy_LDADD = \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ + $(LIBBITCOIN_CRYPTO_SSE41) \ + $(LIBBITCOIN_CRYPTO_AVX2) \ + $(LIBBITCOIN_CRYPTO_SHANI) \ $(LIBSECP256K1) test_test_bitcoin_fuzzy_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) @@ -156,14 +163,16 @@ bitcoin_test_clean : FORCE check-local: $(BITCOIN_TESTS:.cpp=.cpp.test) @echo "Running test/util/bitcoin-util-test.py..." $(PYTHON) $(top_builddir)/test/util/bitcoin-util-test.py + @echo "Running test/util/rpcauth-test.py..." + $(PYTHON) $(top_builddir)/test/util/rpcauth-test.py $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check if EMBEDDED_UNIVALUE $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue check endif %.cpp.test: %.cpp - @echo Running tests: `cat $< | grep "BOOST_FIXTURE_TEST_SUITE(\|BOOST_AUTO_TEST_SUITE(" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< - $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep "BOOST_FIXTURE_TEST_SUITE(\|BOOST_AUTO_TEST_SUITE(" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) + @echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< + $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) %.json.h: %.json @$(MKDIR_P) $(@D) diff --git a/src/arith_uint256.h b/src/arith_uint256.h index 3f4cc8c2bf..e4c7575e2d 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -64,14 +64,6 @@ public: explicit base_uint(const std::string& str); - bool operator!() const - { - for (int i = 0; i < WIDTH; i++) - if (pn[i] != 0) - return false; - return true; - } - const base_uint operator~() const { base_uint ret; diff --git a/src/bech32.cpp b/src/bech32.cpp index 274782e467..c55f22b9b7 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -160,9 +160,9 @@ std::pair<std::string, data> Decode(const std::string& str) { bool lower = false, upper = false; for (size_t i = 0; i < str.size(); ++i) { unsigned char c = str[i]; - if (c < 33 || c > 126) return {}; if (c >= 'a' && c <= 'z') lower = true; - if (c >= 'A' && c <= 'Z') upper = true; + else if (c >= 'A' && c <= 'Z') upper = true; + else if (c < 33 || c > 126) return {}; } if (lower && upper) return {}; size_t pos = str.rfind('1'); @@ -172,7 +172,8 @@ std::pair<std::string, data> Decode(const std::string& str) { data values(str.size() - 1 - pos); for (size_t i = 0; i < str.size() - 1 - pos; ++i) { unsigned char c = str[i + pos + 1]; - int8_t rev = (c < 33 || c > 126) ? -1 : CHARSET_REV[c]; + int8_t rev = CHARSET_REV[c]; + if (rev == -1) { return {}; } diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp new file mode 100644 index 0000000000..8b80e17391 --- /dev/null +++ b/src/bench/bech32.cpp @@ -0,0 +1,37 @@ +// 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 <bench/bench.h> + +#include <validation.h> +#include <bech32.h> +#include <utilstrencodings.h> + +#include <vector> +#include <string> + + +static void Bech32Encode(benchmark::State& state) +{ + std::vector<uint8_t> v = ParseHex("c97f5a67ec381b760aeaf67573bc164845ff39a3bb26a1cee401ac67243b48db"); + std::vector<unsigned char> tmp = {0}; + tmp.reserve(1 + 32 * 8 / 5); + ConvertBits<8, 5, true>([&](unsigned char c) { tmp.push_back(c); }, v.begin(), v.end()); + while (state.KeepRunning()) { + bech32::Encode("bc", tmp); + } +} + + +static void Bech32Decode(benchmark::State& state) +{ + std::string addr = "bc1qkallence7tjawwvy0dwt4twc62qjgaw8f4vlhyd006d99f09"; + while (state.KeepRunning()) { + bech32::Decode(addr); + } +} + + +BENCHMARK(Bech32Encode, 800 * 1000); +BENCHMARK(Bech32Decode, 800 * 1000); diff --git a/src/bench/bench.h b/src/bench/bench.h index e15cb81869..5d131c2aa8 100644 --- a/src/bench/bench.h +++ b/src/bench/bench.h @@ -111,9 +111,9 @@ public: class ConsolePrinter : public Printer { public: - void header(); - void result(const State& state); - void footer(); + void header() override; + void result(const State& state) override; + void footer() override; }; // creates box plot with plotly.js @@ -121,9 +121,9 @@ class PlotlyPrinter : public Printer { public: PlotlyPrinter(std::string plotly_url, int64_t width, int64_t height); - void header(); - void result(const State& state); - void footer(); + void header() override; + void result(const State& state) override; + void footer() override; private: std::string m_plotly_url; diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 1d87883522..92befdd593 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -6,11 +6,10 @@ #include <crypto/sha256.h> #include <key.h> -#include <validation.h> -#include <util.h> #include <random.h> - -#include <boost/lexical_cast.hpp> +#include <util.h> +#include <utilstrencodings.h> +#include <validation.h> #include <memory> @@ -22,39 +21,64 @@ static const char* DEFAULT_PLOT_PLOTLYURL = "https://cdn.plot.ly/plotly-latest.m static const int64_t DEFAULT_PLOT_WIDTH = 1024; static const int64_t DEFAULT_PLOT_HEIGHT = 768; -int -main(int argc, char** argv) +static void SetupBenchArgs() { - gArgs.ParseParameters(argc, argv); + gArgs.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-list", "List benchmarks without executing them. Can be combined with -scaling and -filter", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-evals=<n>", strprintf("Number of measurement evaluations to perform. (default: %u)", DEFAULT_BENCH_EVALUATIONS), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-scaling=<n>", strprintf("Scaling factor for benchmark's runtime (default: %u)", DEFAULT_BENCH_SCALING), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-printer=(console|plot)", strprintf("Choose printer format. console: print data to console. plot: Print results as HTML graph (default: %s)", DEFAULT_BENCH_PRINTER), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-plotlyurl=<uri>", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-width=<x>", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-height=<x>", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), false, OptionsCategory::OPTIONS); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); +} + +static fs::path SetDataDir() +{ + fs::path ret = fs::temp_directory_path() / "bench_bitcoin" / fs::unique_path(); + fs::create_directories(ret); + gArgs.ForceSetArg("-datadir", ret.string()); + return ret; +} + +int main(int argc, char** argv) +{ + SetupBenchArgs(); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } if (HelpRequested(gArgs)) { - std::cout << HelpMessageGroup(_("Options:")) - << HelpMessageOpt("-?", _("Print this help message and exit")) - << HelpMessageOpt("-list", _("List benchmarks without executing them. Can be combined with -scaling and -filter")) - << HelpMessageOpt("-evals=<n>", strprintf(_("Number of measurement evaluations to perform. (default: %u)"), DEFAULT_BENCH_EVALUATIONS)) - << HelpMessageOpt("-filter=<regex>", strprintf(_("Regular expression filter to select benchmark by name (default: %s)"), DEFAULT_BENCH_FILTER)) - << HelpMessageOpt("-scaling=<n>", strprintf(_("Scaling factor for benchmark's runtime (default: %u)"), DEFAULT_BENCH_SCALING)) - << HelpMessageOpt("-printer=(console|plot)", strprintf(_("Choose printer format. console: print data to console. plot: Print results as HTML graph (default: %s)"), DEFAULT_BENCH_PRINTER)) - << HelpMessageOpt("-plot-plotlyurl=<uri>", strprintf(_("URL to use for plotly.js (default: %s)"), DEFAULT_PLOT_PLOTLYURL)) - << HelpMessageOpt("-plot-width=<x>", strprintf(_("Plot width in pixel (default: %u)"), DEFAULT_PLOT_WIDTH)) - << HelpMessageOpt("-plot-height=<x>", strprintf(_("Plot height in pixel (default: %u)"), DEFAULT_PLOT_HEIGHT)); - - return 0; + std::cout << gArgs.GetHelpMessage(); + + return EXIT_SUCCESS; } + // Set the datadir after parsing the bench options + const fs::path bench_datadir{SetDataDir()}; + SHA256AutoDetect(); RandomInit(); ECC_Start(); SetupEnvironment(); - fPrintToDebugLog = false; // don't want to write to debug.log file int64_t evaluations = gArgs.GetArg("-evals", DEFAULT_BENCH_EVALUATIONS); std::string regex_filter = gArgs.GetArg("-filter", DEFAULT_BENCH_FILTER); std::string scaling_str = gArgs.GetArg("-scaling", DEFAULT_BENCH_SCALING); bool is_list_only = gArgs.GetBoolArg("-list", false); - double scaling_factor = boost::lexical_cast<double>(scaling_str); - + double scaling_factor; + if (!ParseDouble(scaling_str, &scaling_factor)) { + fprintf(stderr, "Error parsing scaling factor as double: %s\n", scaling_str.c_str()); + return EXIT_FAILURE; + } std::unique_ptr<benchmark::Printer> printer(new benchmark::ConsolePrinter()); std::string printer_arg = gArgs.GetArg("-printer", DEFAULT_BENCH_PRINTER); @@ -67,5 +91,9 @@ main(int argc, char** argv) benchmark::BenchRunner::RunAll(*printer, evaluations, scaling_factor, regex_filter, is_list_only); + fs::remove_all(bench_datadir); + ECC_Stop(); + + return EXIT_SUCCESS; } diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp new file mode 100644 index 0000000000..36fa175a76 --- /dev/null +++ b/src/bench/block_assemble.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2011-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 <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 <utiltime.h> +#include <validation.h> +#include <validationinterface.h> + +#include <boost/thread.hpp> + +#include <list> +#include <vector> + +static std::shared_ptr<CBlock> PrepareBlock(const CScript& coinbase_scriptPubKey) +{ + auto block = std::make_shared<CBlock>( + BlockAssembler{Params()} + .CreateNewBlock(coinbase_scriptPubKey, /* fMineWitnessTx */ true) + ->block); + + block->nTime = ::chainActive.Tip()->GetMedianTimePast() + 1; + block->hashMerkleRoot = BlockMerkleRoot(*block); + + return block; +} + + +static CTxIn MineBlock(const CScript& coinbase_scriptPubKey) +{ + auto block = PrepareBlock(coinbase_scriptPubKey); + + while (!CheckProofOfWork(block->GetHash(), block->nBits, Params().GetConsensus())) { + assert(++block->nNonce); + } + + bool processed{ProcessNewBlock(Params(), block, true, nullptr)}; + assert(processed); + + return CTxIn{block->vtx[0]->GetHash(), 0}; +} + + +static void AssembleBlock(benchmark::State& state) +{ + const std::vector<unsigned char> op_true{OP_TRUE}; + CScriptWitness witness; + witness.stack.push_back(op_true); + + uint256 witness_program; + CSHA256().Write(&op_true[0], op_true.size()).Finalize(witness_program.begin()); + + const CScript SCRIPT_PUB{CScript(OP_0) << std::vector<unsigned char>{witness_program.begin(), witness_program.end()}}; + + // 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; + { + ::pblocktree.reset(new CBlockTreeDB(1 << 20, true)); + ::pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); + ::pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); + + const CChainParams& chainparams = Params(); + thread_group.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + LoadGenesisBlock(chainparams); + CValidationState state; + ActivateBestChain(state, chainparams); + assert(::chainActive.Tip() != nullptr); + const bool witness_enabled{IsWitnessEnabled(::chainActive.Tip(), chainparams.GetConsensus())}; + assert(witness_enabled); + } + + // Collect some loose transactions that spend the coinbases of our mined blocks + constexpr size_t NUM_BLOCKS{200}; + std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs; + for (size_t b{0}; b < NUM_BLOCKS; ++b) { + CMutableTransaction tx; + tx.vin.push_back(MineBlock(SCRIPT_PUB)); + tx.vin.back().scriptWitness = witness; + tx.vout.emplace_back(1337, SCRIPT_PUB); + if (NUM_BLOCKS - b >= COINBASE_MATURITY) + txs.at(b) = MakeTransactionRef(tx); + } + for (const auto& txr : txs) { + CValidationState state; + bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* pfMissingInputs */, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)}; + assert(ret); + } + + while (state.KeepRunning()) { + PrepareBlock(SCRIPT_PUB); + } + + thread_group.interrupt_all(); + thread_group.join_all(); + GetMainSignals().FlushBackgroundCallbacks(); + GetMainSignals().UnregisterBackgroundSignalScheduler(); +} + +BENCHMARK(AssembleBlock, 700); diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 64ec056c4d..f3180809b5 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -34,31 +34,25 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO static void CoinSelection(benchmark::State& state) { const CWallet wallet("dummy", WalletDatabase::CreateDummy()); - std::vector<COutput> vCoins; LOCK(wallet.cs_wallet); - while (state.KeepRunning()) { - // Add coins. - for (int i = 0; i < 1000; i++) - addCoin(1000 * COIN, wallet, vCoins); - addCoin(3 * COIN, wallet, vCoins); + // Add coins. + std::vector<COutput> vCoins; + for (int i = 0; i < 1000; ++i) { + addCoin(1000 * COIN, wallet, vCoins); + } + addCoin(3 * COIN, wallet, vCoins); + const CoinEligibilityFilter filter_standard(1, 6, 0); + const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0); + while (state.KeepRunning()) { std::set<CInputCoin> setCoinsRet; CAmount nValueRet; bool bnb_used; - CoinEligibilityFilter filter_standard(1, 6, 0); - CoinSelectionParams coin_selection_params(false, 34, 148, CFeeRate(0), 0); - bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) - || wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used); + bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); - - // Empty wallet. - for (COutput& output : vCoins) { - delete output.tx; - } - vCoins.clear(); } } diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index adb69bc6c3..7d907eaf10 100644 --- a/src/bench/crypto_hash.cpp +++ b/src/bench/crypto_hash.cpp @@ -52,6 +52,14 @@ static void SHA256_32b(benchmark::State& state) } } +static void SHA256D64_1024(benchmark::State& state) +{ + std::vector<uint8_t> in(64 * 1024, 0); + while (state.KeepRunning()) { + SHA256D64(in.data(), in.data(), 1024); + } +} + static void SHA512(benchmark::State& state) { uint8_t hash[CSHA512::OUTPUT_SIZE]; @@ -94,5 +102,6 @@ BENCHMARK(SHA512, 330); BENCHMARK(SHA256_32b, 4700 * 1000); BENCHMARK(SipHash_32b, 40 * 1000 * 1000); +BENCHMARK(SHA256D64_1024, 7400); BENCHMARK(FastRandom_32bit, 110 * 1000 * 1000); BENCHMARK(FastRandom_1bit, 440 * 1000 * 1000); diff --git a/src/bench/Examples.cpp b/src/bench/examples.cpp index b68c9cd156..b68c9cd156 100644 --- a/src/bench/Examples.cpp +++ b/src/bench/examples.cpp diff --git a/src/bench/merkle_root.cpp b/src/bench/merkle_root.cpp new file mode 100644 index 0000000000..fab12da311 --- /dev/null +++ b/src/bench/merkle_root.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 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. + +#include <bench/bench.h> + +#include <uint256.h> +#include <random.h> +#include <consensus/merkle.h> + +static void MerkleRoot(benchmark::State& state) +{ + FastRandomContext rng(true); + std::vector<uint256> leaves; + leaves.resize(9001); + for (auto& item : leaves) { + item = rng.rand256(); + } + while (state.KeepRunning()) { + bool mutation = false; + uint256 hash = ComputeMerkleRoot(std::vector<uint256>(leaves), &mutation); + leaves[mutation] = hash; + } +} + +BENCHMARK(MerkleRoot, 800); diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index d0f28d1a3e..09c7020848 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -42,13 +42,13 @@ static void PrevectorClear(benchmark::State& state) t0.resize(28); t0.clear(); t1.resize(29); - t0.clear(); + t1.clear(); } } } template <typename T> -void PrevectorResize(benchmark::State& state) +static void PrevectorResize(benchmark::State& state) { while (state.KeepRunning()) { prevector<28, T> t0; @@ -64,11 +64,11 @@ void PrevectorResize(benchmark::State& state) #define PREVECTOR_TEST(name, nontrivops, trivops) \ static void Prevector ## name ## Nontrivial(benchmark::State& state) { \ - PrevectorResize<nontrivial_t>(state); \ + Prevector ## name<nontrivial_t>(state); \ } \ BENCHMARK(Prevector ## name ## Nontrivial, nontrivops); \ static void Prevector ## name ## Trivial(benchmark::State& state) { \ - PrevectorResize<trivial_t>(state); \ + Prevector ## name<trivial_t>(state); \ } \ BENCHMARK(Prevector ## name ## Trivial, trivops); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index 4100519d48..ae60588c2d 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -9,6 +9,7 @@ #endif #include <script/script.h> #include <script/sign.h> +#include <script/standard.h> #include <streams.h> #include <array> diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 26a9231308..b332b5e581 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -29,29 +29,43 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; static const bool DEFAULT_NAMED=false; static const int CONTINUE_EXECUTION=-1; -std::string HelpMessageCli() +static void SetupCliArgs() { const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); - std::string strUsage; - strUsage += HelpMessageGroup(_("Options:")); - strUsage += HelpMessageOpt("-?", _("This help message")); - strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)"), BITCOIN_CONF_FILENAME)); - strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory")); - strUsage += HelpMessageOpt("-getinfo", _("Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)")); - AppendParamsHelpMessages(strUsage); - strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED)); - strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); - strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT)); - strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort())); - strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start")); - strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)")); - strUsage += HelpMessageOpt("-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.")); - strUsage += HelpMessageOpt("-stdinrpcpass", strprintf(_("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."))); - - return strUsage; + + gArgs.AddArg("-?", "This help message", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", false, OptionsCategory::OPTIONS); + SetupChainParamsBaseOptions(); + gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), false, OptionsCategory::OPTIONS); + 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::OPTIONS); + gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->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("-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", strprintf("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); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); +} + +/** libevent event log callback */ +static void libevent_log_cb(int severity, const char *msg) +{ +#ifndef EVENT_LOG_ERR // EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed. +# define EVENT_LOG_ERR _EVENT_LOG_ERR +#endif + // Ignore everything other than errors + if (severity >= EVENT_LOG_ERR) { + throw std::runtime_error(strprintf("libevent error: %s", msg)); + } } ////////////////////////////////////////////////////////////////////////////// @@ -82,17 +96,22 @@ static int AppInitRPC(int argc, char* argv[]) // // Parameters // - gArgs.ParseParameters(argc, argv); + SetupCliArgs(); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { - std::string strUsage = strprintf(_("%s RPC client version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n"; + std::string strUsage = strprintf("%s RPC client version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n"; if (!gArgs.IsArgSet("-version")) { - strUsage += "\n" + _("Usage:") + "\n" + - " bitcoin-cli [options] <command> [params] " + strprintf(_("Send command to %s"), _(PACKAGE_NAME)) + "\n" + - " bitcoin-cli [options] -named <command> [name=value] ... " + strprintf(_("Send command to %s (with named arguments)"), _(PACKAGE_NAME)) + "\n" + - " bitcoin-cli [options] help " + _("List commands") + "\n" + - " bitcoin-cli [options] help <command> " + _("Get help for a command") + "\n"; + strUsage += "\nUsage:\n" + " bitcoin-cli [options] <command> [params] " + strprintf("Send command to %s", PACKAGE_NAME) + "\n" + + " bitcoin-cli [options] -named <command> [name=value] ... " + strprintf("Send command to %s (with named arguments)", PACKAGE_NAME) + "\n" + + " bitcoin-cli [options] help List commands\n" + + " bitcoin-cli [options] help <command> Get help for a command\n"; - strUsage += "\n" + HelpMessageCli(); + strUsage += "\n" + gArgs.GetHelpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); @@ -106,10 +125,8 @@ static int AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } - try { - gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); - } catch (const std::exception& e) { - fprintf(stderr,"Error reading configuration file: %s\n", e.what()); + if (!gArgs.ReadConfigFiles(error, true)) { + fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return EXIT_FAILURE; } // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) @@ -138,7 +155,7 @@ struct HTTPReply std::string body; }; -const char *http_errorstring(int code) +static const char *http_errorstring(int code) { switch(code) { #if LIBEVENT_VERSION_NUMBER >= 0x02010300 @@ -366,7 +383,7 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co } else if (response.status == HTTP_UNAUTHORIZED) { if (failedToGetAuthCookie) { throw std::runtime_error(strprintf( - _("Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)"), + "Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string().c_str())); } else { throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword"); @@ -387,7 +404,7 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co return reply; } -int CommandLineRPC(int argc, char *argv[]) +static int CommandLineRPC(int argc, char *argv[]) { std::string strPrint; int nRet = 0; @@ -501,6 +518,7 @@ int main(int argc, char* argv[]) fprintf(stderr, "Error: Initializing networking failed\n"); return EXIT_FAILURE; } + event_set_log_callback(&libevent_log_cb); try { int ret = AppInitRPC(argc, argv); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 07ad09ea7b..181e2bb1bc 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2017 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. @@ -31,6 +31,45 @@ static bool fCreateBlank; static std::map<std::string,UniValue> registers; static const int CONTINUE_EXECUTION=-1; +static void SetupBitcoinTxArgs() +{ + gArgs.AddArg("-?", "This help message", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-create", "Create new, empty TX.", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-json", "Select JSON output", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-txid", "Output only the hex-encoded transaction id of the resultant transaction.", false, OptionsCategory::OPTIONS); + SetupChainParamsBaseOptions(); + + gArgs.AddArg("delin=N", "Delete input N from TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("delout=N", "Delete output N from TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("locktime=N", "Set TX lock time to N", false, OptionsCategory::COMMANDS); + gArgs.AddArg("nversion=N", "Set TX version to N", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", "Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS. " + "Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. " + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outpubkey=VALUE:PUBKEY[:FLAGS]", "Add pay-to-pubkey output to TX. " + "Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output. " + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outscript=VALUE:SCRIPT[:FLAGS]", "Add raw script output to TX. " + "Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. " + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); + gArgs.AddArg("replaceable(=N)", "Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)", false, OptionsCategory::COMMANDS); + gArgs.AddArg("sign=SIGHASH-FLAGS", "Add zero or more signatures to transaction. " + "This command requires JSON registers:" + "prevtxs=JSON object, " + "privatekeys=JSON object. " + "See signrawtransaction docs for format of sighash flags, JSON objects.", false, OptionsCategory::COMMANDS); + + gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS); + gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS); + + // Hidden + gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); + gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); +} + // // This function returns either one of EXIT_ codes when it's expected to stop the process or // CONTINUE_EXECUTION when it's expected to continue further. @@ -40,7 +79,12 @@ static int AppInitRawTx(int argc, char* argv[]) // // Parameters // - gArgs.ParseParameters(argc, argv); + SetupBitcoinTxArgs(); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return EXIT_FAILURE; + } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) try { @@ -54,53 +98,15 @@ static int AppInitRawTx(int argc, char* argv[]) if (argc < 2 || HelpRequested(gArgs)) { // First part of help message is specific to this utility - std::string strUsage = strprintf(_("%s bitcoin-tx utility version"), _(PACKAGE_NAME)) + " " + FormatFullVersion() + "\n\n" + - _("Usage:") + "\n" + - " bitcoin-tx [options] <hex-tx> [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + - " bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + + std::string strUsage = strprintf("%s bitcoin-tx utility version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n\n" + + "Usage:\n" + " bitcoin-tx [options] <hex-tx> [commands] Update hex-encoded bitcoin transaction\n" + + " bitcoin-tx [options] -create [commands] Create hex-encoded bitcoin transaction\n" + "\n"; + strUsage += gArgs.GetHelpMessage(); fprintf(stdout, "%s", strUsage.c_str()); - strUsage = HelpMessageGroup(_("Options:")); - strUsage += HelpMessageOpt("-?", _("This help message")); - strUsage += HelpMessageOpt("-create", _("Create new, empty TX.")); - strUsage += HelpMessageOpt("-json", _("Select JSON output")); - strUsage += HelpMessageOpt("-txid", _("Output only the hex-encoded transaction id of the resultant transaction.")); - AppendParamsHelpMessages(strUsage); - - fprintf(stdout, "%s", strUsage.c_str()); - - strUsage = HelpMessageGroup(_("Commands:")); - strUsage += HelpMessageOpt("delin=N", _("Delete input N from TX")); - strUsage += HelpMessageOpt("delout=N", _("Delete output N from TX")); - strUsage += HelpMessageOpt("in=TXID:VOUT(:SEQUENCE_NUMBER)", _("Add input to TX")); - strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N")); - strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N")); - strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX")); - strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX")); - strUsage += HelpMessageOpt("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", _("Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS") + ". " + - _("Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output") + ". " + - _("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.")); - strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " + - _("Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output") + ". " + - _("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.")); - strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT[:FLAGS]", _("Add raw script output to TX") + ". " + - _("Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output") + ". " + - _("Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.")); - strUsage += HelpMessageOpt("replaceable(=N)", _("Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)")); - strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " + - _("This command requires JSON registers:") + - _("prevtxs=JSON object") + ", " + - _("privatekeys=JSON object") + ". " + - _("See signrawtransaction docs for format of sighash flags, JSON objects.")); - fprintf(stdout, "%s", strUsage.c_str()); - - strUsage = HelpMessageGroup(_("Register Commands:")); - strUsage += HelpMessageOpt("load=NAME:FILENAME", _("Load JSON file FILENAME into register NAME")); - strUsage += HelpMessageOpt("set=NAME:JSON-STRING", _("Set register NAME to given JSON-STRING")); - fprintf(stdout, "%s", strUsage.c_str()); - if (argc < 2) { fprintf(stderr, "Error: too few parameters\n"); return EXIT_FAILURE; @@ -187,18 +193,18 @@ static CAmount ExtractAndValidateValue(const std::string& strValue) static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal) { - int64_t newVersion = atoi64(cmdVal); - if (newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION) - throw std::runtime_error("Invalid TX version requested"); + int64_t newVersion; + if (!ParseInt64(cmdVal, &newVersion) || newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION) + throw std::runtime_error("Invalid TX version requested: '" + cmdVal + "'"); tx.nVersion = (int) newVersion; } static void MutateTxLocktime(CMutableTransaction& tx, const std::string& cmdVal) { - int64_t newLocktime = atoi64(cmdVal); - if (newLocktime < 0LL || newLocktime > 0xffffffffLL) - throw std::runtime_error("Invalid TX locktime requested"); + int64_t newLocktime; + if (!ParseInt64(cmdVal, &newLocktime) || newLocktime < 0LL || newLocktime > 0xffffffffLL) + throw std::runtime_error("Invalid TX locktime requested: '" + cmdVal + "'"); tx.nLockTime = (unsigned int) newLocktime; } @@ -206,8 +212,8 @@ static void MutateTxLocktime(CMutableTransaction& tx, const std::string& cmdVal) static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInIdx) { // parse requested index - int inIdx = atoi(strInIdx); - if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { + int64_t inIdx; + if (!ParseInt64(strInIdx, &inIdx) || inIdx < 0 || inIdx >= static_cast<int64_t>(tx.vin.size())) { throw std::runtime_error("Invalid TX input index '" + strInIdx + "'"); } @@ -242,10 +248,10 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu static const unsigned int maxVout = MAX_BLOCK_WEIGHT / (WITNESS_SCALE_FACTOR * minTxOutSz); // extract and validate vout - std::string strVout = vStrInputParts[1]; - int vout = atoi(strVout); - if ((vout < 0) || (vout > (int)maxVout)) - throw std::runtime_error("invalid TX input vout"); + const std::string& strVout = vStrInputParts[1]; + int64_t vout; + if (!ParseInt64(strVout, &vout) || vout < 0 || vout > static_cast<int64_t>(maxVout)) + throw std::runtime_error("invalid TX input vout '" + strVout + "'"); // extract the optional sequence number uint32_t nSequenceIn=std::numeric_limits<unsigned int>::max(); @@ -475,10 +481,9 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str static void MutateTxDelInput(CMutableTransaction& tx, const std::string& strInIdx) { // parse requested deletion index - int inIdx = atoi(strInIdx); - if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { - std::string strErr = "Invalid TX input index '" + strInIdx + "'"; - throw std::runtime_error(strErr.c_str()); + int64_t inIdx; + if (!ParseInt64(strInIdx, &inIdx) || inIdx < 0 || inIdx >= static_cast<int64_t>(tx.vin.size())) { + throw std::runtime_error("Invalid TX input index '" + strInIdx + "'"); } // delete input from transaction @@ -488,10 +493,9 @@ static void MutateTxDelInput(CMutableTransaction& tx, const std::string& strInId static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOutIdx) { // parse requested deletion index - int outIdx = atoi(strOutIdx); - if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { - std::string strErr = "Invalid TX output index '" + strOutIdx + "'"; - throw std::runtime_error(strErr.c_str()); + int64_t outIdx; + if (!ParseInt64(strOutIdx, &outIdx) || outIdx < 0 || outIdx >= static_cast<int64_t>(tx.vout.size())) { + throw std::runtime_error("Invalid TX output index '" + strOutIdx + "'"); } // delete output from transaction @@ -548,7 +552,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) // mergedTx will end up with all the signatures; it // starts as a clone of the raw tx: CMutableTransaction mergedTx{tx}; - const CTransaction txv{tx}; + const CMutableTransaction txv{tx}; CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); @@ -585,9 +589,9 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) if (!prevOut.checkObject(types)) throw std::runtime_error("prevtxs internal object typecheck fail"); - uint256 txid = ParseHashUV(prevOut["txid"], "txid"); + uint256 txid = ParseHashStr(prevOut["txid"].get_str(), "txid"); - int nOut = atoi(prevOut["vout"].getValStr()); + const int nOut = prevOut["vout"].get_int(); if (nOut < 0) throw std::runtime_error("vout must be positive"); @@ -631,7 +635,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) // Sign what we can: for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { - const CTxIn& txin = mergedTx.vin[i]; + CTxIn& txin = mergedTx.vin[i]; const Coin& coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { continue; @@ -639,14 +643,12 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) const CScript& prevPubKey = coin.out.scriptPubKey; const CAmount& amount = coin.out.nValue; - SignatureData sigdata; + SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) ProduceSignature(keystore, MutableTransactionSignatureCreator(&mergedTx, i, amount, nHashType), prevPubKey, sigdata); - // ... and merge in other signatures: - sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i)); - UpdateTransaction(mergedTx, i, sigdata); + UpdateInput(txin, sigdata); } tx = mergedTx; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 0dc2dfbf7d..494a925a79 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -14,6 +14,7 @@ #include <rpc/server.h> #include <init.h> #include <noui.h> +#include <shutdown.h> #include <util.h> #include <httpserver.h> #include <httprpc.h> @@ -40,7 +41,7 @@ * Use the buttons <code>Namespaces</code>, <code>Classes</code> or <code>Files</code> at the top of the page to start navigating the code. */ -void WaitForShutdown() +static void WaitForShutdown() { while (!ShutdownRequested()) { @@ -53,7 +54,7 @@ void WaitForShutdown() // // Start // -bool AppInit(int argc, char* argv[]) +static bool AppInit(int argc, char* argv[]) { bool fRet = false; @@ -61,11 +62,16 @@ bool AppInit(int argc, char* argv[]) // Parameters // // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() - gArgs.ParseParameters(argc, argv); + SetupServerArgs(); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + return false; + } // Process help and version before taking care about datadir if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { - std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n"; + std::string strUsage = strprintf("%s Daemon", PACKAGE_NAME) + " version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { @@ -73,10 +79,10 @@ bool AppInit(int argc, char* argv[]) } else { - strUsage += "\n" + _("Usage:") + "\n" + - " bitcoind [options] " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n"; + strUsage += "\nUsage:\n" + " bitcoind [options] " + strprintf("Start %s Daemon", PACKAGE_NAME) + "\n"; - strUsage += "\n" + HelpMessage(HelpMessageMode::BITCOIND); + strUsage += "\n" + gArgs.GetHelpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); @@ -90,11 +96,8 @@ bool AppInit(int argc, char* argv[]) fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } - try - { - gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); - } catch (const std::exception& e) { - fprintf(stderr,"Error reading configuration file: %s\n", e.what()); + if (!gArgs.ReadConfigFiles(error)) { + fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return false; } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) diff --git a/src/bloom.cpp b/src/bloom.cpp index f07b5b6066..f8e28edded 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -245,6 +245,14 @@ static inline uint32_t RollingBloomHash(unsigned int nHashNum, uint32_t nTweak, return MurmurHash3(nHashNum * 0xFBA4C795 + nTweak, vDataToHash); } + +// A replacement for x % n. This assumes that x and n are 32bit integers, and x is a uniformly random distributed 32bit value +// which should be the case for a good hash. +// See https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +static inline uint32_t FastMod(uint32_t x, size_t n) { + return ((uint64_t)x * (uint64_t)n) >> 32; +} + void CRollingBloomFilter::insert(const std::vector<unsigned char>& vKey) { if (nEntriesThisGeneration == nEntriesPerGeneration) { @@ -268,7 +276,8 @@ void CRollingBloomFilter::insert(const std::vector<unsigned char>& vKey) for (int n = 0; n < nHashFuncs; n++) { uint32_t h = RollingBloomHash(n, nTweak, vKey); int bit = h & 0x3F; - uint32_t pos = (h >> 6) % data.size(); + /* FastMod works with the upper bits of h, so it is safe to ignore that the lower bits of h are already used for bit. */ + uint32_t pos = FastMod(h, data.size()); /* The lowest bit of pos is ignored, and set to zero for the first bit, and to one for the second. */ data[pos & ~1] = (data[pos & ~1] & ~(((uint64_t)1) << bit)) | ((uint64_t)(nGeneration & 1)) << bit; data[pos | 1] = (data[pos | 1] & ~(((uint64_t)1) << bit)) | ((uint64_t)(nGeneration >> 1)) << bit; @@ -286,7 +295,7 @@ bool CRollingBloomFilter::contains(const std::vector<unsigned char>& vKey) const for (int n = 0; n < nHashFuncs; n++) { uint32_t h = RollingBloomHash(n, nTweak, vKey); int bit = h & 0x3F; - uint32_t pos = (h >> 6) % data.size(); + uint32_t pos = FastMod(h, data.size()); /* If the relevant bit is not set in either data[pos & ~1] or data[pos | 1], the filter does not contain vKey */ if (!(((data[pos & ~1] | data[pos | 1]) >> bit) & 1)) { return false; diff --git a/src/chain.h b/src/chain.h index 757840bb23..8e6ac8d821 100644 --- a/src/chain.h +++ b/src/chain.h @@ -7,8 +7,8 @@ #define BITCOIN_CHAIN_H #include <arith_uint256.h> +#include <consensus/params.h> #include <primitives/block.h> -#include <pow.h> #include <tinyformat.h> #include <uint256.h> diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 121d95af90..71762158f0 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -173,7 +173,7 @@ public: // Data as of block 0000000000000000002d6cca6761c99b3c2e936f9a0e304b7c7651a993f461de (height 506081). 1516903077, // * UNIX timestamp of last known number of transactions 295363220, // * total number of transactions between genesis and that timestamp - // (the tx=... number in the SetBestChain debug.log lines) + // (the tx=... number in the ChainStateFlushed debug.log lines) 3.5 // * estimated number of transactions per second after that timestamp }; diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 3ef9c2cfe5..da9f3e3209 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -7,6 +7,7 @@ #include <tinyformat.h> #include <util.h> +#include <utilmemory.h> #include <assert.h> @@ -14,14 +15,11 @@ const std::string CBaseChainParams::MAIN = "main"; const std::string CBaseChainParams::TESTNET = "test"; const std::string CBaseChainParams::REGTEST = "regtest"; -void AppendParamsHelpMessages(std::string& strUsage, bool debugHelp) +void SetupChainParamsBaseOptions() { - strUsage += HelpMessageGroup(_("Chain selection options:")); - if (debugHelp) { - strUsage += HelpMessageOpt("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " - "This is intended for regression testing tools and app development."); - } - strUsage += HelpMessageOpt("-testnet", _("Use the test chain")); + gArgs.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " + "This is intended for regression testing tools and app development.", true, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-testnet", "Use the test chain", false, OptionsCategory::CHAINPARAMS); } static std::unique_ptr<CBaseChainParams> globalChainBaseParams; diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 5b11f36770..9f8bbafcd5 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -40,10 +40,9 @@ private: std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain); /** - * Append the help messages for the chainparams options to the - * parameter string. + *Set the arguments for chainparams */ -void AppendParamsHelpMessages(std::string& strUsage, bool debugHelp=true); +void SetupChainParamsBaseOptions(); /** * Return the currently selected parameters. This won't change after app diff --git a/src/compat/glibc_compat.cpp b/src/compat/glibc_compat.cpp index 55da5ef63f..b1cbe9f72a 100644 --- a/src/compat/glibc_compat.cpp +++ b/src/compat/glibc_compat.cpp @@ -7,6 +7,7 @@ #endif #include <cstddef> +#include <cstdint> #if defined(HAVE_SYS_SELECT_H) #include <sys/select.h> @@ -27,3 +28,47 @@ extern "C" FDELT_TYPE __fdelt_warn(FDELT_TYPE a) return a / __NFDBITS; } extern "C" FDELT_TYPE __fdelt_chk(FDELT_TYPE) __attribute__((weak, alias("__fdelt_warn"))); + +#if defined(__i386__) || defined(__arm__) + +extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp); + +extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp) +{ + int32_t c1 = 0, c2 = 0; + int64_t uu = u, vv = v; + int64_t w; + int64_t r; + + if (uu < 0) { + c1 = ~c1, c2 = ~c2, uu = -uu; + } + if (vv < 0) { + c1 = ~c1, vv = -vv; + } + + w = __udivmoddi4(uu, vv, (uint64_t*)&r); + if (c1) + w = -w; + if (c2) + r = -r; + + *rp = r; + return w; +} +#endif + +extern "C" float log2f_old(float x); +#ifdef __i386__ +__asm(".symver log2f_old,log2f@GLIBC_2.1"); +#elif defined(__amd64__) +__asm(".symver log2f_old,log2f@GLIBC_2.2.5"); +#elif defined(__arm__) +__asm(".symver log2f_old,log2f@GLIBC_2.4"); +#elif defined(__aarch64__) +__asm(".symver log2f_old,log2f@GLIBC_2.17"); +#endif +extern "C" float __wrap_log2f(float x) +{ + return log2f_old(x); +} diff --git a/src/consensus/merkle.cpp b/src/consensus/merkle.cpp index 74a9ebb2e3..07cd109cc1 100644 --- a/src/consensus/merkle.cpp +++ b/src/consensus/merkle.cpp @@ -42,118 +42,26 @@ root. */ -/* This implements a constant-space merkle root/path calculator, limited to 2^32 leaves. */ -static void MerkleComputation(const std::vector<uint256>& leaves, uint256* proot, bool* pmutated, uint32_t branchpos, std::vector<uint256>* pbranch) { - if (pbranch) pbranch->clear(); - if (leaves.size() == 0) { - if (pmutated) *pmutated = false; - if (proot) *proot = uint256(); - return; - } - bool mutated = false; - // count is the number of leaves processed so far. - uint32_t count = 0; - // inner is an array of eagerly computed subtree hashes, indexed by tree - // level (0 being the leaves). - // For example, when count is 25 (11001 in binary), inner[4] is the hash of - // the first 16 leaves, inner[3] of the next 8 leaves, and inner[0] equal to - // the last leaf. The other inner entries are undefined. - uint256 inner[32]; - // Which position in inner is a hash that depends on the matching leaf. - int matchlevel = -1; - // First process all leaves into 'inner' values. - while (count < leaves.size()) { - uint256 h = leaves[count]; - bool matchh = count == branchpos; - count++; - int level; - // For each of the lower bits in count that are 0, do 1 step. Each - // corresponds to an inner value that existed before processing the - // current leaf, and each needs a hash to combine it. - for (level = 0; !(count & (((uint32_t)1) << level)); level++) { - if (pbranch) { - if (matchh) { - pbranch->push_back(inner[level]); - } else if (matchlevel == level) { - pbranch->push_back(h); - matchh = true; - } + +uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated) { + bool mutation = false; + while (hashes.size() > 1) { + if (mutated) { + for (size_t pos = 0; pos + 1 < hashes.size(); pos += 2) { + if (hashes[pos] == hashes[pos + 1]) mutation = true; } - mutated |= (inner[level] == h); - CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); } - // Store the resulting hash at inner position level. - inner[level] = h; - if (matchh) { - matchlevel = level; + if (hashes.size() & 1) { + hashes.push_back(hashes.back()); } + SHA256D64(hashes[0].begin(), hashes[0].begin(), hashes.size() / 2); + hashes.resize(hashes.size() / 2); } - // Do a final 'sweep' over the rightmost branch of the tree to process - // odd levels, and reduce everything to a single top value. - // Level is the level (counted from the bottom) up to which we've sweeped. - int level = 0; - // As long as bit number level in count is zero, skip it. It means there - // is nothing left at this level. - while (!(count & (((uint32_t)1) << level))) { - level++; - } - uint256 h = inner[level]; - bool matchh = matchlevel == level; - while (count != (((uint32_t)1) << level)) { - // If we reach this point, h is an inner value that is not the top. - // We combine it with itself (Bitcoin's special rule for odd levels in - // the tree) to produce a higher level one. - if (pbranch && matchh) { - pbranch->push_back(h); - } - CHash256().Write(h.begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); - // Increment count to the value it would have if two entries at this - // level had existed. - count += (((uint32_t)1) << level); - level++; - // And propagate the result upwards accordingly. - while (!(count & (((uint32_t)1) << level))) { - if (pbranch) { - if (matchh) { - pbranch->push_back(inner[level]); - } else if (matchlevel == level) { - pbranch->push_back(h); - matchh = true; - } - } - CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); - level++; - } - } - // Return result. - if (pmutated) *pmutated = mutated; - if (proot) *proot = h; + if (mutated) *mutated = mutation; + if (hashes.size() == 0) return uint256(); + return hashes[0]; } -uint256 ComputeMerkleRoot(const std::vector<uint256>& leaves, bool* mutated) { - uint256 hash; - MerkleComputation(leaves, &hash, mutated, -1, nullptr); - return hash; -} - -std::vector<uint256> ComputeMerkleBranch(const std::vector<uint256>& leaves, uint32_t position) { - std::vector<uint256> ret; - MerkleComputation(leaves, nullptr, nullptr, position, &ret); - return ret; -} - -uint256 ComputeMerkleRootFromBranch(const uint256& leaf, const std::vector<uint256>& vMerkleBranch, uint32_t nIndex) { - 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)); - } else { - hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it)); - } - nIndex >>= 1; - } - return hash; -} uint256 BlockMerkleRoot(const CBlock& block, bool* mutated) { @@ -162,7 +70,7 @@ uint256 BlockMerkleRoot(const CBlock& block, bool* mutated) for (size_t s = 0; s < block.vtx.size(); s++) { leaves[s] = block.vtx[s]->GetHash(); } - return ComputeMerkleRoot(leaves, mutated); + return ComputeMerkleRoot(std::move(leaves), mutated); } uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated) @@ -173,15 +81,6 @@ uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated) for (size_t s = 1; s < block.vtx.size(); s++) { leaves[s] = block.vtx[s]->GetWitnessHash(); } - return ComputeMerkleRoot(leaves, mutated); + return ComputeMerkleRoot(std::move(leaves), mutated); } -std::vector<uint256> BlockMerkleBranch(const CBlock& block, uint32_t position) -{ - std::vector<uint256> leaves; - leaves.resize(block.vtx.size()); - for (size_t s = 0; s < block.vtx.size(); s++) { - leaves[s] = block.vtx[s]->GetHash(); - } - return ComputeMerkleBranch(leaves, position); -} diff --git a/src/consensus/merkle.h b/src/consensus/merkle.h index 0afb73adb5..01d75b1329 100644 --- a/src/consensus/merkle.h +++ b/src/consensus/merkle.h @@ -12,9 +12,7 @@ #include <primitives/block.h> #include <uint256.h> -uint256 ComputeMerkleRoot(const std::vector<uint256>& leaves, bool* mutated = nullptr); -std::vector<uint256> ComputeMerkleBranch(const std::vector<uint256>& leaves, uint32_t position); -uint256 ComputeMerkleRootFromBranch(const uint256& leaf, const std::vector<uint256>& branch, uint32_t position); +uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated = nullptr); /* * Compute the Merkle root of the transactions in a block. @@ -28,11 +26,4 @@ uint256 BlockMerkleRoot(const CBlock& block, bool* mutated = nullptr); */ uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated = nullptr); -/* - * Compute the Merkle branch for the tree of transactions in a block, for a - * given position. - * This can be verified using ComputeMerkleRootFromBranch. - */ -std::vector<uint256> BlockMerkleBranch(const CBlock& block, uint32_t position); - #endif // BITCOIN_CONSENSUS_MERKLE_H diff --git a/src/core_io.h b/src/core_io.h index 377633ac77..ebddcc2a04 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -14,6 +14,7 @@ class CBlock; class CScript; class CTransaction; struct CMutableTransaction; +struct PartiallySignedTransaction; class uint256; class UniValue; @@ -22,15 +23,18 @@ 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); -uint256 ParseHashUV(const UniValue& v, const std::string& strName); uint256 ParseHashStr(const std::string&, const std::string& strName); std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); +bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error); +int ParseSighashString(const UniValue& sighash); // core_write.cpp UniValue ValueFromAmount(const CAmount& amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0); +std::string SighashToStr(unsigned char sighash_type); void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); +void ScriptToUniv(const CScript& script, UniValue& out, bool include_address); void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0); #endif // BITCOIN_CORE_IO_H diff --git a/src/core_read.cpp b/src/core_read.cpp index 6a8308f869..067e1b91bd 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -7,6 +7,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <script/script.h> +#include <script/sign.h> #include <serialize.h> #include <streams.h> #include <univalue.h> @@ -88,7 +89,7 @@ CScript ParseScript(const std::string& s) } // Check that all of the input and output scripts of a transaction contains valid opcodes -bool CheckTxScriptsSanity(const CMutableTransaction& tx) +static bool CheckTxScriptsSanity(const CMutableTransaction& tx) { // Check input scripts for non-coinbase txs if (!CTransaction(tx).IsCoinBase()) { @@ -160,12 +161,21 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk) return true; } -uint256 ParseHashUV(const UniValue& v, const std::string& strName) +bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) { - std::string strHex; - if (v.isStr()) - strHex = v.getValStr(); - return ParseHashStr(strHex, strName); // Note: ParseHashStr("") throws a runtime_error + std::vector<unsigned char> tx_data = DecodeBase64(base64_tx.c_str()); + CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION); + try { + ss_data >> psbt; + if (!ss_data.empty()) { + error = "extra data after PSBT"; + return false; + } + } catch (const std::exception& e) { + error = e.what(); + return false; + } + return true; } uint256 ParseHashStr(const std::string& strHex, const std::string& strName) @@ -187,3 +197,26 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN throw std::runtime_error(strName + " must be hexadecimal string (not '" + strHex + "')"); return ParseHex(strHex); } + +int ParseSighashString(const UniValue& sighash) +{ + int hash_type = SIGHASH_ALL; + if (!sighash.isNull()) { + static std::map<std::string, int> map_sighash_values = { + {std::string("ALL"), int(SIGHASH_ALL)}, + {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, + {std::string("NONE"), int(SIGHASH_NONE)}, + {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, + {std::string("SINGLE"), int(SIGHASH_SINGLE)}, + {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, + }; + std::string strHashType = sighash.get_str(); + const auto& it = map_sighash_values.find(strHashType); + if (it != map_sighash_values.end()) { + hash_type = it->second; + } else { + throw std::runtime_error(strHashType + " is not a valid sighash parameter."); + } + } + return hash_type; +} diff --git a/src/core_write.cpp b/src/core_write.cpp index ee6737201b..1272266235 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -70,6 +70,13 @@ const std::map<unsigned char, std::string> mapSigHashTypes = { {static_cast<unsigned char>(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY), std::string("SINGLE|ANYONECANPAY")}, }; +std::string SighashToStr(unsigned char sighash_type) +{ + const auto& it = mapSigHashTypes.find(sighash_type); + if (it == mapSigHashTypes.end()) return ""; + return it->second; +} + /** * Create the assembly string representation of a CScript object. * @param[in] script CScript object to convert into the asm string representation. @@ -128,6 +135,22 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags) return HexStr(ssTx.begin(), ssTx.end()); } +void ScriptToUniv(const CScript& script, UniValue& out, bool include_address) +{ + out.pushKV("asm", ScriptToAsmStr(script)); + out.pushKV("hex", HexStr(script.begin(), script.end())); + + std::vector<std::vector<unsigned char>> solns; + txnouttype type; + Solver(script, type, solns); + out.pushKV("type", GetTxnOutputType(type)); + + CTxDestination address; + if (include_address && ExtractDestination(script, address)) { + out.pushKV("address", EncodeDestination(address)); + } +} + void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex) { diff --git a/src/crypto/sha256.cpp b/src/crypto/sha256.cpp index f3245b8dea..fbdbef0bc6 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -9,7 +9,7 @@ #include <string.h> #include <atomic> -#if defined(__x86_64__) || defined(__amd64__) +#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) #if defined(USE_ASM) #include <cpuid.h> namespace sha256_sse4 @@ -19,6 +19,26 @@ void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks); #endif #endif +namespace sha256d64_sse41 +{ +void Transform_4way(unsigned char* out, const unsigned char* in); +} + +namespace sha256d64_avx2 +{ +void Transform_8way(unsigned char* out, const unsigned char* in); +} + +namespace sha256d64_shani +{ +void Transform_2way(unsigned char* out, const unsigned char* in); +} + +namespace sha256_shani +{ +void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks); +} + // Internal implementation code. namespace { @@ -33,9 +53,9 @@ uint32_t inline sigma0(uint32_t x) { return (x >> 7 | x << 25) ^ (x >> 18 | x << uint32_t inline sigma1(uint32_t x) { return (x >> 17 | x << 15) ^ (x >> 19 | x << 13) ^ (x >> 10); } /** One round of SHA-256. */ -void inline Round(uint32_t a, uint32_t b, uint32_t c, uint32_t& d, uint32_t e, uint32_t f, uint32_t g, uint32_t& h, uint32_t k, uint32_t w) +void inline Round(uint32_t a, uint32_t b, uint32_t c, uint32_t& d, uint32_t e, uint32_t f, uint32_t g, uint32_t& h, uint32_t k) { - uint32_t t1 = h + Sigma1(e) + Ch(e, f, g) + k + w; + uint32_t t1 = h + Sigma1(e) + Ch(e, f, g) + k; uint32_t t2 = Sigma0(a) + Maj(a, b, c); d += t1; h = t1 + t2; @@ -61,73 +81,73 @@ void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks) uint32_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7]; uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; - Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = ReadBE32(chunk + 0)); - Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = ReadBE32(chunk + 4)); - Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = ReadBE32(chunk + 8)); - Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = ReadBE32(chunk + 12)); - Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = ReadBE32(chunk + 16)); - Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = ReadBE32(chunk + 20)); - Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = ReadBE32(chunk + 24)); - Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = ReadBE32(chunk + 28)); - Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = ReadBE32(chunk + 32)); - Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = ReadBE32(chunk + 36)); - Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = ReadBE32(chunk + 40)); - Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = ReadBE32(chunk + 44)); - Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = ReadBE32(chunk + 48)); - Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = ReadBE32(chunk + 52)); - Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = ReadBE32(chunk + 56)); - Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = ReadBE32(chunk + 60)); - - Round(a, b, c, d, e, f, g, h, 0xe49b69c1, w0 += sigma1(w14) + w9 + sigma0(w1)); - Round(h, a, b, c, d, e, f, g, 0xefbe4786, w1 += sigma1(w15) + w10 + sigma0(w2)); - Round(g, h, a, b, c, d, e, f, 0x0fc19dc6, w2 += sigma1(w0) + w11 + sigma0(w3)); - Round(f, g, h, a, b, c, d, e, 0x240ca1cc, w3 += sigma1(w1) + w12 + sigma0(w4)); - Round(e, f, g, h, a, b, c, d, 0x2de92c6f, w4 += sigma1(w2) + w13 + sigma0(w5)); - Round(d, e, f, g, h, a, b, c, 0x4a7484aa, w5 += sigma1(w3) + w14 + sigma0(w6)); - Round(c, d, e, f, g, h, a, b, 0x5cb0a9dc, w6 += sigma1(w4) + w15 + sigma0(w7)); - Round(b, c, d, e, f, g, h, a, 0x76f988da, w7 += sigma1(w5) + w0 + sigma0(w8)); - Round(a, b, c, d, e, f, g, h, 0x983e5152, w8 += sigma1(w6) + w1 + sigma0(w9)); - Round(h, a, b, c, d, e, f, g, 0xa831c66d, w9 += sigma1(w7) + w2 + sigma0(w10)); - Round(g, h, a, b, c, d, e, f, 0xb00327c8, w10 += sigma1(w8) + w3 + sigma0(w11)); - Round(f, g, h, a, b, c, d, e, 0xbf597fc7, w11 += sigma1(w9) + w4 + sigma0(w12)); - Round(e, f, g, h, a, b, c, d, 0xc6e00bf3, w12 += sigma1(w10) + w5 + sigma0(w13)); - Round(d, e, f, g, h, a, b, c, 0xd5a79147, w13 += sigma1(w11) + w6 + sigma0(w14)); - Round(c, d, e, f, g, h, a, b, 0x06ca6351, w14 += sigma1(w12) + w7 + sigma0(w15)); - Round(b, c, d, e, f, g, h, a, 0x14292967, w15 += sigma1(w13) + w8 + sigma0(w0)); - - Round(a, b, c, d, e, f, g, h, 0x27b70a85, w0 += sigma1(w14) + w9 + sigma0(w1)); - Round(h, a, b, c, d, e, f, g, 0x2e1b2138, w1 += sigma1(w15) + w10 + sigma0(w2)); - Round(g, h, a, b, c, d, e, f, 0x4d2c6dfc, w2 += sigma1(w0) + w11 + sigma0(w3)); - Round(f, g, h, a, b, c, d, e, 0x53380d13, w3 += sigma1(w1) + w12 + sigma0(w4)); - Round(e, f, g, h, a, b, c, d, 0x650a7354, w4 += sigma1(w2) + w13 + sigma0(w5)); - Round(d, e, f, g, h, a, b, c, 0x766a0abb, w5 += sigma1(w3) + w14 + sigma0(w6)); - Round(c, d, e, f, g, h, a, b, 0x81c2c92e, w6 += sigma1(w4) + w15 + sigma0(w7)); - Round(b, c, d, e, f, g, h, a, 0x92722c85, w7 += sigma1(w5) + w0 + sigma0(w8)); - Round(a, b, c, d, e, f, g, h, 0xa2bfe8a1, w8 += sigma1(w6) + w1 + sigma0(w9)); - Round(h, a, b, c, d, e, f, g, 0xa81a664b, w9 += sigma1(w7) + w2 + sigma0(w10)); - Round(g, h, a, b, c, d, e, f, 0xc24b8b70, w10 += sigma1(w8) + w3 + sigma0(w11)); - Round(f, g, h, a, b, c, d, e, 0xc76c51a3, w11 += sigma1(w9) + w4 + sigma0(w12)); - Round(e, f, g, h, a, b, c, d, 0xd192e819, w12 += sigma1(w10) + w5 + sigma0(w13)); - Round(d, e, f, g, h, a, b, c, 0xd6990624, w13 += sigma1(w11) + w6 + sigma0(w14)); - Round(c, d, e, f, g, h, a, b, 0xf40e3585, w14 += sigma1(w12) + w7 + sigma0(w15)); - Round(b, c, d, e, f, g, h, a, 0x106aa070, w15 += sigma1(w13) + w8 + sigma0(w0)); - - Round(a, b, c, d, e, f, g, h, 0x19a4c116, w0 += sigma1(w14) + w9 + sigma0(w1)); - Round(h, a, b, c, d, e, f, g, 0x1e376c08, w1 += sigma1(w15) + w10 + sigma0(w2)); - Round(g, h, a, b, c, d, e, f, 0x2748774c, w2 += sigma1(w0) + w11 + sigma0(w3)); - Round(f, g, h, a, b, c, d, e, 0x34b0bcb5, w3 += sigma1(w1) + w12 + sigma0(w4)); - Round(e, f, g, h, a, b, c, d, 0x391c0cb3, w4 += sigma1(w2) + w13 + sigma0(w5)); - Round(d, e, f, g, h, a, b, c, 0x4ed8aa4a, w5 += sigma1(w3) + w14 + sigma0(w6)); - Round(c, d, e, f, g, h, a, b, 0x5b9cca4f, w6 += sigma1(w4) + w15 + sigma0(w7)); - Round(b, c, d, e, f, g, h, a, 0x682e6ff3, w7 += sigma1(w5) + w0 + sigma0(w8)); - Round(a, b, c, d, e, f, g, h, 0x748f82ee, w8 += sigma1(w6) + w1 + sigma0(w9)); - Round(h, a, b, c, d, e, f, g, 0x78a5636f, w9 += sigma1(w7) + w2 + sigma0(w10)); - Round(g, h, a, b, c, d, e, f, 0x84c87814, w10 += sigma1(w8) + w3 + sigma0(w11)); - Round(f, g, h, a, b, c, d, e, 0x8cc70208, w11 += sigma1(w9) + w4 + sigma0(w12)); - Round(e, f, g, h, a, b, c, d, 0x90befffa, w12 += sigma1(w10) + w5 + sigma0(w13)); - Round(d, e, f, g, h, a, b, c, 0xa4506ceb, w13 += sigma1(w11) + w6 + sigma0(w14)); - Round(c, d, e, f, g, h, a, b, 0xbef9a3f7, w14 + sigma1(w12) + w7 + sigma0(w15)); - Round(b, c, d, e, f, g, h, a, 0xc67178f2, w15 + sigma1(w13) + w8 + sigma0(w0)); + Round(a, b, c, d, e, f, g, h, 0x428a2f98 + (w0 = ReadBE32(chunk + 0))); + Round(h, a, b, c, d, e, f, g, 0x71374491 + (w1 = ReadBE32(chunk + 4))); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf + (w2 = ReadBE32(chunk + 8))); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5 + (w3 = ReadBE32(chunk + 12))); + Round(e, f, g, h, a, b, c, d, 0x3956c25b + (w4 = ReadBE32(chunk + 16))); + Round(d, e, f, g, h, a, b, c, 0x59f111f1 + (w5 = ReadBE32(chunk + 20))); + Round(c, d, e, f, g, h, a, b, 0x923f82a4 + (w6 = ReadBE32(chunk + 24))); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5 + (w7 = ReadBE32(chunk + 28))); + Round(a, b, c, d, e, f, g, h, 0xd807aa98 + (w8 = ReadBE32(chunk + 32))); + Round(h, a, b, c, d, e, f, g, 0x12835b01 + (w9 = ReadBE32(chunk + 36))); + Round(g, h, a, b, c, d, e, f, 0x243185be + (w10 = ReadBE32(chunk + 40))); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3 + (w11 = ReadBE32(chunk + 44))); + Round(e, f, g, h, a, b, c, d, 0x72be5d74 + (w12 = ReadBE32(chunk + 48))); + Round(d, e, f, g, h, a, b, c, 0x80deb1fe + (w13 = ReadBE32(chunk + 52))); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7 + (w14 = ReadBE32(chunk + 56))); + Round(b, c, d, e, f, g, h, a, 0xc19bf174 + (w15 = ReadBE32(chunk + 60))); + + Round(a, b, c, d, e, f, g, h, 0xe49b69c1 + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0xefbe4786 + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x0fc19dc6 + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x240ca1cc + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x2de92c6f + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4a7484aa + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5cb0a9dc + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x76f988da + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0x983e5152 + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0xa831c66d + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0xb00327c8 + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0xbf597fc7 + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0xc6e00bf3 + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xd5a79147 + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0x06ca6351 + (w14 += sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0x14292967 + (w15 += sigma1(w13) + w8 + sigma0(w0))); + + Round(a, b, c, d, e, f, g, h, 0x27b70a85 + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x2e1b2138 + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x4d2c6dfc + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x53380d13 + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x650a7354 + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x766a0abb + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x81c2c92e + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x92722c85 + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0xa2bfe8a1 + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0xa81a664b + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0xc24b8b70 + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0xc76c51a3 + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0xd192e819 + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xd6990624 + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xf40e3585 + (w14 += sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0x106aa070 + (w15 += sigma1(w13) + w8 + sigma0(w0))); + + Round(a, b, c, d, e, f, g, h, 0x19a4c116 + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x1e376c08 + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x2748774c + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x34b0bcb5 + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x391c0cb3 + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4ed8aa4a + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5b9cca4f + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x682e6ff3 + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0x748f82ee + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0x78a5636f + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0x84c87814 + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0x8cc70208 + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0x90befffa + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xa4506ceb + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xbef9a3f7 + (w14 + sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0xc67178f2 + (w15 + sigma1(w13) + w8 + sigma0(w0))); s[0] += a; s[1] += b; @@ -141,54 +161,481 @@ void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks) } } +void TransformD64(unsigned char* out, const unsigned char* in) +{ + // Transform 1 + uint32_t a = 0x6a09e667ul; + uint32_t b = 0xbb67ae85ul; + uint32_t c = 0x3c6ef372ul; + uint32_t d = 0xa54ff53aul; + uint32_t e = 0x510e527ful; + uint32_t f = 0x9b05688cul; + uint32_t g = 0x1f83d9abul; + uint32_t h = 0x5be0cd19ul; + + uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; + + Round(a, b, c, d, e, f, g, h, 0x428a2f98ul + (w0 = ReadBE32(in + 0))); + Round(h, a, b, c, d, e, f, g, 0x71374491ul + (w1 = ReadBE32(in + 4))); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcful + (w2 = ReadBE32(in + 8))); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5ul + (w3 = ReadBE32(in + 12))); + Round(e, f, g, h, a, b, c, d, 0x3956c25bul + (w4 = ReadBE32(in + 16))); + Round(d, e, f, g, h, a, b, c, 0x59f111f1ul + (w5 = ReadBE32(in + 20))); + Round(c, d, e, f, g, h, a, b, 0x923f82a4ul + (w6 = ReadBE32(in + 24))); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5ul + (w7 = ReadBE32(in + 28))); + Round(a, b, c, d, e, f, g, h, 0xd807aa98ul + (w8 = ReadBE32(in + 32))); + Round(h, a, b, c, d, e, f, g, 0x12835b01ul + (w9 = ReadBE32(in + 36))); + Round(g, h, a, b, c, d, e, f, 0x243185beul + (w10 = ReadBE32(in + 40))); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3ul + (w11 = ReadBE32(in + 44))); + Round(e, f, g, h, a, b, c, d, 0x72be5d74ul + (w12 = ReadBE32(in + 48))); + Round(d, e, f, g, h, a, b, c, 0x80deb1feul + (w13 = ReadBE32(in + 52))); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7ul + (w14 = ReadBE32(in + 56))); + Round(b, c, d, e, f, g, h, a, 0xc19bf174ul + (w15 = ReadBE32(in + 60))); + Round(a, b, c, d, e, f, g, h, 0xe49b69c1ul + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0xefbe4786ul + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x0fc19dc6ul + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x240ca1ccul + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x2de92c6ful + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4a7484aaul + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5cb0a9dcul + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x76f988daul + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0x983e5152ul + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0xa831c66dul + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0xb00327c8ul + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0xbf597fc7ul + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0xc6e00bf3ul + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xd5a79147ul + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0x06ca6351ul + (w14 += sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0x14292967ul + (w15 += sigma1(w13) + w8 + sigma0(w0))); + Round(a, b, c, d, e, f, g, h, 0x27b70a85ul + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x2e1b2138ul + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x4d2c6dfcul + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x53380d13ul + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x650a7354ul + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x766a0abbul + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x81c2c92eul + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x92722c85ul + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0xa2bfe8a1ul + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0xa81a664bul + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0xc24b8b70ul + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0xc76c51a3ul + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0xd192e819ul + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xd6990624ul + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xf40e3585ul + (w14 += sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0x106aa070ul + (w15 += sigma1(w13) + w8 + sigma0(w0))); + Round(a, b, c, d, e, f, g, h, 0x19a4c116ul + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x1e376c08ul + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x2748774cul + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x34b0bcb5ul + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x391c0cb3ul + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4ed8aa4aul + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5b9cca4ful + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x682e6ff3ul + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0x748f82eeul + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0x78a5636ful + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0x84c87814ul + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0x8cc70208ul + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0x90befffaul + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xa4506cebul + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xbef9a3f7ul + (w14 + sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0xc67178f2ul + (w15 + sigma1(w13) + w8 + sigma0(w0))); + + a += 0x6a09e667ul; + b += 0xbb67ae85ul; + c += 0x3c6ef372ul; + d += 0xa54ff53aul; + e += 0x510e527ful; + f += 0x9b05688cul; + g += 0x1f83d9abul; + h += 0x5be0cd19ul; + + uint32_t t0 = a, t1 = b, t2 = c, t3 = d, t4 = e, t5 = f, t6 = g, t7 = h; + + // Transform 2 + Round(a, b, c, d, e, f, g, h, 0xc28a2f98ul); + Round(h, a, b, c, d, e, f, g, 0x71374491ul); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcful); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5ul); + Round(e, f, g, h, a, b, c, d, 0x3956c25bul); + Round(d, e, f, g, h, a, b, c, 0x59f111f1ul); + Round(c, d, e, f, g, h, a, b, 0x923f82a4ul); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5ul); + Round(a, b, c, d, e, f, g, h, 0xd807aa98ul); + Round(h, a, b, c, d, e, f, g, 0x12835b01ul); + Round(g, h, a, b, c, d, e, f, 0x243185beul); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3ul); + Round(e, f, g, h, a, b, c, d, 0x72be5d74ul); + Round(d, e, f, g, h, a, b, c, 0x80deb1feul); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7ul); + Round(b, c, d, e, f, g, h, a, 0xc19bf374ul); + Round(a, b, c, d, e, f, g, h, 0x649b69c1ul); + Round(h, a, b, c, d, e, f, g, 0xf0fe4786ul); + Round(g, h, a, b, c, d, e, f, 0x0fe1edc6ul); + Round(f, g, h, a, b, c, d, e, 0x240cf254ul); + Round(e, f, g, h, a, b, c, d, 0x4fe9346ful); + Round(d, e, f, g, h, a, b, c, 0x6cc984beul); + Round(c, d, e, f, g, h, a, b, 0x61b9411eul); + Round(b, c, d, e, f, g, h, a, 0x16f988faul); + Round(a, b, c, d, e, f, g, h, 0xf2c65152ul); + Round(h, a, b, c, d, e, f, g, 0xa88e5a6dul); + Round(g, h, a, b, c, d, e, f, 0xb019fc65ul); + Round(f, g, h, a, b, c, d, e, 0xb9d99ec7ul); + Round(e, f, g, h, a, b, c, d, 0x9a1231c3ul); + Round(d, e, f, g, h, a, b, c, 0xe70eeaa0ul); + Round(c, d, e, f, g, h, a, b, 0xfdb1232bul); + Round(b, c, d, e, f, g, h, a, 0xc7353eb0ul); + Round(a, b, c, d, e, f, g, h, 0x3069bad5ul); + Round(h, a, b, c, d, e, f, g, 0xcb976d5ful); + Round(g, h, a, b, c, d, e, f, 0x5a0f118ful); + Round(f, g, h, a, b, c, d, e, 0xdc1eeefdul); + Round(e, f, g, h, a, b, c, d, 0x0a35b689ul); + Round(d, e, f, g, h, a, b, c, 0xde0b7a04ul); + Round(c, d, e, f, g, h, a, b, 0x58f4ca9dul); + Round(b, c, d, e, f, g, h, a, 0xe15d5b16ul); + Round(a, b, c, d, e, f, g, h, 0x007f3e86ul); + Round(h, a, b, c, d, e, f, g, 0x37088980ul); + Round(g, h, a, b, c, d, e, f, 0xa507ea32ul); + Round(f, g, h, a, b, c, d, e, 0x6fab9537ul); + Round(e, f, g, h, a, b, c, d, 0x17406110ul); + Round(d, e, f, g, h, a, b, c, 0x0d8cd6f1ul); + Round(c, d, e, f, g, h, a, b, 0xcdaa3b6dul); + Round(b, c, d, e, f, g, h, a, 0xc0bbbe37ul); + Round(a, b, c, d, e, f, g, h, 0x83613bdaul); + Round(h, a, b, c, d, e, f, g, 0xdb48a363ul); + Round(g, h, a, b, c, d, e, f, 0x0b02e931ul); + Round(f, g, h, a, b, c, d, e, 0x6fd15ca7ul); + Round(e, f, g, h, a, b, c, d, 0x521afacaul); + Round(d, e, f, g, h, a, b, c, 0x31338431ul); + Round(c, d, e, f, g, h, a, b, 0x6ed41a95ul); + Round(b, c, d, e, f, g, h, a, 0x6d437890ul); + Round(a, b, c, d, e, f, g, h, 0xc39c91f2ul); + Round(h, a, b, c, d, e, f, g, 0x9eccabbdul); + Round(g, h, a, b, c, d, e, f, 0xb5c9a0e6ul); + Round(f, g, h, a, b, c, d, e, 0x532fb63cul); + Round(e, f, g, h, a, b, c, d, 0xd2c741c6ul); + Round(d, e, f, g, h, a, b, c, 0x07237ea3ul); + Round(c, d, e, f, g, h, a, b, 0xa4954b68ul); + Round(b, c, d, e, f, g, h, a, 0x4c191d76ul); + + w0 = t0 + a; + w1 = t1 + b; + w2 = t2 + c; + w3 = t3 + d; + w4 = t4 + e; + w5 = t5 + f; + w6 = t6 + g; + w7 = t7 + h; + + // Transform 3 + a = 0x6a09e667ul; + b = 0xbb67ae85ul; + c = 0x3c6ef372ul; + d = 0xa54ff53aul; + e = 0x510e527ful; + f = 0x9b05688cul; + g = 0x1f83d9abul; + h = 0x5be0cd19ul; + + Round(a, b, c, d, e, f, g, h, 0x428a2f98ul + w0); + Round(h, a, b, c, d, e, f, g, 0x71374491ul + w1); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcful + w2); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5ul + w3); + Round(e, f, g, h, a, b, c, d, 0x3956c25bul + w4); + Round(d, e, f, g, h, a, b, c, 0x59f111f1ul + w5); + Round(c, d, e, f, g, h, a, b, 0x923f82a4ul + w6); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5ul + w7); + Round(a, b, c, d, e, f, g, h, 0x5807aa98ul); + Round(h, a, b, c, d, e, f, g, 0x12835b01ul); + Round(g, h, a, b, c, d, e, f, 0x243185beul); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3ul); + Round(e, f, g, h, a, b, c, d, 0x72be5d74ul); + Round(d, e, f, g, h, a, b, c, 0x80deb1feul); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7ul); + Round(b, c, d, e, f, g, h, a, 0xc19bf274ul); + Round(a, b, c, d, e, f, g, h, 0xe49b69c1ul + (w0 += sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0xefbe4786ul + (w1 += 0xa00000ul + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x0fc19dc6ul + (w2 += sigma1(w0) + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x240ca1ccul + (w3 += sigma1(w1) + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x2de92c6ful + (w4 += sigma1(w2) + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4a7484aaul + (w5 += sigma1(w3) + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5cb0a9dcul + (w6 += sigma1(w4) + 0x100ul + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x76f988daul + (w7 += sigma1(w5) + w0 + 0x11002000ul)); + Round(a, b, c, d, e, f, g, h, 0x983e5152ul + (w8 = 0x80000000ul + sigma1(w6) + w1)); + Round(h, a, b, c, d, e, f, g, 0xa831c66dul + (w9 = sigma1(w7) + w2)); + Round(g, h, a, b, c, d, e, f, 0xb00327c8ul + (w10 = sigma1(w8) + w3)); + Round(f, g, h, a, b, c, d, e, 0xbf597fc7ul + (w11 = sigma1(w9) + w4)); + Round(e, f, g, h, a, b, c, d, 0xc6e00bf3ul + (w12 = sigma1(w10) + w5)); + Round(d, e, f, g, h, a, b, c, 0xd5a79147ul + (w13 = sigma1(w11) + w6)); + Round(c, d, e, f, g, h, a, b, 0x06ca6351ul + (w14 = sigma1(w12) + w7 + 0x400022ul)); + Round(b, c, d, e, f, g, h, a, 0x14292967ul + (w15 = 0x100ul + sigma1(w13) + w8 + sigma0(w0))); + Round(a, b, c, d, e, f, g, h, 0x27b70a85ul + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x2e1b2138ul + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x4d2c6dfcul + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x53380d13ul + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x650a7354ul + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x766a0abbul + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x81c2c92eul + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x92722c85ul + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0xa2bfe8a1ul + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0xa81a664bul + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0xc24b8b70ul + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0xc76c51a3ul + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0xd192e819ul + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xd6990624ul + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xf40e3585ul + (w14 += sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0x106aa070ul + (w15 += sigma1(w13) + w8 + sigma0(w0))); + Round(a, b, c, d, e, f, g, h, 0x19a4c116ul + (w0 += sigma1(w14) + w9 + sigma0(w1))); + Round(h, a, b, c, d, e, f, g, 0x1e376c08ul + (w1 += sigma1(w15) + w10 + sigma0(w2))); + Round(g, h, a, b, c, d, e, f, 0x2748774cul + (w2 += sigma1(w0) + w11 + sigma0(w3))); + Round(f, g, h, a, b, c, d, e, 0x34b0bcb5ul + (w3 += sigma1(w1) + w12 + sigma0(w4))); + Round(e, f, g, h, a, b, c, d, 0x391c0cb3ul + (w4 += sigma1(w2) + w13 + sigma0(w5))); + Round(d, e, f, g, h, a, b, c, 0x4ed8aa4aul + (w5 += sigma1(w3) + w14 + sigma0(w6))); + Round(c, d, e, f, g, h, a, b, 0x5b9cca4ful + (w6 += sigma1(w4) + w15 + sigma0(w7))); + Round(b, c, d, e, f, g, h, a, 0x682e6ff3ul + (w7 += sigma1(w5) + w0 + sigma0(w8))); + Round(a, b, c, d, e, f, g, h, 0x748f82eeul + (w8 += sigma1(w6) + w1 + sigma0(w9))); + Round(h, a, b, c, d, e, f, g, 0x78a5636ful + (w9 += sigma1(w7) + w2 + sigma0(w10))); + Round(g, h, a, b, c, d, e, f, 0x84c87814ul + (w10 += sigma1(w8) + w3 + sigma0(w11))); + Round(f, g, h, a, b, c, d, e, 0x8cc70208ul + (w11 += sigma1(w9) + w4 + sigma0(w12))); + Round(e, f, g, h, a, b, c, d, 0x90befffaul + (w12 += sigma1(w10) + w5 + sigma0(w13))); + Round(d, e, f, g, h, a, b, c, 0xa4506cebul + (w13 += sigma1(w11) + w6 + sigma0(w14))); + Round(c, d, e, f, g, h, a, b, 0xbef9a3f7ul + (w14 + sigma1(w12) + w7 + sigma0(w15))); + Round(b, c, d, e, f, g, h, a, 0xc67178f2ul + (w15 + sigma1(w13) + w8 + sigma0(w0))); + + // Output + WriteBE32(out + 0, a + 0x6a09e667ul); + WriteBE32(out + 4, b + 0xbb67ae85ul); + WriteBE32(out + 8, c + 0x3c6ef372ul); + WriteBE32(out + 12, d + 0xa54ff53aul); + WriteBE32(out + 16, e + 0x510e527ful); + WriteBE32(out + 20, f + 0x9b05688cul); + WriteBE32(out + 24, g + 0x1f83d9abul); + WriteBE32(out + 28, h + 0x5be0cd19ul); +} + } // namespace sha256 typedef void (*TransformType)(uint32_t*, const unsigned char*, size_t); +typedef void (*TransformD64Type)(unsigned char*, const unsigned char*); -bool SelfTest(TransformType tr) { - static const unsigned char in1[65] = {0, 0x80}; - static const unsigned char in2[129] = { - 0, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 +template<TransformType tr> +void TransformD64Wrapper(unsigned char* out, const unsigned char* in) +{ + uint32_t s[8]; + static const unsigned char padding1[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 }; - static const uint32_t init[8] = {0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, 0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul}; - static const uint32_t out1[8] = {0xe3b0c442ul, 0x98fc1c14ul, 0x9afbf4c8ul, 0x996fb924ul, 0x27ae41e4ul, 0x649b934cul, 0xa495991bul, 0x7852b855ul}; - static const uint32_t out2[8] = {0xce4153b0ul, 0x147c2a86ul, 0x3ed4298eul, 0xe0676bc8ul, 0x79fc77a1ul, 0x2abe1f49ul, 0xb2b055dful, 0x1069523eul}; - uint32_t buf[8]; - memcpy(buf, init, sizeof(buf)); - // Process nothing, and check we remain in the initial state. - tr(buf, nullptr, 0); - if (memcmp(buf, init, sizeof(buf))) return false; - // Process the padded empty string (unaligned) - tr(buf, in1 + 1, 1); - if (memcmp(buf, out1, sizeof(buf))) return false; - // Process 64 spaces (unaligned) - memcpy(buf, init, sizeof(buf)); - tr(buf, in2 + 1, 2); - if (memcmp(buf, out2, sizeof(buf))) return false; - return true; + unsigned char buffer2[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + }; + sha256::Initialize(s); + tr(s, in, 1); + tr(s, padding1, 1); + WriteBE32(buffer2 + 0, s[0]); + WriteBE32(buffer2 + 4, s[1]); + WriteBE32(buffer2 + 8, s[2]); + WriteBE32(buffer2 + 12, s[3]); + WriteBE32(buffer2 + 16, s[4]); + WriteBE32(buffer2 + 20, s[5]); + WriteBE32(buffer2 + 24, s[6]); + WriteBE32(buffer2 + 28, s[7]); + sha256::Initialize(s); + tr(s, buffer2, 1); + WriteBE32(out + 0, s[0]); + WriteBE32(out + 4, s[1]); + WriteBE32(out + 8, s[2]); + WriteBE32(out + 12, s[3]); + WriteBE32(out + 16, s[4]); + WriteBE32(out + 20, s[5]); + WriteBE32(out + 24, s[6]); + WriteBE32(out + 28, s[7]); } TransformType Transform = sha256::Transform; +TransformD64Type TransformD64 = sha256::TransformD64; +TransformD64Type TransformD64_2way = nullptr; +TransformD64Type TransformD64_4way = nullptr; +TransformD64Type TransformD64_8way = nullptr; + +bool SelfTest() { + // Input state (equal to the initial SHA256 state) + static const uint32_t init[8] = { + 0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, 0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul + }; + // Some random input data to test with + static const unsigned char data[641] = "-" // Intentionally not aligned + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Et m" + "olestie ac feugiat sed lectus vestibulum mattis ullamcorper. Mor" + "bi blandit cursus risus at ultrices mi tempus imperdiet nulla. N" + "unc congue nisi vita suscipit tellus mauris. Imperdiet proin fer" + "mentum leo vel orci. Massa tempor nec feugiat nisl pretium fusce" + " id velit. Telus in metus vulputate eu scelerisque felis. Mi tem" + "pus imperdiet nulla malesuada pellentesque. Tristique magna sit."; + // Expected output state for hashing the i*64 first input bytes above (excluding SHA256 padding). + static const uint32_t result[9][8] = { + {0x6a09e667ul, 0xbb67ae85ul, 0x3c6ef372ul, 0xa54ff53aul, 0x510e527ful, 0x9b05688cul, 0x1f83d9abul, 0x5be0cd19ul}, + {0x91f8ec6bul, 0x4da10fe3ul, 0x1c9c292cul, 0x45e18185ul, 0x435cc111ul, 0x3ca26f09ul, 0xeb954caeul, 0x402a7069ul}, + {0xcabea5acul, 0x374fb97cul, 0x182ad996ul, 0x7bd69cbful, 0x450ff900ul, 0xc1d2be8aul, 0x6a41d505ul, 0xe6212dc3ul}, + {0xbcff09d6ul, 0x3e76f36eul, 0x3ecb2501ul, 0x78866e97ul, 0xe1c1e2fdul, 0x32f4eafful, 0x8aa6c4e5ul, 0xdfc024bcul}, + {0xa08c5d94ul, 0x0a862f93ul, 0x6b7f2f40ul, 0x8f9fae76ul, 0x6d40439ful, 0x79dcee0cul, 0x3e39ff3aul, 0xdc3bdbb1ul}, + {0x216a0895ul, 0x9f1a3662ul, 0xe99946f9ul, 0x87ba4364ul, 0x0fb5db2cul, 0x12bed3d3ul, 0x6689c0c7ul, 0x292f1b04ul}, + {0xca3067f8ul, 0xbc8c2656ul, 0x37cb7e0dul, 0x9b6b8b0ful, 0x46dc380bul, 0xf1287f57ul, 0xc42e4b23ul, 0x3fefe94dul}, + {0x3e4c4039ul, 0xbb6fca8cul, 0x6f27d2f7ul, 0x301e44a4ul, 0x8352ba14ul, 0x5769ce37ul, 0x48a1155ful, 0xc0e1c4c6ul}, + {0xfe2fa9ddul, 0x69d0862bul, 0x1ae0db23ul, 0x471f9244ul, 0xf55c0145ul, 0xc30f9c3bul, 0x40a84ea0ul, 0x5b8a266cul}, + }; + // Expected output for each of the individual 8 64-byte messages under full double SHA256 (including padding). + static const unsigned char result_d64[256] = { + 0x09, 0x3a, 0xc4, 0xd0, 0x0f, 0xf7, 0x57, 0xe1, 0x72, 0x85, 0x79, 0x42, 0xfe, 0xe7, 0xe0, 0xa0, + 0xfc, 0x52, 0xd7, 0xdb, 0x07, 0x63, 0x45, 0xfb, 0x53, 0x14, 0x7d, 0x17, 0x22, 0x86, 0xf0, 0x52, + 0x48, 0xb6, 0x11, 0x9e, 0x6e, 0x48, 0x81, 0x6d, 0xcc, 0x57, 0x1f, 0xb2, 0x97, 0xa8, 0xd5, 0x25, + 0x9b, 0x82, 0xaa, 0x89, 0xe2, 0xfd, 0x2d, 0x56, 0xe8, 0x28, 0x83, 0x0b, 0xe2, 0xfa, 0x53, 0xb7, + 0xd6, 0x6b, 0x07, 0x85, 0x83, 0xb0, 0x10, 0xa2, 0xf5, 0x51, 0x3c, 0xf9, 0x60, 0x03, 0xab, 0x45, + 0x6c, 0x15, 0x6e, 0xef, 0xb5, 0xac, 0x3e, 0x6c, 0xdf, 0xb4, 0x92, 0x22, 0x2d, 0xce, 0xbf, 0x3e, + 0xe9, 0xe5, 0xf6, 0x29, 0x0e, 0x01, 0x4f, 0xd2, 0xd4, 0x45, 0x65, 0xb3, 0xbb, 0xf2, 0x4c, 0x16, + 0x37, 0x50, 0x3c, 0x6e, 0x49, 0x8c, 0x5a, 0x89, 0x2b, 0x1b, 0xab, 0xc4, 0x37, 0xd1, 0x46, 0xe9, + 0x3d, 0x0e, 0x85, 0xa2, 0x50, 0x73, 0xa1, 0x5e, 0x54, 0x37, 0xd7, 0x94, 0x17, 0x56, 0xc2, 0xd8, + 0xe5, 0x9f, 0xed, 0x4e, 0xae, 0x15, 0x42, 0x06, 0x0d, 0x74, 0x74, 0x5e, 0x24, 0x30, 0xce, 0xd1, + 0x9e, 0x50, 0xa3, 0x9a, 0xb8, 0xf0, 0x4a, 0x57, 0x69, 0x78, 0x67, 0x12, 0x84, 0x58, 0xbe, 0xc7, + 0x36, 0xaa, 0xee, 0x7c, 0x64, 0xa3, 0x76, 0xec, 0xff, 0x55, 0x41, 0x00, 0x2a, 0x44, 0x68, 0x4d, + 0xb6, 0x53, 0x9e, 0x1c, 0x95, 0xb7, 0xca, 0xdc, 0x7f, 0x7d, 0x74, 0x27, 0x5c, 0x8e, 0xa6, 0x84, + 0xb5, 0xac, 0x87, 0xa9, 0xf3, 0xff, 0x75, 0xf2, 0x34, 0xcd, 0x1a, 0x3b, 0x82, 0x2c, 0x2b, 0x4e, + 0x6a, 0x46, 0x30, 0xa6, 0x89, 0x86, 0x23, 0xac, 0xf8, 0xa5, 0x15, 0xe9, 0x0a, 0xaa, 0x1e, 0x9a, + 0xd7, 0x93, 0x6b, 0x28, 0xe4, 0x3b, 0xfd, 0x59, 0xc6, 0xed, 0x7c, 0x5f, 0xa5, 0x41, 0xcb, 0x51 + }; + + + // Test Transform() for 0 through 8 transformations. + for (size_t i = 0; i <= 8; ++i) { + uint32_t state[8]; + std::copy(init, init + 8, state); + Transform(state, data + 1, i); + if (!std::equal(state, state + 8, result[i])) return false; + } + // Test TransformD64 + unsigned char out[32]; + TransformD64(out, data + 1); + if (!std::equal(out, out + 32, result_d64)) return false; + + // Test TransformD64_2way, if available. + if (TransformD64_2way) { + unsigned char out[64]; + TransformD64_2way(out, data + 1); + if (!std::equal(out, out + 64, result_d64)) return false; + } + + // Test TransformD64_4way, if available. + if (TransformD64_4way) { + unsigned char out[128]; + TransformD64_4way(out, data + 1); + if (!std::equal(out, out + 128, result_d64)) return false; + } + + // Test TransformD64_8way, if available. + if (TransformD64_8way) { + unsigned char out[256]; + TransformD64_8way(out, data + 1); + if (!std::equal(out, out + 256, result_d64)) return false; + } + + return true; +} + + +#if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) +// We can't use cpuid.h's __get_cpuid as it does not support subleafs. +void inline cpuid(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) +{ +#ifdef __GNUC__ + __cpuid_count(leaf, subleaf, a, b, c, d); +#else + __asm__ ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(leaf), "2"(subleaf)); +#endif +} + +/** Check whether the OS has enabled AVX registers. */ +bool AVXEnabled() +{ + uint32_t a, d; + __asm__("xgetbv" : "=a"(a), "=d"(d) : "c"(0)); + return (a & 6) == 6; +} +#endif } // namespace + std::string SHA256AutoDetect() { -#if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__)) + std::string ret = "standard"; +#if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) + bool have_sse4 = false; + bool have_xsave = false; + bool have_avx = false; + bool have_avx2 = false; + bool have_shani = false; + bool enabled_avx = false; + + (void)AVXEnabled; + (void)have_sse4; + (void)have_avx; + (void)have_xsave; + (void)have_avx2; + (void)have_shani; + (void)enabled_avx; + uint32_t eax, ebx, ecx, edx; - if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx >> 19) & 1) { + cpuid(1, 0, eax, ebx, ecx, edx); + have_sse4 = (ecx >> 19) & 1; + have_xsave = (ecx >> 27) & 1; + have_avx = (ecx >> 28) & 1; + if (have_xsave && have_avx) { + enabled_avx = AVXEnabled(); + } + if (have_sse4) { + cpuid(7, 0, eax, ebx, ecx, edx); + have_avx2 = (ebx >> 5) & 1; + have_shani = (ebx >> 29) & 1; + } + +#if defined(ENABLE_SHANI) && !defined(BUILD_BITCOIN_INTERNAL) + if (have_shani) { + Transform = sha256_shani::Transform; + TransformD64 = TransformD64Wrapper<sha256_shani::Transform>; + TransformD64_2way = sha256d64_shani::Transform_2way; + ret = "shani(1way,2way)"; + have_sse4 = false; // Disable SSE4/AVX2; + have_avx2 = false; + } +#endif + + if (have_sse4) { +#if defined(__x86_64__) || defined(__amd64__) Transform = sha256_sse4::Transform; - assert(SelfTest(Transform)); - return "sse4"; + TransformD64 = TransformD64Wrapper<sha256_sse4::Transform>; + ret = "sse4(1way)"; +#endif +#if defined(ENABLE_SSE41) && !defined(BUILD_BITCOIN_INTERNAL) + TransformD64_4way = sha256d64_sse41::Transform_4way; + ret += ",sse41(4way)"; +#endif } + +#if defined(ENABLE_AVX2) && !defined(BUILD_BITCOIN_INTERNAL) + if (have_avx2 && have_avx && enabled_avx) { + TransformD64_8way = sha256d64_avx2::Transform_8way; + ret += ",avx2(8way)"; + } +#endif #endif - assert(SelfTest(Transform)); - return "standard"; + assert(SelfTest()); + return ret; } ////// SHA-256 @@ -247,3 +694,37 @@ CSHA256& CSHA256::Reset() sha256::Initialize(s); return *this; } + +void SHA256D64(unsigned char* out, const unsigned char* in, size_t blocks) +{ + if (TransformD64_8way) { + while (blocks >= 8) { + TransformD64_8way(out, in); + out += 256; + in += 512; + blocks -= 8; + } + } + if (TransformD64_4way) { + while (blocks >= 4) { + TransformD64_4way(out, in); + out += 128; + in += 256; + blocks -= 4; + } + } + if (TransformD64_2way) { + while (blocks >= 2) { + TransformD64_2way(out, in); + out += 64; + in += 128; + blocks -= 2; + } + } + while (blocks) { + TransformD64(out, in); + out += 32; + in += 64; + --blocks; + } +} diff --git a/src/crypto/sha256.h b/src/crypto/sha256.h index dd30fe396f..31b2b3b3df 100644 --- a/src/crypto/sha256.h +++ b/src/crypto/sha256.h @@ -31,4 +31,11 @@ public: */ std::string SHA256AutoDetect(); +/** Compute multiple double-SHA256's of 64-byte blobs. + * output: pointer to a blocks*32 byte output buffer + * input: pointer to a blocks*64 byte input buffer + * blocks: the number of hashes to compute. + */ +void SHA256D64(unsigned char* output, const unsigned char* input, size_t blocks); + #endif // BITCOIN_CRYPTO_SHA256_H diff --git a/src/crypto/sha256_avx2.cpp b/src/crypto/sha256_avx2.cpp new file mode 100644 index 0000000000..068e0e5ff6 --- /dev/null +++ b/src/crypto/sha256_avx2.cpp @@ -0,0 +1,325 @@ +#ifdef ENABLE_AVX2 + +#include <stdint.h> +#include <immintrin.h> + +#include <crypto/sha256.h> +#include <crypto/common.h> + +namespace sha256d64_avx2 { +namespace { + +__m256i inline K(uint32_t x) { return _mm256_set1_epi32(x); } + +__m256i inline Add(__m256i x, __m256i y) { return _mm256_add_epi32(x, y); } +__m256i inline Add(__m256i x, __m256i y, __m256i z) { return Add(Add(x, y), z); } +__m256i inline Add(__m256i x, __m256i y, __m256i z, __m256i w) { return Add(Add(x, y), Add(z, w)); } +__m256i inline Add(__m256i x, __m256i y, __m256i z, __m256i w, __m256i v) { return Add(Add(x, y, z), Add(w, v)); } +__m256i inline Inc(__m256i& x, __m256i y) { x = Add(x, y); return x; } +__m256i inline Inc(__m256i& x, __m256i y, __m256i z) { x = Add(x, y, z); return x; } +__m256i inline Inc(__m256i& x, __m256i y, __m256i z, __m256i w) { x = Add(x, y, z, w); return x; } +__m256i inline Xor(__m256i x, __m256i y) { return _mm256_xor_si256(x, y); } +__m256i inline Xor(__m256i x, __m256i y, __m256i z) { return Xor(Xor(x, y), z); } +__m256i inline Or(__m256i x, __m256i y) { return _mm256_or_si256(x, y); } +__m256i inline And(__m256i x, __m256i y) { return _mm256_and_si256(x, y); } +__m256i inline ShR(__m256i x, int n) { return _mm256_srli_epi32(x, n); } +__m256i inline ShL(__m256i x, int n) { return _mm256_slli_epi32(x, n); } + +__m256i inline Ch(__m256i x, __m256i y, __m256i z) { return Xor(z, And(x, Xor(y, z))); } +__m256i inline Maj(__m256i x, __m256i y, __m256i z) { return Or(And(x, y), And(z, Or(x, y))); } +__m256i inline Sigma0(__m256i x) { return Xor(Or(ShR(x, 2), ShL(x, 30)), Or(ShR(x, 13), ShL(x, 19)), Or(ShR(x, 22), ShL(x, 10))); } +__m256i inline Sigma1(__m256i x) { return Xor(Or(ShR(x, 6), ShL(x, 26)), Or(ShR(x, 11), ShL(x, 21)), Or(ShR(x, 25), ShL(x, 7))); } +__m256i inline sigma0(__m256i x) { return Xor(Or(ShR(x, 7), ShL(x, 25)), Or(ShR(x, 18), ShL(x, 14)), ShR(x, 3)); } +__m256i inline sigma1(__m256i x) { return Xor(Or(ShR(x, 17), ShL(x, 15)), Or(ShR(x, 19), ShL(x, 13)), ShR(x, 10)); } + +/** One round of SHA-256. */ +void inline __attribute__((always_inline)) Round(__m256i a, __m256i b, __m256i c, __m256i& d, __m256i e, __m256i f, __m256i g, __m256i& h, __m256i k) +{ + __m256i t1 = Add(h, Sigma1(e), Ch(e, f, g), k); + __m256i t2 = Add(Sigma0(a), Maj(a, b, c)); + d = Add(d, t1); + h = Add(t1, t2); +} + +__m256i inline Read8(const unsigned char* chunk, int offset) { + __m256i ret = _mm256_set_epi32( + ReadLE32(chunk + 0 + offset), + ReadLE32(chunk + 64 + offset), + ReadLE32(chunk + 128 + offset), + ReadLE32(chunk + 192 + offset), + ReadLE32(chunk + 256 + offset), + ReadLE32(chunk + 320 + offset), + ReadLE32(chunk + 384 + offset), + ReadLE32(chunk + 448 + offset) + ); + return _mm256_shuffle_epi8(ret, _mm256_set_epi32(0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL, 0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL)); +} + +void inline Write8(unsigned char* out, int offset, __m256i v) { + v = _mm256_shuffle_epi8(v, _mm256_set_epi32(0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL, 0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL)); + WriteLE32(out + 0 + offset, _mm256_extract_epi32(v, 7)); + WriteLE32(out + 32 + offset, _mm256_extract_epi32(v, 6)); + WriteLE32(out + 64 + offset, _mm256_extract_epi32(v, 5)); + WriteLE32(out + 96 + offset, _mm256_extract_epi32(v, 4)); + WriteLE32(out + 128 + offset, _mm256_extract_epi32(v, 3)); + WriteLE32(out + 160 + offset, _mm256_extract_epi32(v, 2)); + WriteLE32(out + 192 + offset, _mm256_extract_epi32(v, 1)); + WriteLE32(out + 224 + offset, _mm256_extract_epi32(v, 0)); +} + +} + +void Transform_8way(unsigned char* out, const unsigned char* in) +{ + // Transform 1 + __m256i a = K(0x6a09e667ul); + __m256i b = K(0xbb67ae85ul); + __m256i c = K(0x3c6ef372ul); + __m256i d = K(0xa54ff53aul); + __m256i e = K(0x510e527ful); + __m256i f = K(0x9b05688cul); + __m256i g = K(0x1f83d9abul); + __m256i h = K(0x5be0cd19ul); + + __m256i w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; + + Round(a, b, c, d, e, f, g, h, Add(K(0x428a2f98ul), w0 = Read8(in, 0))); + Round(h, a, b, c, d, e, f, g, Add(K(0x71374491ul), w1 = Read8(in, 4))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb5c0fbcful), w2 = Read8(in, 8))); + Round(f, g, h, a, b, c, d, e, Add(K(0xe9b5dba5ul), w3 = Read8(in, 12))); + Round(e, f, g, h, a, b, c, d, Add(K(0x3956c25bul), w4 = Read8(in, 16))); + Round(d, e, f, g, h, a, b, c, Add(K(0x59f111f1ul), w5 = Read8(in, 20))); + Round(c, d, e, f, g, h, a, b, Add(K(0x923f82a4ul), w6 = Read8(in, 24))); + Round(b, c, d, e, f, g, h, a, Add(K(0xab1c5ed5ul), w7 = Read8(in, 28))); + Round(a, b, c, d, e, f, g, h, Add(K(0xd807aa98ul), w8 = Read8(in, 32))); + Round(h, a, b, c, d, e, f, g, Add(K(0x12835b01ul), w9 = Read8(in, 36))); + Round(g, h, a, b, c, d, e, f, Add(K(0x243185beul), w10 = Read8(in, 40))); + Round(f, g, h, a, b, c, d, e, Add(K(0x550c7dc3ul), w11 = Read8(in, 44))); + Round(e, f, g, h, a, b, c, d, Add(K(0x72be5d74ul), w12 = Read8(in, 48))); + Round(d, e, f, g, h, a, b, c, Add(K(0x80deb1feul), w13 = Read8(in, 52))); + Round(c, d, e, f, g, h, a, b, Add(K(0x9bdc06a7ul), w14 = Read8(in, 56))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc19bf174ul), w15 = Read8(in, 60))); + Round(a, b, c, d, e, f, g, h, Add(K(0xe49b69c1ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xefbe4786ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x0fc19dc6ul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x240ca1ccul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x2de92c6ful), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4a7484aaul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5cb0a9dcul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x76f988daul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x983e5152ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa831c66dul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb00327c8ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xbf597fc7ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xc6e00bf3ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd5a79147ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x06ca6351ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x14292967ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x27b70a85ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x2e1b2138ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x4d2c6dfcul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x53380d13ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x650a7354ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x766a0abbul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x81c2c92eul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x92722c85ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0xa2bfe8a1ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa81a664bul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xc24b8b70ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xc76c51a3ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xd192e819ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd6990624ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xf40e3585ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x106aa070ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x19a4c116ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x1e376c08ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x2748774cul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x34b0bcb5ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x391c0cb3ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4ed8aa4aul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5b9cca4ful), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x682e6ff3ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x748f82eeul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x78a5636ful), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x84c87814ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x8cc70208ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x90befffaul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xa4506cebul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xbef9a3f7ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc67178f2ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + + a = Add(a, K(0x6a09e667ul)); + b = Add(b, K(0xbb67ae85ul)); + c = Add(c, K(0x3c6ef372ul)); + d = Add(d, K(0xa54ff53aul)); + e = Add(e, K(0x510e527ful)); + f = Add(f, K(0x9b05688cul)); + g = Add(g, K(0x1f83d9abul)); + h = Add(h, K(0x5be0cd19ul)); + + __m256i t0 = a, t1 = b, t2 = c, t3 = d, t4 = e, t5 = f, t6 = g, t7 = h; + + // Transform 2 + Round(a, b, c, d, e, f, g, h, K(0xc28a2f98ul)); + Round(h, a, b, c, d, e, f, g, K(0x71374491ul)); + Round(g, h, a, b, c, d, e, f, K(0xb5c0fbcful)); + Round(f, g, h, a, b, c, d, e, K(0xe9b5dba5ul)); + Round(e, f, g, h, a, b, c, d, K(0x3956c25bul)); + Round(d, e, f, g, h, a, b, c, K(0x59f111f1ul)); + Round(c, d, e, f, g, h, a, b, K(0x923f82a4ul)); + Round(b, c, d, e, f, g, h, a, K(0xab1c5ed5ul)); + Round(a, b, c, d, e, f, g, h, K(0xd807aa98ul)); + Round(h, a, b, c, d, e, f, g, K(0x12835b01ul)); + Round(g, h, a, b, c, d, e, f, K(0x243185beul)); + Round(f, g, h, a, b, c, d, e, K(0x550c7dc3ul)); + Round(e, f, g, h, a, b, c, d, K(0x72be5d74ul)); + Round(d, e, f, g, h, a, b, c, K(0x80deb1feul)); + Round(c, d, e, f, g, h, a, b, K(0x9bdc06a7ul)); + Round(b, c, d, e, f, g, h, a, K(0xc19bf374ul)); + Round(a, b, c, d, e, f, g, h, K(0x649b69c1ul)); + Round(h, a, b, c, d, e, f, g, K(0xf0fe4786ul)); + Round(g, h, a, b, c, d, e, f, K(0x0fe1edc6ul)); + Round(f, g, h, a, b, c, d, e, K(0x240cf254ul)); + Round(e, f, g, h, a, b, c, d, K(0x4fe9346ful)); + Round(d, e, f, g, h, a, b, c, K(0x6cc984beul)); + Round(c, d, e, f, g, h, a, b, K(0x61b9411eul)); + Round(b, c, d, e, f, g, h, a, K(0x16f988faul)); + Round(a, b, c, d, e, f, g, h, K(0xf2c65152ul)); + Round(h, a, b, c, d, e, f, g, K(0xa88e5a6dul)); + Round(g, h, a, b, c, d, e, f, K(0xb019fc65ul)); + Round(f, g, h, a, b, c, d, e, K(0xb9d99ec7ul)); + Round(e, f, g, h, a, b, c, d, K(0x9a1231c3ul)); + Round(d, e, f, g, h, a, b, c, K(0xe70eeaa0ul)); + Round(c, d, e, f, g, h, a, b, K(0xfdb1232bul)); + Round(b, c, d, e, f, g, h, a, K(0xc7353eb0ul)); + Round(a, b, c, d, e, f, g, h, K(0x3069bad5ul)); + Round(h, a, b, c, d, e, f, g, K(0xcb976d5ful)); + Round(g, h, a, b, c, d, e, f, K(0x5a0f118ful)); + Round(f, g, h, a, b, c, d, e, K(0xdc1eeefdul)); + Round(e, f, g, h, a, b, c, d, K(0x0a35b689ul)); + Round(d, e, f, g, h, a, b, c, K(0xde0b7a04ul)); + Round(c, d, e, f, g, h, a, b, K(0x58f4ca9dul)); + Round(b, c, d, e, f, g, h, a, K(0xe15d5b16ul)); + Round(a, b, c, d, e, f, g, h, K(0x007f3e86ul)); + Round(h, a, b, c, d, e, f, g, K(0x37088980ul)); + Round(g, h, a, b, c, d, e, f, K(0xa507ea32ul)); + Round(f, g, h, a, b, c, d, e, K(0x6fab9537ul)); + Round(e, f, g, h, a, b, c, d, K(0x17406110ul)); + Round(d, e, f, g, h, a, b, c, K(0x0d8cd6f1ul)); + Round(c, d, e, f, g, h, a, b, K(0xcdaa3b6dul)); + Round(b, c, d, e, f, g, h, a, K(0xc0bbbe37ul)); + Round(a, b, c, d, e, f, g, h, K(0x83613bdaul)); + Round(h, a, b, c, d, e, f, g, K(0xdb48a363ul)); + Round(g, h, a, b, c, d, e, f, K(0x0b02e931ul)); + Round(f, g, h, a, b, c, d, e, K(0x6fd15ca7ul)); + Round(e, f, g, h, a, b, c, d, K(0x521afacaul)); + Round(d, e, f, g, h, a, b, c, K(0x31338431ul)); + Round(c, d, e, f, g, h, a, b, K(0x6ed41a95ul)); + Round(b, c, d, e, f, g, h, a, K(0x6d437890ul)); + Round(a, b, c, d, e, f, g, h, K(0xc39c91f2ul)); + Round(h, a, b, c, d, e, f, g, K(0x9eccabbdul)); + Round(g, h, a, b, c, d, e, f, K(0xb5c9a0e6ul)); + Round(f, g, h, a, b, c, d, e, K(0x532fb63cul)); + Round(e, f, g, h, a, b, c, d, K(0xd2c741c6ul)); + Round(d, e, f, g, h, a, b, c, K(0x07237ea3ul)); + Round(c, d, e, f, g, h, a, b, K(0xa4954b68ul)); + Round(b, c, d, e, f, g, h, a, K(0x4c191d76ul)); + + w0 = Add(t0, a); + w1 = Add(t1, b); + w2 = Add(t2, c); + w3 = Add(t3, d); + w4 = Add(t4, e); + w5 = Add(t5, f); + w6 = Add(t6, g); + w7 = Add(t7, h); + + // Transform 3 + a = K(0x6a09e667ul); + b = K(0xbb67ae85ul); + c = K(0x3c6ef372ul); + d = K(0xa54ff53aul); + e = K(0x510e527ful); + f = K(0x9b05688cul); + g = K(0x1f83d9abul); + h = K(0x5be0cd19ul); + + Round(a, b, c, d, e, f, g, h, Add(K(0x428a2f98ul), w0)); + Round(h, a, b, c, d, e, f, g, Add(K(0x71374491ul), w1)); + Round(g, h, a, b, c, d, e, f, Add(K(0xb5c0fbcful), w2)); + Round(f, g, h, a, b, c, d, e, Add(K(0xe9b5dba5ul), w3)); + Round(e, f, g, h, a, b, c, d, Add(K(0x3956c25bul), w4)); + Round(d, e, f, g, h, a, b, c, Add(K(0x59f111f1ul), w5)); + Round(c, d, e, f, g, h, a, b, Add(K(0x923f82a4ul), w6)); + Round(b, c, d, e, f, g, h, a, Add(K(0xab1c5ed5ul), w7)); + Round(a, b, c, d, e, f, g, h, K(0x5807aa98ul)); + Round(h, a, b, c, d, e, f, g, K(0x12835b01ul)); + Round(g, h, a, b, c, d, e, f, K(0x243185beul)); + Round(f, g, h, a, b, c, d, e, K(0x550c7dc3ul)); + Round(e, f, g, h, a, b, c, d, K(0x72be5d74ul)); + Round(d, e, f, g, h, a, b, c, K(0x80deb1feul)); + Round(c, d, e, f, g, h, a, b, K(0x9bdc06a7ul)); + Round(b, c, d, e, f, g, h, a, K(0xc19bf274ul)); + Round(a, b, c, d, e, f, g, h, Add(K(0xe49b69c1ul), Inc(w0, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xefbe4786ul), Inc(w1, K(0xa00000ul), sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x0fc19dc6ul), Inc(w2, sigma1(w0), sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x240ca1ccul), Inc(w3, sigma1(w1), sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x2de92c6ful), Inc(w4, sigma1(w2), sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4a7484aaul), Inc(w5, sigma1(w3), sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5cb0a9dcul), Inc(w6, sigma1(w4), K(0x100ul), sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x76f988daul), Inc(w7, sigma1(w5), w0, K(0x11002000ul)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x983e5152ul), w8 = Add(K(0x80000000ul), sigma1(w6), w1))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa831c66dul), w9 = Add(sigma1(w7), w2))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb00327c8ul), w10 = Add(sigma1(w8), w3))); + Round(f, g, h, a, b, c, d, e, Add(K(0xbf597fc7ul), w11 = Add(sigma1(w9), w4))); + Round(e, f, g, h, a, b, c, d, Add(K(0xc6e00bf3ul), w12 = Add(sigma1(w10), w5))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd5a79147ul), w13 = Add(sigma1(w11), w6))); + Round(c, d, e, f, g, h, a, b, Add(K(0x06ca6351ul), w14 = Add(sigma1(w12), w7, K(0x400022ul)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x14292967ul), w15 = Add(K(0x100ul), sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x27b70a85ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x2e1b2138ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x4d2c6dfcul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x53380d13ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x650a7354ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x766a0abbul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x81c2c92eul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x92722c85ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0xa2bfe8a1ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa81a664bul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xc24b8b70ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xc76c51a3ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xd192e819ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd6990624ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xf40e3585ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x106aa070ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x19a4c116ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x1e376c08ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x2748774cul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x34b0bcb5ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x391c0cb3ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4ed8aa4aul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5b9cca4ful), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x682e6ff3ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x748f82eeul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x78a5636ful), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x84c87814ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x8cc70208ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x90befffaul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xa4506cebul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xbef9a3f7ul), w14, sigma1(w12), w7, sigma0(w15))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc67178f2ul), w15, sigma1(w13), w8, sigma0(w0))); + + // Output + Write8(out, 0, Add(a, K(0x6a09e667ul))); + Write8(out, 4, Add(b, K(0xbb67ae85ul))); + Write8(out, 8, Add(c, K(0x3c6ef372ul))); + Write8(out, 12, Add(d, K(0xa54ff53aul))); + Write8(out, 16, Add(e, K(0x510e527ful))); + Write8(out, 20, Add(f, K(0x9b05688cul))); + Write8(out, 24, Add(g, K(0x1f83d9abul))); + Write8(out, 28, Add(h, K(0x5be0cd19ul))); +} + +} + +#endif diff --git a/src/crypto/sha256_shani.cpp b/src/crypto/sha256_shani.cpp new file mode 100644 index 0000000000..e561da42c5 --- /dev/null +++ b/src/crypto/sha256_shani.cpp @@ -0,0 +1,359 @@ +// 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. +// +// Based on https://github.com/noloader/SHA-Intrinsics/blob/master/sha256-x86.c, +// Written and placed in public domain by Jeffrey Walton. +// Based on code from Intel, and by Sean Gulley for the miTLS project. + +#ifdef ENABLE_SHANI + +#include <stdint.h> +#include <immintrin.h> + +#include <crypto/common.h> + + +namespace { + +const __m128i MASK = _mm_set_epi64x(0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); +const __m128i INIT0 = _mm_set_epi64x(0x6a09e667bb67ae85ull, 0x510e527f9b05688cull); +const __m128i INIT1 = _mm_set_epi64x(0x3c6ef372a54ff53aull, 0x1f83d9ab5be0cd19ull); + +void inline __attribute__((always_inline)) QuadRound(__m128i& state0, __m128i& state1, uint64_t k1, uint64_t k0) +{ + const __m128i msg = _mm_set_epi64x(k1, k0); + state1 = _mm_sha256rnds2_epu32(state1, state0, msg); + state0 = _mm_sha256rnds2_epu32(state0, state1, _mm_shuffle_epi32(msg, 0x0e)); +} + +void inline __attribute__((always_inline)) QuadRound(__m128i& state0, __m128i& state1, __m128i m, uint64_t k1, uint64_t k0) +{ + const __m128i msg = _mm_add_epi32(m, _mm_set_epi64x(k1, k0)); + state1 = _mm_sha256rnds2_epu32(state1, state0, msg); + state0 = _mm_sha256rnds2_epu32(state0, state1, _mm_shuffle_epi32(msg, 0x0e)); +} + +void inline __attribute__((always_inline)) ShiftMessageA(__m128i& m0, __m128i m1) +{ + m0 = _mm_sha256msg1_epu32(m0, m1); +} + +void inline __attribute__((always_inline)) ShiftMessageC(__m128i& m0, __m128i m1, __m128i& m2) +{ + m2 = _mm_sha256msg2_epu32(_mm_add_epi32(m2, _mm_alignr_epi8(m1, m0, 4)), m1); +} + +void inline __attribute__((always_inline)) ShiftMessageB(__m128i& m0, __m128i m1, __m128i& m2) +{ + ShiftMessageC(m0, m1, m2); + ShiftMessageA(m0, m1); +} + +void inline __attribute__((always_inline)) Shuffle(__m128i& s0, __m128i& s1) +{ + const __m128i t1 = _mm_shuffle_epi32(s0, 0xB1); + const __m128i t2 = _mm_shuffle_epi32(s1, 0x1B); + s0 = _mm_alignr_epi8(t1, t2, 0x08); + s1 = _mm_blend_epi16(t2, t1, 0xF0); +} + +void inline __attribute__((always_inline)) Unshuffle(__m128i& s0, __m128i& s1) +{ + const __m128i t1 = _mm_shuffle_epi32(s0, 0x1B); + const __m128i t2 = _mm_shuffle_epi32(s1, 0xB1); + s0 = _mm_blend_epi16(t1, t2, 0xF0); + s1 = _mm_alignr_epi8(t2, t1, 0x08); +} + +__m128i inline __attribute__((always_inline)) Load(const unsigned char* in) +{ + return _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)in), MASK); +} + +void inline __attribute__((always_inline)) Save(unsigned char* out, __m128i s) +{ + _mm_storeu_si128((__m128i*)out, _mm_shuffle_epi8(s, MASK)); +} +} + +namespace sha256_shani { +void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks) +{ + __m128i m0, m1, m2, m3, s0, s1, so0, so1; + + /* Load state */ + s0 = _mm_loadu_si128((const __m128i*)s); + s1 = _mm_loadu_si128((const __m128i*)(s + 4)); + Shuffle(s0, s1); + + while (blocks--) { + /* Remember old state */ + so0 = s0; + so1 = s1; + + /* Load data and transform */ + m0 = Load(chunk); + QuadRound(s0, s1, m0, 0xe9b5dba5b5c0fbcfull, 0x71374491428a2f98ull); + m1 = Load(chunk + 16); + QuadRound(s0, s1, m1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + ShiftMessageA(m0, m1); + m2 = Load(chunk + 32); + QuadRound(s0, s1, m2, 0x550c7dc3243185beull, 0x12835b01d807aa98ull); + ShiftMessageA(m1, m2); + m3 = Load(chunk + 48); + QuadRound(s0, s1, m3, 0xc19bf1749bdc06a7ull, 0x80deb1fe72be5d74ull); + ShiftMessageB(m2, m3, m0); + QuadRound(s0, s1, m0, 0x240ca1cc0fc19dc6ull, 0xefbe4786E49b69c1ull); + ShiftMessageB(m3, m0, m1); + QuadRound(s0, s1, m1, 0x76f988da5cb0a9dcull, 0x4a7484aa2de92c6full); + ShiftMessageB(m0, m1, m2); + QuadRound(s0, s1, m2, 0xbf597fc7b00327c8ull, 0xa831c66d983e5152ull); + ShiftMessageB(m1, m2, m3); + QuadRound(s0, s1, m3, 0x1429296706ca6351ull, 0xd5a79147c6e00bf3ull); + ShiftMessageB(m2, m3, m0); + QuadRound(s0, s1, m0, 0x53380d134d2c6dfcull, 0x2e1b213827b70a85ull); + ShiftMessageB(m3, m0, m1); + QuadRound(s0, s1, m1, 0x92722c8581c2c92eull, 0x766a0abb650a7354ull); + ShiftMessageB(m0, m1, m2); + QuadRound(s0, s1, m2, 0xc76c51A3c24b8b70ull, 0xa81a664ba2bfe8a1ull); + ShiftMessageB(m1, m2, m3); + QuadRound(s0, s1, m3, 0x106aa070f40e3585ull, 0xd6990624d192e819ull); + ShiftMessageB(m2, m3, m0); + QuadRound(s0, s1, m0, 0x34b0bcb52748774cull, 0x1e376c0819a4c116ull); + ShiftMessageB(m3, m0, m1); + QuadRound(s0, s1, m1, 0x682e6ff35b9cca4full, 0x4ed8aa4a391c0cb3ull); + ShiftMessageC(m0, m1, m2); + QuadRound(s0, s1, m2, 0x8cc7020884c87814ull, 0x78a5636f748f82eeull); + ShiftMessageC(m1, m2, m3); + QuadRound(s0, s1, m3, 0xc67178f2bef9A3f7ull, 0xa4506ceb90befffaull); + + /* Combine with old state */ + s0 = _mm_add_epi32(s0, so0); + s1 = _mm_add_epi32(s1, so1); + + /* Advance */ + chunk += 64; + } + + Unshuffle(s0, s1); + _mm_storeu_si128((__m128i*)s, s0); + _mm_storeu_si128((__m128i*)(s + 4), s1); +} +} + +namespace sha256d64_shani { + +void Transform_2way(unsigned char* out, const unsigned char* in) +{ + __m128i am0, am1, am2, am3, as0, as1, aso0, aso1; + __m128i bm0, bm1, bm2, bm3, bs0, bs1, bso0, bso1; + + /* Transform 1 */ + bs0 = as0 = INIT0; + bs1 = as1 = INIT1; + am0 = Load(in); + bm0 = Load(in + 64); + QuadRound(as0, as1, am0, 0xe9b5dba5b5c0fbcfull, 0x71374491428a2f98ull); + QuadRound(bs0, bs1, bm0, 0xe9b5dba5b5c0fbcfull, 0x71374491428a2f98ull); + am1 = Load(in + 16); + bm1 = Load(in + 80); + QuadRound(as0, as1, am1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + QuadRound(bs0, bs1, bm1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + ShiftMessageA(am0, am1); + ShiftMessageA(bm0, bm1); + am2 = Load(in + 32); + bm2 = Load(in + 96); + QuadRound(as0, as1, am2, 0x550c7dc3243185beull, 0x12835b01d807aa98ull); + QuadRound(bs0, bs1, bm2, 0x550c7dc3243185beull, 0x12835b01d807aa98ull); + ShiftMessageA(am1, am2); + ShiftMessageA(bm1, bm2); + am3 = Load(in + 48); + bm3 = Load(in + 112); + QuadRound(as0, as1, am3, 0xc19bf1749bdc06a7ull, 0x80deb1fe72be5d74ull); + QuadRound(bs0, bs1, bm3, 0xc19bf1749bdc06a7ull, 0x80deb1fe72be5d74ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x240ca1cc0fc19dc6ull, 0xefbe4786E49b69c1ull); + QuadRound(bs0, bs1, bm0, 0x240ca1cc0fc19dc6ull, 0xefbe4786E49b69c1ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x76f988da5cb0a9dcull, 0x4a7484aa2de92c6full); + QuadRound(bs0, bs1, bm1, 0x76f988da5cb0a9dcull, 0x4a7484aa2de92c6full); + ShiftMessageB(am0, am1, am2); + ShiftMessageB(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0xbf597fc7b00327c8ull, 0xa831c66d983e5152ull); + QuadRound(bs0, bs1, bm2, 0xbf597fc7b00327c8ull, 0xa831c66d983e5152ull); + ShiftMessageB(am1, am2, am3); + ShiftMessageB(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0x1429296706ca6351ull, 0xd5a79147c6e00bf3ull); + QuadRound(bs0, bs1, bm3, 0x1429296706ca6351ull, 0xd5a79147c6e00bf3ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x53380d134d2c6dfcull, 0x2e1b213827b70a85ull); + QuadRound(bs0, bs1, bm0, 0x53380d134d2c6dfcull, 0x2e1b213827b70a85ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x92722c8581c2c92eull, 0x766a0abb650a7354ull); + QuadRound(bs0, bs1, bm1, 0x92722c8581c2c92eull, 0x766a0abb650a7354ull); + ShiftMessageB(am0, am1, am2); + ShiftMessageB(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0xc76c51A3c24b8b70ull, 0xa81a664ba2bfe8a1ull); + QuadRound(bs0, bs1, bm2, 0xc76c51A3c24b8b70ull, 0xa81a664ba2bfe8a1ull); + ShiftMessageB(am1, am2, am3); + ShiftMessageB(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0x106aa070f40e3585ull, 0xd6990624d192e819ull); + QuadRound(bs0, bs1, bm3, 0x106aa070f40e3585ull, 0xd6990624d192e819ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x34b0bcb52748774cull, 0x1e376c0819a4c116ull); + QuadRound(bs0, bs1, bm0, 0x34b0bcb52748774cull, 0x1e376c0819a4c116ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x682e6ff35b9cca4full, 0x4ed8aa4a391c0cb3ull); + QuadRound(bs0, bs1, bm1, 0x682e6ff35b9cca4full, 0x4ed8aa4a391c0cb3ull); + ShiftMessageC(am0, am1, am2); + ShiftMessageC(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0x8cc7020884c87814ull, 0x78a5636f748f82eeull); + QuadRound(bs0, bs1, bm2, 0x8cc7020884c87814ull, 0x78a5636f748f82eeull); + ShiftMessageC(am1, am2, am3); + ShiftMessageC(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0xc67178f2bef9A3f7ull, 0xa4506ceb90befffaull); + QuadRound(bs0, bs1, bm3, 0xc67178f2bef9A3f7ull, 0xa4506ceb90befffaull); + as0 = _mm_add_epi32(as0, INIT0); + bs0 = _mm_add_epi32(bs0, INIT0); + as1 = _mm_add_epi32(as1, INIT1); + bs1 = _mm_add_epi32(bs1, INIT1); + + /* Transform 2 */ + aso0 = as0; + bso0 = bs0; + aso1 = as1; + bso1 = bs1; + QuadRound(as0, as1, 0xe9b5dba5b5c0fbcfull, 0x71374491c28a2f98ull); + QuadRound(bs0, bs1, 0xe9b5dba5b5c0fbcfull, 0x71374491c28a2f98ull); + QuadRound(as0, as1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + QuadRound(bs0, bs1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + QuadRound(as0, as1, 0x550c7dc3243185beull, 0x12835b01d807aa98ull); + QuadRound(bs0, bs1, 0x550c7dc3243185beull, 0x12835b01d807aa98ull); + QuadRound(as0, as1, 0xc19bf3749bdc06a7ull, 0x80deb1fe72be5d74ull); + QuadRound(bs0, bs1, 0xc19bf3749bdc06a7ull, 0x80deb1fe72be5d74ull); + QuadRound(as0, as1, 0x240cf2540fe1edc6ull, 0xf0fe4786649b69c1ull); + QuadRound(bs0, bs1, 0x240cf2540fe1edc6ull, 0xf0fe4786649b69c1ull); + QuadRound(as0, as1, 0x16f988fa61b9411eull, 0x6cc984be4fe9346full); + QuadRound(bs0, bs1, 0x16f988fa61b9411eull, 0x6cc984be4fe9346full); + QuadRound(as0, as1, 0xb9d99ec7b019fc65ull, 0xa88e5a6df2c65152ull); + QuadRound(bs0, bs1, 0xb9d99ec7b019fc65ull, 0xa88e5a6df2c65152ull); + QuadRound(as0, as1, 0xc7353eb0fdb1232bull, 0xe70eeaa09a1231c3ull); + QuadRound(bs0, bs1, 0xc7353eb0fdb1232bull, 0xe70eeaa09a1231c3ull); + QuadRound(as0, as1, 0xdc1eeefd5a0f118full, 0xcb976d5f3069bad5ull); + QuadRound(bs0, bs1, 0xdc1eeefd5a0f118full, 0xcb976d5f3069bad5ull); + QuadRound(as0, as1, 0xe15d5b1658f4ca9dull, 0xde0b7a040a35b689ull); + QuadRound(bs0, bs1, 0xe15d5b1658f4ca9dull, 0xde0b7a040a35b689ull); + QuadRound(as0, as1, 0x6fab9537a507ea32ull, 0x37088980007f3e86ull); + QuadRound(bs0, bs1, 0x6fab9537a507ea32ull, 0x37088980007f3e86ull); + QuadRound(as0, as1, 0xc0bbbe37cdaa3b6dull, 0x0d8cd6f117406110ull); + QuadRound(bs0, bs1, 0xc0bbbe37cdaa3b6dull, 0x0d8cd6f117406110ull); + QuadRound(as0, as1, 0x6fd15ca70b02e931ull, 0xdb48a36383613bdaull); + QuadRound(bs0, bs1, 0x6fd15ca70b02e931ull, 0xdb48a36383613bdaull); + QuadRound(as0, as1, 0x6d4378906ed41a95ull, 0x31338431521afacaull); + QuadRound(bs0, bs1, 0x6d4378906ed41a95ull, 0x31338431521afacaull); + QuadRound(as0, as1, 0x532fb63cb5c9a0e6ull, 0x9eccabbdc39c91f2ull); + QuadRound(bs0, bs1, 0x532fb63cb5c9a0e6ull, 0x9eccabbdc39c91f2ull); + QuadRound(as0, as1, 0x4c191d76a4954b68ull, 0x07237ea3d2c741c6ull); + QuadRound(bs0, bs1, 0x4c191d76a4954b68ull, 0x07237ea3d2c741c6ull); + as0 = _mm_add_epi32(as0, aso0); + bs0 = _mm_add_epi32(bs0, bso0); + as1 = _mm_add_epi32(as1, aso1); + bs1 = _mm_add_epi32(bs1, bso1); + + /* Extract hash */ + Unshuffle(as0, as1); + Unshuffle(bs0, bs1); + am0 = as0; + bm0 = bs0; + am1 = as1; + bm1 = bs1; + + /* Transform 3 */ + bs0 = as0 = INIT0; + bs1 = as1 = INIT1; + QuadRound(as0, as1, am0, 0xe9b5dba5B5c0fbcfull, 0x71374491428a2f98ull); + QuadRound(bs0, bs1, bm0, 0xe9b5dba5B5c0fbcfull, 0x71374491428a2f98ull); + QuadRound(as0, as1, am1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + QuadRound(bs0, bs1, bm1, 0xab1c5ed5923f82a4ull, 0x59f111f13956c25bull); + ShiftMessageA(am0, am1); + ShiftMessageA(bm0, bm1); + bm2 = am2 = _mm_set_epi64x(0x0ull, 0x80000000ull); + QuadRound(as0, as1, 0x550c7dc3243185beull, 0x12835b015807aa98ull); + QuadRound(bs0, bs1, 0x550c7dc3243185beull, 0x12835b015807aa98ull); + ShiftMessageA(am1, am2); + ShiftMessageA(bm1, bm2); + bm3 = am3 = _mm_set_epi64x(0x10000000000ull, 0x0ull); + QuadRound(as0, as1, 0xc19bf2749bdc06a7ull, 0x80deb1fe72be5d74ull); + QuadRound(bs0, bs1, 0xc19bf2749bdc06a7ull, 0x80deb1fe72be5d74ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x240ca1cc0fc19dc6ull, 0xefbe4786e49b69c1ull); + QuadRound(bs0, bs1, bm0, 0x240ca1cc0fc19dc6ull, 0xefbe4786e49b69c1ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x76f988da5cb0a9dcull, 0x4a7484aa2de92c6full); + QuadRound(bs0, bs1, bm1, 0x76f988da5cb0a9dcull, 0x4a7484aa2de92c6full); + ShiftMessageB(am0, am1, am2); + ShiftMessageB(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0xbf597fc7b00327c8ull, 0xa831c66d983e5152ull); + QuadRound(bs0, bs1, bm2, 0xbf597fc7b00327c8ull, 0xa831c66d983e5152ull); + ShiftMessageB(am1, am2, am3); + ShiftMessageB(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0x1429296706ca6351ull, 0xd5a79147c6e00bf3ull); + QuadRound(bs0, bs1, bm3, 0x1429296706ca6351ull, 0xd5a79147c6e00bf3ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x53380d134d2c6dfcull, 0x2e1b213827b70a85ull); + QuadRound(bs0, bs1, bm0, 0x53380d134d2c6dfcull, 0x2e1b213827b70a85ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x92722c8581c2c92eull, 0x766a0abb650a7354ull); + QuadRound(bs0, bs1, bm1, 0x92722c8581c2c92eull, 0x766a0abb650a7354ull); + ShiftMessageB(am0, am1, am2); + ShiftMessageB(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0xc76c51a3c24b8b70ull, 0xa81a664ba2bfe8A1ull); + QuadRound(bs0, bs1, bm2, 0xc76c51a3c24b8b70ull, 0xa81a664ba2bfe8A1ull); + ShiftMessageB(am1, am2, am3); + ShiftMessageB(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0x106aa070f40e3585ull, 0xd6990624d192e819ull); + QuadRound(bs0, bs1, bm3, 0x106aa070f40e3585ull, 0xd6990624d192e819ull); + ShiftMessageB(am2, am3, am0); + ShiftMessageB(bm2, bm3, bm0); + QuadRound(as0, as1, am0, 0x34b0bcb52748774cull, 0x1e376c0819a4c116ull); + QuadRound(bs0, bs1, bm0, 0x34b0bcb52748774cull, 0x1e376c0819a4c116ull); + ShiftMessageB(am3, am0, am1); + ShiftMessageB(bm3, bm0, bm1); + QuadRound(as0, as1, am1, 0x682e6ff35b9cca4full, 0x4ed8aa4a391c0cb3ull); + QuadRound(bs0, bs1, bm1, 0x682e6ff35b9cca4full, 0x4ed8aa4a391c0cb3ull); + ShiftMessageC(am0, am1, am2); + ShiftMessageC(bm0, bm1, bm2); + QuadRound(as0, as1, am2, 0x8cc7020884c87814ull, 0x78a5636f748f82eeull); + QuadRound(bs0, bs1, bm2, 0x8cc7020884c87814ull, 0x78a5636f748f82eeull); + ShiftMessageC(am1, am2, am3); + ShiftMessageC(bm1, bm2, bm3); + QuadRound(as0, as1, am3, 0xc67178f2bef9a3f7ull, 0xa4506ceb90befffaull); + QuadRound(bs0, bs1, bm3, 0xc67178f2bef9a3f7ull, 0xa4506ceb90befffaull); + as0 = _mm_add_epi32(as0, INIT0); + bs0 = _mm_add_epi32(bs0, INIT0); + as1 = _mm_add_epi32(as1, INIT1); + bs1 = _mm_add_epi32(bs1, INIT1); + + /* Extract hash into out */ + Unshuffle(as0, as1); + Unshuffle(bs0, bs1); + Save(out, as0); + Save(out + 16, as1); + Save(out + 32, bs0); + Save(out + 48, bs1); +} + +} + +#endif diff --git a/src/crypto/sha256_sse41.cpp b/src/crypto/sha256_sse41.cpp new file mode 100644 index 0000000000..adca870e2d --- /dev/null +++ b/src/crypto/sha256_sse41.cpp @@ -0,0 +1,317 @@ +#ifdef ENABLE_SSE41 + +#include <stdint.h> +#include <immintrin.h> + +#include <crypto/sha256.h> +#include <crypto/common.h> + +namespace sha256d64_sse41 { +namespace { + +__m128i inline K(uint32_t x) { return _mm_set1_epi32(x); } + +__m128i inline Add(__m128i x, __m128i y) { return _mm_add_epi32(x, y); } +__m128i inline Add(__m128i x, __m128i y, __m128i z) { return Add(Add(x, y), z); } +__m128i inline Add(__m128i x, __m128i y, __m128i z, __m128i w) { return Add(Add(x, y), Add(z, w)); } +__m128i inline Add(__m128i x, __m128i y, __m128i z, __m128i w, __m128i v) { return Add(Add(x, y, z), Add(w, v)); } +__m128i inline Inc(__m128i& x, __m128i y) { x = Add(x, y); return x; } +__m128i inline Inc(__m128i& x, __m128i y, __m128i z) { x = Add(x, y, z); return x; } +__m128i inline Inc(__m128i& x, __m128i y, __m128i z, __m128i w) { x = Add(x, y, z, w); return x; } +__m128i inline Xor(__m128i x, __m128i y) { return _mm_xor_si128(x, y); } +__m128i inline Xor(__m128i x, __m128i y, __m128i z) { return Xor(Xor(x, y), z); } +__m128i inline Or(__m128i x, __m128i y) { return _mm_or_si128(x, y); } +__m128i inline And(__m128i x, __m128i y) { return _mm_and_si128(x, y); } +__m128i inline ShR(__m128i x, int n) { return _mm_srli_epi32(x, n); } +__m128i inline ShL(__m128i x, int n) { return _mm_slli_epi32(x, n); } + +__m128i inline Ch(__m128i x, __m128i y, __m128i z) { return Xor(z, And(x, Xor(y, z))); } +__m128i inline Maj(__m128i x, __m128i y, __m128i z) { return Or(And(x, y), And(z, Or(x, y))); } +__m128i inline Sigma0(__m128i x) { return Xor(Or(ShR(x, 2), ShL(x, 30)), Or(ShR(x, 13), ShL(x, 19)), Or(ShR(x, 22), ShL(x, 10))); } +__m128i inline Sigma1(__m128i x) { return Xor(Or(ShR(x, 6), ShL(x, 26)), Or(ShR(x, 11), ShL(x, 21)), Or(ShR(x, 25), ShL(x, 7))); } +__m128i inline sigma0(__m128i x) { return Xor(Or(ShR(x, 7), ShL(x, 25)), Or(ShR(x, 18), ShL(x, 14)), ShR(x, 3)); } +__m128i inline sigma1(__m128i x) { return Xor(Or(ShR(x, 17), ShL(x, 15)), Or(ShR(x, 19), ShL(x, 13)), ShR(x, 10)); } + +/** One round of SHA-256. */ +void inline __attribute__((always_inline)) Round(__m128i a, __m128i b, __m128i c, __m128i& d, __m128i e, __m128i f, __m128i g, __m128i& h, __m128i k) +{ + __m128i t1 = Add(h, Sigma1(e), Ch(e, f, g), k); + __m128i t2 = Add(Sigma0(a), Maj(a, b, c)); + d = Add(d, t1); + h = Add(t1, t2); +} + +__m128i inline Read4(const unsigned char* chunk, int offset) { + __m128i ret = _mm_set_epi32( + ReadLE32(chunk + 0 + offset), + ReadLE32(chunk + 64 + offset), + ReadLE32(chunk + 128 + offset), + ReadLE32(chunk + 192 + offset) + ); + return _mm_shuffle_epi8(ret, _mm_set_epi32(0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL)); +} + +void inline Write4(unsigned char* out, int offset, __m128i v) { + v = _mm_shuffle_epi8(v, _mm_set_epi32(0x0C0D0E0FUL, 0x08090A0BUL, 0x04050607UL, 0x00010203UL)); + WriteLE32(out + 0 + offset, _mm_extract_epi32(v, 3)); + WriteLE32(out + 32 + offset, _mm_extract_epi32(v, 2)); + WriteLE32(out + 64 + offset, _mm_extract_epi32(v, 1)); + WriteLE32(out + 96 + offset, _mm_extract_epi32(v, 0)); +} + +} + +void Transform_4way(unsigned char* out, const unsigned char* in) +{ + // Transform 1 + __m128i a = K(0x6a09e667ul); + __m128i b = K(0xbb67ae85ul); + __m128i c = K(0x3c6ef372ul); + __m128i d = K(0xa54ff53aul); + __m128i e = K(0x510e527ful); + __m128i f = K(0x9b05688cul); + __m128i g = K(0x1f83d9abul); + __m128i h = K(0x5be0cd19ul); + + __m128i w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; + + Round(a, b, c, d, e, f, g, h, Add(K(0x428a2f98ul), w0 = Read4(in, 0))); + Round(h, a, b, c, d, e, f, g, Add(K(0x71374491ul), w1 = Read4(in, 4))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb5c0fbcful), w2 = Read4(in, 8))); + Round(f, g, h, a, b, c, d, e, Add(K(0xe9b5dba5ul), w3 = Read4(in, 12))); + Round(e, f, g, h, a, b, c, d, Add(K(0x3956c25bul), w4 = Read4(in, 16))); + Round(d, e, f, g, h, a, b, c, Add(K(0x59f111f1ul), w5 = Read4(in, 20))); + Round(c, d, e, f, g, h, a, b, Add(K(0x923f82a4ul), w6 = Read4(in, 24))); + Round(b, c, d, e, f, g, h, a, Add(K(0xab1c5ed5ul), w7 = Read4(in, 28))); + Round(a, b, c, d, e, f, g, h, Add(K(0xd807aa98ul), w8 = Read4(in, 32))); + Round(h, a, b, c, d, e, f, g, Add(K(0x12835b01ul), w9 = Read4(in, 36))); + Round(g, h, a, b, c, d, e, f, Add(K(0x243185beul), w10 = Read4(in, 40))); + Round(f, g, h, a, b, c, d, e, Add(K(0x550c7dc3ul), w11 = Read4(in, 44))); + Round(e, f, g, h, a, b, c, d, Add(K(0x72be5d74ul), w12 = Read4(in, 48))); + Round(d, e, f, g, h, a, b, c, Add(K(0x80deb1feul), w13 = Read4(in, 52))); + Round(c, d, e, f, g, h, a, b, Add(K(0x9bdc06a7ul), w14 = Read4(in, 56))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc19bf174ul), w15 = Read4(in, 60))); + Round(a, b, c, d, e, f, g, h, Add(K(0xe49b69c1ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xefbe4786ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x0fc19dc6ul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x240ca1ccul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x2de92c6ful), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4a7484aaul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5cb0a9dcul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x76f988daul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x983e5152ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa831c66dul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb00327c8ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xbf597fc7ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xc6e00bf3ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd5a79147ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x06ca6351ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x14292967ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x27b70a85ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x2e1b2138ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x4d2c6dfcul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x53380d13ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x650a7354ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x766a0abbul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x81c2c92eul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x92722c85ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0xa2bfe8a1ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa81a664bul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xc24b8b70ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xc76c51a3ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xd192e819ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd6990624ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xf40e3585ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x106aa070ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x19a4c116ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x1e376c08ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x2748774cul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x34b0bcb5ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x391c0cb3ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4ed8aa4aul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5b9cca4ful), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x682e6ff3ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x748f82eeul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x78a5636ful), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x84c87814ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x8cc70208ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x90befffaul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xa4506cebul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xbef9a3f7ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc67178f2ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + + a = Add(a, K(0x6a09e667ul)); + b = Add(b, K(0xbb67ae85ul)); + c = Add(c, K(0x3c6ef372ul)); + d = Add(d, K(0xa54ff53aul)); + e = Add(e, K(0x510e527ful)); + f = Add(f, K(0x9b05688cul)); + g = Add(g, K(0x1f83d9abul)); + h = Add(h, K(0x5be0cd19ul)); + + __m128i t0 = a, t1 = b, t2 = c, t3 = d, t4 = e, t5 = f, t6 = g, t7 = h; + + // Transform 2 + Round(a, b, c, d, e, f, g, h, K(0xc28a2f98ul)); + Round(h, a, b, c, d, e, f, g, K(0x71374491ul)); + Round(g, h, a, b, c, d, e, f, K(0xb5c0fbcful)); + Round(f, g, h, a, b, c, d, e, K(0xe9b5dba5ul)); + Round(e, f, g, h, a, b, c, d, K(0x3956c25bul)); + Round(d, e, f, g, h, a, b, c, K(0x59f111f1ul)); + Round(c, d, e, f, g, h, a, b, K(0x923f82a4ul)); + Round(b, c, d, e, f, g, h, a, K(0xab1c5ed5ul)); + Round(a, b, c, d, e, f, g, h, K(0xd807aa98ul)); + Round(h, a, b, c, d, e, f, g, K(0x12835b01ul)); + Round(g, h, a, b, c, d, e, f, K(0x243185beul)); + Round(f, g, h, a, b, c, d, e, K(0x550c7dc3ul)); + Round(e, f, g, h, a, b, c, d, K(0x72be5d74ul)); + Round(d, e, f, g, h, a, b, c, K(0x80deb1feul)); + Round(c, d, e, f, g, h, a, b, K(0x9bdc06a7ul)); + Round(b, c, d, e, f, g, h, a, K(0xc19bf374ul)); + Round(a, b, c, d, e, f, g, h, K(0x649b69c1ul)); + Round(h, a, b, c, d, e, f, g, K(0xf0fe4786ul)); + Round(g, h, a, b, c, d, e, f, K(0x0fe1edc6ul)); + Round(f, g, h, a, b, c, d, e, K(0x240cf254ul)); + Round(e, f, g, h, a, b, c, d, K(0x4fe9346ful)); + Round(d, e, f, g, h, a, b, c, K(0x6cc984beul)); + Round(c, d, e, f, g, h, a, b, K(0x61b9411eul)); + Round(b, c, d, e, f, g, h, a, K(0x16f988faul)); + Round(a, b, c, d, e, f, g, h, K(0xf2c65152ul)); + Round(h, a, b, c, d, e, f, g, K(0xa88e5a6dul)); + Round(g, h, a, b, c, d, e, f, K(0xb019fc65ul)); + Round(f, g, h, a, b, c, d, e, K(0xb9d99ec7ul)); + Round(e, f, g, h, a, b, c, d, K(0x9a1231c3ul)); + Round(d, e, f, g, h, a, b, c, K(0xe70eeaa0ul)); + Round(c, d, e, f, g, h, a, b, K(0xfdb1232bul)); + Round(b, c, d, e, f, g, h, a, K(0xc7353eb0ul)); + Round(a, b, c, d, e, f, g, h, K(0x3069bad5ul)); + Round(h, a, b, c, d, e, f, g, K(0xcb976d5ful)); + Round(g, h, a, b, c, d, e, f, K(0x5a0f118ful)); + Round(f, g, h, a, b, c, d, e, K(0xdc1eeefdul)); + Round(e, f, g, h, a, b, c, d, K(0x0a35b689ul)); + Round(d, e, f, g, h, a, b, c, K(0xde0b7a04ul)); + Round(c, d, e, f, g, h, a, b, K(0x58f4ca9dul)); + Round(b, c, d, e, f, g, h, a, K(0xe15d5b16ul)); + Round(a, b, c, d, e, f, g, h, K(0x007f3e86ul)); + Round(h, a, b, c, d, e, f, g, K(0x37088980ul)); + Round(g, h, a, b, c, d, e, f, K(0xa507ea32ul)); + Round(f, g, h, a, b, c, d, e, K(0x6fab9537ul)); + Round(e, f, g, h, a, b, c, d, K(0x17406110ul)); + Round(d, e, f, g, h, a, b, c, K(0x0d8cd6f1ul)); + Round(c, d, e, f, g, h, a, b, K(0xcdaa3b6dul)); + Round(b, c, d, e, f, g, h, a, K(0xc0bbbe37ul)); + Round(a, b, c, d, e, f, g, h, K(0x83613bdaul)); + Round(h, a, b, c, d, e, f, g, K(0xdb48a363ul)); + Round(g, h, a, b, c, d, e, f, K(0x0b02e931ul)); + Round(f, g, h, a, b, c, d, e, K(0x6fd15ca7ul)); + Round(e, f, g, h, a, b, c, d, K(0x521afacaul)); + Round(d, e, f, g, h, a, b, c, K(0x31338431ul)); + Round(c, d, e, f, g, h, a, b, K(0x6ed41a95ul)); + Round(b, c, d, e, f, g, h, a, K(0x6d437890ul)); + Round(a, b, c, d, e, f, g, h, K(0xc39c91f2ul)); + Round(h, a, b, c, d, e, f, g, K(0x9eccabbdul)); + Round(g, h, a, b, c, d, e, f, K(0xb5c9a0e6ul)); + Round(f, g, h, a, b, c, d, e, K(0x532fb63cul)); + Round(e, f, g, h, a, b, c, d, K(0xd2c741c6ul)); + Round(d, e, f, g, h, a, b, c, K(0x07237ea3ul)); + Round(c, d, e, f, g, h, a, b, K(0xa4954b68ul)); + Round(b, c, d, e, f, g, h, a, K(0x4c191d76ul)); + + w0 = Add(t0, a); + w1 = Add(t1, b); + w2 = Add(t2, c); + w3 = Add(t3, d); + w4 = Add(t4, e); + w5 = Add(t5, f); + w6 = Add(t6, g); + w7 = Add(t7, h); + + // Transform 3 + a = K(0x6a09e667ul); + b = K(0xbb67ae85ul); + c = K(0x3c6ef372ul); + d = K(0xa54ff53aul); + e = K(0x510e527ful); + f = K(0x9b05688cul); + g = K(0x1f83d9abul); + h = K(0x5be0cd19ul); + + Round(a, b, c, d, e, f, g, h, Add(K(0x428a2f98ul), w0)); + Round(h, a, b, c, d, e, f, g, Add(K(0x71374491ul), w1)); + Round(g, h, a, b, c, d, e, f, Add(K(0xb5c0fbcful), w2)); + Round(f, g, h, a, b, c, d, e, Add(K(0xe9b5dba5ul), w3)); + Round(e, f, g, h, a, b, c, d, Add(K(0x3956c25bul), w4)); + Round(d, e, f, g, h, a, b, c, Add(K(0x59f111f1ul), w5)); + Round(c, d, e, f, g, h, a, b, Add(K(0x923f82a4ul), w6)); + Round(b, c, d, e, f, g, h, a, Add(K(0xab1c5ed5ul), w7)); + Round(a, b, c, d, e, f, g, h, K(0x5807aa98ul)); + Round(h, a, b, c, d, e, f, g, K(0x12835b01ul)); + Round(g, h, a, b, c, d, e, f, K(0x243185beul)); + Round(f, g, h, a, b, c, d, e, K(0x550c7dc3ul)); + Round(e, f, g, h, a, b, c, d, K(0x72be5d74ul)); + Round(d, e, f, g, h, a, b, c, K(0x80deb1feul)); + Round(c, d, e, f, g, h, a, b, K(0x9bdc06a7ul)); + Round(b, c, d, e, f, g, h, a, K(0xc19bf274ul)); + Round(a, b, c, d, e, f, g, h, Add(K(0xe49b69c1ul), Inc(w0, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xefbe4786ul), Inc(w1, K(0xa00000ul), sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x0fc19dc6ul), Inc(w2, sigma1(w0), sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x240ca1ccul), Inc(w3, sigma1(w1), sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x2de92c6ful), Inc(w4, sigma1(w2), sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4a7484aaul), Inc(w5, sigma1(w3), sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5cb0a9dcul), Inc(w6, sigma1(w4), K(0x100ul), sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x76f988daul), Inc(w7, sigma1(w5), w0, K(0x11002000ul)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x983e5152ul), w8 = Add(K(0x80000000ul), sigma1(w6), w1))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa831c66dul), w9 = Add(sigma1(w7), w2))); + Round(g, h, a, b, c, d, e, f, Add(K(0xb00327c8ul), w10 = Add(sigma1(w8), w3))); + Round(f, g, h, a, b, c, d, e, Add(K(0xbf597fc7ul), w11 = Add(sigma1(w9), w4))); + Round(e, f, g, h, a, b, c, d, Add(K(0xc6e00bf3ul), w12 = Add(sigma1(w10), w5))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd5a79147ul), w13 = Add(sigma1(w11), w6))); + Round(c, d, e, f, g, h, a, b, Add(K(0x06ca6351ul), w14 = Add(sigma1(w12), w7, K(0x400022ul)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x14292967ul), w15 = Add(K(0x100ul), sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x27b70a85ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x2e1b2138ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x4d2c6dfcul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x53380d13ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x650a7354ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x766a0abbul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x81c2c92eul), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x92722c85ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0xa2bfe8a1ul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0xa81a664bul), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0xc24b8b70ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0xc76c51a3ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0xd192e819ul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xd6990624ul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xf40e3585ul), Inc(w14, sigma1(w12), w7, sigma0(w15)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x106aa070ul), Inc(w15, sigma1(w13), w8, sigma0(w0)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x19a4c116ul), Inc(w0, sigma1(w14), w9, sigma0(w1)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x1e376c08ul), Inc(w1, sigma1(w15), w10, sigma0(w2)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x2748774cul), Inc(w2, sigma1(w0), w11, sigma0(w3)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x34b0bcb5ul), Inc(w3, sigma1(w1), w12, sigma0(w4)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x391c0cb3ul), Inc(w4, sigma1(w2), w13, sigma0(w5)))); + Round(d, e, f, g, h, a, b, c, Add(K(0x4ed8aa4aul), Inc(w5, sigma1(w3), w14, sigma0(w6)))); + Round(c, d, e, f, g, h, a, b, Add(K(0x5b9cca4ful), Inc(w6, sigma1(w4), w15, sigma0(w7)))); + Round(b, c, d, e, f, g, h, a, Add(K(0x682e6ff3ul), Inc(w7, sigma1(w5), w0, sigma0(w8)))); + Round(a, b, c, d, e, f, g, h, Add(K(0x748f82eeul), Inc(w8, sigma1(w6), w1, sigma0(w9)))); + Round(h, a, b, c, d, e, f, g, Add(K(0x78a5636ful), Inc(w9, sigma1(w7), w2, sigma0(w10)))); + Round(g, h, a, b, c, d, e, f, Add(K(0x84c87814ul), Inc(w10, sigma1(w8), w3, sigma0(w11)))); + Round(f, g, h, a, b, c, d, e, Add(K(0x8cc70208ul), Inc(w11, sigma1(w9), w4, sigma0(w12)))); + Round(e, f, g, h, a, b, c, d, Add(K(0x90befffaul), Inc(w12, sigma1(w10), w5, sigma0(w13)))); + Round(d, e, f, g, h, a, b, c, Add(K(0xa4506cebul), Inc(w13, sigma1(w11), w6, sigma0(w14)))); + Round(c, d, e, f, g, h, a, b, Add(K(0xbef9a3f7ul), w14, sigma1(w12), w7, sigma0(w15))); + Round(b, c, d, e, f, g, h, a, Add(K(0xc67178f2ul), w15, sigma1(w13), w8, sigma0(w0))); + + // Output + Write4(out, 0, Add(a, K(0x6a09e667ul))); + Write4(out, 4, Add(b, K(0xbb67ae85ul))); + Write4(out, 8, Add(c, K(0x3c6ef372ul))); + Write4(out, 12, Add(d, K(0xa54ff53aul))); + Write4(out, 16, Add(e, K(0x510e527ful))); + Write4(out, 20, Add(f, K(0x9b05688cul))); + Write4(out, 24, Add(g, K(0x1f83d9abul))); + Write4(out, 28, Add(h, K(0x5be0cd19ul))); +} + +} + +#endif diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 6f80eedc7a..2a5e0cab00 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -224,6 +224,9 @@ public: CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false); ~CDBWrapper(); + CDBWrapper(const CDBWrapper&) = delete; + CDBWrapper& operator=(const CDBWrapper&) = delete; + template <typename K, typename V> bool Read(const K& key, V& value) const { diff --git a/src/httprpc.cpp b/src/httprpc.cpp index de2437943e..97b152b11b 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -215,7 +215,7 @@ static bool InitRPCAuthentication() { if (gArgs.GetArg("-rpcpassword", "") == "") { - LogPrintf("No rpcpassword set - using random cookie authentication\n"); + LogPrintf("No rpcpassword set - using random cookie authentication.\n"); if (!GenerateAuthCookie(&strRPCUserColonPass)) { uiInterface.ThreadSafeMessageBox( _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode @@ -223,9 +223,13 @@ static bool InitRPCAuthentication() return false; } } else { - LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcuser for rpcauth auth generation.\n"); + LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n"); strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } + if (gArgs.GetArg("-rpcauth","") != "") + { + LogPrintf("Using rpcauth authentication.\n"); + } return true; } diff --git a/src/httpserver.cpp b/src/httpserver.cpp index b8b338dfbc..bd08b04c0f 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -364,8 +364,8 @@ bool InitHTTPServer() // Update libevent's log handling. Returns false if our version of // libevent doesn't support debug logging, in which case we should // clear the BCLog::LIBEVENT flag. - if (!UpdateHTTPServerLogging(logCategories & BCLog::LIBEVENT)) { - logCategories &= ~BCLog::LIBEVENT; + if (!UpdateHTTPServerLogging(g_logger->WillLogCategory(BCLog::LIBEVENT))) { + g_logger->DisableCategory(BCLog::LIBEVENT); } #ifdef WIN32 diff --git a/src/httpserver.h b/src/httpserver.h index f17be65962..8132c887b5 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -32,7 +32,7 @@ void InterruptHTTPServer(); /** Stop HTTP server */ void StopHTTPServer(); -/** Change logging level for libevent. Removes BCLog::LIBEVENT from logCategories if +/** Change logging level for libevent. Removes BCLog::LIBEVENT from log categories if * libevent doesn't support debug logging.*/ bool UpdateHTTPServerLogging(bool enable); diff --git a/src/index/base.cpp b/src/index/base.cpp new file mode 100644 index 0000000000..788f7adccd --- /dev/null +++ b/src/index/base.cpp @@ -0,0 +1,278 @@ +// 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. + +#include <chainparams.h> +#include <index/base.h> +#include <shutdown.h> +#include <tinyformat.h> +#include <ui_interface.h> +#include <util.h> +#include <validation.h> +#include <warnings.h> + +constexpr char DB_BEST_BLOCK = 'B'; + +constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds +constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds + +template<typename... Args> +static void FatalError(const char* fmt, const Args&... args) +{ + std::string strMessage = tfm::format(fmt, args...); + SetMiscWarning(strMessage); + LogPrintf("*** %s\n", strMessage); + uiInterface.ThreadSafeMessageBox( + "Error: A fatal internal error occurred, see debug.log for details", + "", CClientUIInterface::MSG_ERROR); + StartShutdown(); +} + +BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : + CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) +{} + +bool BaseIndex::DB::ReadBestBlock(CBlockLocator& locator) const +{ + bool success = Read(DB_BEST_BLOCK, locator); + if (!success) { + locator.SetNull(); + } + return success; +} + +bool BaseIndex::DB::WriteBestBlock(const CBlockLocator& locator) +{ + return Write(DB_BEST_BLOCK, locator); +} + +BaseIndex::~BaseIndex() +{ + Interrupt(); + Stop(); +} + +bool BaseIndex::Init() +{ + CBlockLocator locator; + if (!GetDB().ReadBestBlock(locator)) { + locator.SetNull(); + } + + LOCK(cs_main); + 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) +{ + AssertLockHeld(cs_main); + + if (!pindex_prev) { + return chainActive.Genesis(); + } + + const CBlockIndex* pindex = chainActive.Next(pindex_prev); + if (pindex) { + return pindex; + } + + return chainActive.Next(chainActive.FindFork(pindex_prev)); +} + +void BaseIndex::ThreadSync() +{ + const CBlockIndex* pindex = m_best_block_index.load(); + if (!m_synced) { + auto& consensus_params = Params().GetConsensus(); + + int64_t last_log_time = 0; + int64_t last_locator_write_time = 0; + while (true) { + if (m_interrupt) { + WriteBestBlock(pindex); + return; + } + + { + LOCK(cs_main); + const CBlockIndex* pindex_next = NextSyncBlock(pindex); + if (!pindex_next) { + WriteBestBlock(pindex); + m_best_block_index = pindex; + m_synced = true; + break; + } + pindex = pindex_next; + } + + int64_t current_time = GetTime(); + if (last_log_time + SYNC_LOG_INTERVAL < current_time) { + LogPrintf("Syncing %s with block chain from height %d\n", + GetName(), pindex->nHeight); + last_log_time = current_time; + } + + if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { + WriteBestBlock(pindex); + last_locator_write_time = current_time; + } + + CBlock block; + if (!ReadBlockFromDisk(block, pindex, consensus_params)) { + FatalError("%s: Failed to read block %s from disk", + __func__, pindex->GetBlockHash().ToString()); + return; + } + if (!WriteBlock(block, pindex)) { + FatalError("%s: Failed to write block %s to index database", + __func__, pindex->GetBlockHash().ToString()); + return; + } + } + } + + if (pindex) { + LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight); + } else { + LogPrintf("%s is enabled\n", GetName()); + } +} + +bool BaseIndex::WriteBestBlock(const CBlockIndex* block_index) +{ + LOCK(cs_main); + if (!GetDB().WriteBestBlock(chainActive.GetLocator(block_index))) { + return error("%s: Failed to write locator to disk", __func__); + } + return true; +} + +void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex, + const std::vector<CTransactionRef>& txn_conflicted) +{ + if (!m_synced) { + return; + } + + const CBlockIndex* best_block_index = m_best_block_index.load(); + if (!best_block_index) { + if (pindex->nHeight != 0) { + FatalError("%s: First block connected is not the genesis block (height=%d)", + __func__, pindex->nHeight); + return; + } + } else { + // Ensure block connects to an ancestor of the current best block. This should be the case + // most of the time, but may not be immediately after the sync thread catches up and sets + // m_synced. Consider the case where there is a reorg and the blocks on the stale branch are + // in the ValidationInterface queue backlog even after the sync thread has caught up to the + // new chain tip. In this unlikely event, log a warning and let the queue clear. + if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) { + LogPrintf("%s: WARNING: Block %s does not connect to an ancestor of " /* Continued */ + "known best chain (tip=%s); not updating index\n", + __func__, pindex->GetBlockHash().ToString(), + best_block_index->GetBlockHash().ToString()); + return; + } + } + + if (WriteBlock(*block, pindex)) { + m_best_block_index = pindex; + } else { + FatalError("%s: Failed to write block %s to index", + __func__, pindex->GetBlockHash().ToString()); + return; + } +} + +void BaseIndex::ChainStateFlushed(const CBlockLocator& locator) +{ + if (!m_synced) { + return; + } + + const uint256& locator_tip_hash = locator.vHave.front(); + const CBlockIndex* locator_tip_index; + { + LOCK(cs_main); + locator_tip_index = LookupBlockIndex(locator_tip_hash); + } + + if (!locator_tip_index) { + FatalError("%s: First block (hash=%s) in locator was not found", + __func__, locator_tip_hash.ToString()); + return; + } + + // This checks that ChainStateFlushed callbacks are received after BlockConnected. The check may fail + // immediately after the sync thread catches up and sets m_synced. Consider the case where + // there is a reorg and the blocks on the stale branch are in the ValidationInterface queue + // backlog even after the sync thread has caught up to the new chain tip. In this unlikely + // event, log a warning and let the queue clear. + const CBlockIndex* best_block_index = m_best_block_index.load(); + if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) { + LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known best " /* Continued */ + "chain (tip=%s); not writing index locator\n", + __func__, locator_tip_hash.ToString(), + best_block_index->GetBlockHash().ToString()); + return; + } + + if (!GetDB().WriteBestBlock(locator)) { + error("%s: Failed to write locator to disk", __func__); + } +} + +bool BaseIndex::BlockUntilSyncedToCurrentChain() +{ + AssertLockNotHeld(cs_main); + + if (!m_synced) { + return false; + } + + { + // Skip the queue-draining stuff if we know we're caught up with + // chainActive.Tip(). + LOCK(cs_main); + const CBlockIndex* chain_tip = chainActive.Tip(); + const CBlockIndex* best_block_index = m_best_block_index.load(); + if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) { + return true; + } + } + + LogPrintf("%s: %s is catching up on block notifications\n", __func__, GetName()); + SyncWithValidationInterfaceQueue(); + return true; +} + +void BaseIndex::Interrupt() +{ + m_interrupt(); +} + +void BaseIndex::Start() +{ + // Need to register this ValidationInterface before running Init(), so that + // callbacks are not missed if Init sets m_synced to true. + RegisterValidationInterface(this); + if (!Init()) { + FatalError("%s: %s failed to initialize", __func__, GetName()); + return; + } + + m_thread_sync = std::thread(&TraceThread<std::function<void()>>, GetName(), + std::bind(&BaseIndex::ThreadSync, this)); +} + +void BaseIndex::Stop() +{ + UnregisterValidationInterface(this); + + if (m_thread_sync.joinable()) { + m_thread_sync.join(); + } +} diff --git a/src/index/base.h b/src/index/base.h new file mode 100644 index 0000000000..04ee6e6cc2 --- /dev/null +++ b/src/index/base.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef BITCOIN_INDEX_BASE_H +#define BITCOIN_INDEX_BASE_H + +#include <dbwrapper.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <threadinterrupt.h> +#include <uint256.h> +#include <validationinterface.h> + +class CBlockIndex; + +/** + * Base class for indices of blockchain data. This implements + * CValidationInterface and ensures blocks are indexed sequentially according + * to their position in the active chain. + */ +class BaseIndex : public CValidationInterface +{ +protected: + class DB : public CDBWrapper + { + public: + DB(const fs::path& path, size_t n_cache_size, + bool f_memory = false, bool f_wipe = false, bool f_obfuscate = false); + + /// Read block locator of the chain that the txindex is in sync with. + bool ReadBestBlock(CBlockLocator& locator) const; + + /// Write block locator of the chain that the txindex is in sync with. + bool WriteBestBlock(const CBlockLocator& locator); + }; + +private: + /// Whether the index is in sync with the main chain. The flag is flipped + /// from false to true once, after which point this starts processing + /// ValidationInterface notifications to stay in sync. + std::atomic<bool> m_synced{false}; + + /// The last block in the chain that the index is in sync with. + std::atomic<const CBlockIndex*> m_best_block_index{nullptr}; + + std::thread m_thread_sync; + CThreadInterrupt m_interrupt; + + /// Sync the index with the block index starting from the current best block. + /// Intended to be run in its own thread, m_thread_sync, and can be + /// interrupted with m_interrupt. Once the index gets in sync, the m_synced + /// flag is set and the BlockConnected ValidationInterface callback takes + /// over and the sync thread exits. + void ThreadSync(); + + /// Write the current chain block locator to the DB. + bool WriteBestBlock(const CBlockIndex* block_index); + +protected: + void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex, + const std::vector<CTransactionRef>& txn_conflicted) override; + + void ChainStateFlushed(const CBlockLocator& locator) override; + + /// Initialize internal state from the database and block index. + virtual bool Init(); + + /// Write update index entries for a newly connected block. + virtual bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) { return true; } + + virtual DB& GetDB() const = 0; + + /// Get the name of the index for display in logs. + virtual const char* GetName() const = 0; + +public: + /// Destructor interrupts sync thread if running and blocks until it exits. + virtual ~BaseIndex(); + + /// Blocks the current thread until the index is caught up to the current + /// state of the block chain. This only blocks if the index has gotten in + /// sync once and only needs to process blocks in the ValidationInterface + /// queue. If the index is catching up from far behind, this method does + /// not block and immediately returns false. + bool BlockUntilSyncedToCurrentChain(); + + void Interrupt(); + + /// Start initializes the sync state and registers the instance as a + /// ValidationInterface so that it stays in sync with blockchain updates. + void Start(); + + /// Stops the instance from staying in sync with blockchain updates. + void Stop(); +}; + +#endif // BITCOIN_INDEX_BASE_H diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp new file mode 100644 index 0000000000..c85030e18e --- /dev/null +++ b/src/index/txindex.cpp @@ -0,0 +1,286 @@ +// 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. + +#include <index/txindex.h> +#include <shutdown.h> +#include <ui_interface.h> +#include <util.h> +#include <validation.h> + +#include <boost/thread.hpp> + +constexpr char DB_BEST_BLOCK = 'B'; +constexpr char DB_TXINDEX = 't'; +constexpr char DB_TXINDEX_BLOCK = 'T'; + +std::unique_ptr<TxIndex> g_txindex; + +struct CDiskTxPos : public CDiskBlockPos +{ + unsigned int nTxOffset; // after header + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITEAS(CDiskBlockPos, *this); + READWRITE(VARINT(nTxOffset)); + } + + CDiskTxPos(const CDiskBlockPos &blockIn, unsigned int nTxOffsetIn) : CDiskBlockPos(blockIn.nFile, blockIn.nPos), nTxOffset(nTxOffsetIn) { + } + + CDiskTxPos() { + SetNull(); + } + + void SetNull() { + CDiskBlockPos::SetNull(); + nTxOffset = 0; + } +}; + +/** + * Access to the txindex database (indexes/txindex/) + * + * The database stores a block locator of the chain the database is synced to + * so that the TxIndex can efficiently determine the point it last stopped at. + * A locator is used instead of a simple hash of the chain tip because blocks + * and block index entries may not be flushed to disk until after this database + * is updated. + */ +class TxIndex::DB : public BaseIndex::DB +{ +public: + explicit DB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); + + /// Read the disk location of the transaction data with the given hash. Returns false if the + /// transaction hash is not indexed. + bool ReadTxPos(const uint256& txid, CDiskTxPos& pos) const; + + /// Write a batch of transaction positions to the DB. + bool WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_pos); + + /// Migrate txindex data from the block tree DB, where it may be for older nodes that have not + /// been upgraded yet to the new database. + bool MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& best_locator); +}; + +TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) : + BaseIndex::DB(GetDataDir() / "indexes" / "txindex", n_cache_size, f_memory, f_wipe) +{} + +bool TxIndex::DB::ReadTxPos(const uint256 &txid, CDiskTxPos& pos) const +{ + return Read(std::make_pair(DB_TXINDEX, txid), pos); +} + +bool TxIndex::DB::WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_pos) +{ + CDBBatch batch(*this); + for (const auto& tuple : v_pos) { + batch.Write(std::make_pair(DB_TXINDEX, tuple.first), tuple.second); + } + return WriteBatch(batch); +} + +/* + * Safely persist a transfer of data from the old txindex database to the new one, and compact the + * range of keys updated. This is used internally by MigrateData. + */ +static void WriteTxIndexMigrationBatches(CDBWrapper& newdb, CDBWrapper& olddb, + CDBBatch& batch_newdb, CDBBatch& batch_olddb, + const std::pair<unsigned char, uint256>& begin_key, + const std::pair<unsigned char, uint256>& end_key) +{ + // Sync new DB changes to disk before deleting from old DB. + newdb.WriteBatch(batch_newdb, /*fSync=*/ true); + olddb.WriteBatch(batch_olddb); + olddb.CompactRange(begin_key, end_key); + + batch_newdb.Clear(); + batch_olddb.Clear(); +} + +bool TxIndex::DB::MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& best_locator) +{ + // The prior implementation of txindex was always in sync with block index + // and presence was indicated with a boolean DB flag. If the flag is set, + // this means the txindex from a previous version is valid and in sync with + // the chain tip. The first step of the migration is to unset the flag and + // write the chain hash to a separate key, DB_TXINDEX_BLOCK. After that, the + // index entries are copied over in batches to the new database. Finally, + // DB_TXINDEX_BLOCK is erased from the old database and the block hash is + // written to the new database. + // + // Unsetting the boolean flag ensures that if the node is downgraded to a + // previous version, it will not see a corrupted, partially migrated index + // -- it will see that the txindex is disabled. When the node is upgraded + // again, the migration will pick up where it left off and sync to the block + // with hash DB_TXINDEX_BLOCK. + bool f_legacy_flag = false; + block_tree_db.ReadFlag("txindex", f_legacy_flag); + if (f_legacy_flag) { + if (!block_tree_db.Write(DB_TXINDEX_BLOCK, best_locator)) { + return error("%s: cannot write block indicator", __func__); + } + if (!block_tree_db.WriteFlag("txindex", false)) { + return error("%s: cannot write block index db flag", __func__); + } + } + + CBlockLocator locator; + if (!block_tree_db.Read(DB_TXINDEX_BLOCK, locator)) { + return true; + } + + int64_t count = 0; + LogPrintf("Upgrading txindex database... [0%%]\n"); + uiInterface.ShowProgress(_("Upgrading txindex database"), 0, true); + int report_done = 0; + const size_t batch_size = 1 << 24; // 16 MiB + + CDBBatch batch_newdb(*this); + CDBBatch batch_olddb(block_tree_db); + + std::pair<unsigned char, uint256> key; + std::pair<unsigned char, uint256> begin_key{DB_TXINDEX, uint256()}; + std::pair<unsigned char, uint256> prev_key = begin_key; + + bool interrupted = false; + std::unique_ptr<CDBIterator> cursor(block_tree_db.NewIterator()); + for (cursor->Seek(begin_key); cursor->Valid(); cursor->Next()) { + boost::this_thread::interruption_point(); + if (ShutdownRequested()) { + interrupted = true; + break; + } + + if (!cursor->GetKey(key)) { + return error("%s: cannot get key from valid cursor", __func__); + } + if (key.first != DB_TXINDEX) { + break; + } + + // Log progress every 10%. + if (++count % 256 == 0) { + // Since txids are uniformly random and traversed in increasing order, the high 16 bits + // of the hash can be used to estimate the current progress. + const uint256& txid = key.second; + uint32_t high_nibble = + (static_cast<uint32_t>(*(txid.begin() + 0)) << 8) + + (static_cast<uint32_t>(*(txid.begin() + 1)) << 0); + int percentage_done = (int)(high_nibble * 100.0 / 65536.0 + 0.5); + + uiInterface.ShowProgress(_("Upgrading txindex database"), percentage_done, true); + if (report_done < percentage_done/10) { + LogPrintf("Upgrading txindex database... [%d%%]\n", percentage_done); + report_done = percentage_done/10; + } + } + + CDiskTxPos value; + if (!cursor->GetValue(value)) { + return error("%s: cannot parse txindex record", __func__); + } + batch_newdb.Write(key, value); + batch_olddb.Erase(key); + + if (batch_newdb.SizeEstimate() > batch_size || batch_olddb.SizeEstimate() > batch_size) { + // NOTE: it's OK to delete the key pointed at by the current DB cursor while iterating + // because LevelDB iterators are guaranteed to provide a consistent view of the + // underlying data, like a lightweight snapshot. + WriteTxIndexMigrationBatches(*this, block_tree_db, + batch_newdb, batch_olddb, + prev_key, key); + prev_key = key; + } + } + + // If these final DB batches complete the migration, write the best block + // hash marker to the new database and delete from the old one. This signals + // that the former is fully caught up to that point in the blockchain and + // that all txindex entries have been removed from the latter. + if (!interrupted) { + batch_olddb.Erase(DB_TXINDEX_BLOCK); + batch_newdb.Write(DB_BEST_BLOCK, locator); + } + + WriteTxIndexMigrationBatches(*this, block_tree_db, + batch_newdb, batch_olddb, + begin_key, key); + + if (interrupted) { + LogPrintf("[CANCELLED].\n"); + return false; + } + + uiInterface.ShowProgress("", 100, false); + + LogPrintf("[DONE].\n"); + return true; +} + +TxIndex::TxIndex(size_t n_cache_size, bool f_memory, bool f_wipe) + : m_db(MakeUnique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) +{} + +TxIndex::~TxIndex() {} + +bool TxIndex::Init() +{ + LOCK(cs_main); + + // Attempt to migrate txindex from the old database to the new one. Even if + // chain_tip is null, the node could be reindexing and we still want to + // delete txindex records in the old database. + if (!m_db->MigrateData(*pblocktree, chainActive.GetLocator())) { + return false; + } + + return BaseIndex::Init(); +} + +bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) +{ + CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size())); + std::vector<std::pair<uint256, CDiskTxPos>> vPos; + vPos.reserve(block.vtx.size()); + for (const auto& tx : block.vtx) { + vPos.emplace_back(tx->GetHash(), pos); + pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION); + } + return m_db->WriteTxs(vPos); +} + +BaseIndex::DB& TxIndex::GetDB() const { return *m_db; } + +bool TxIndex::FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRef& tx) const +{ + CDiskTxPos postx; + if (!m_db->ReadTxPos(tx_hash, postx)) { + return false; + } + + CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); + if (file.IsNull()) { + return error("%s: OpenBlockFile failed", __func__); + } + CBlockHeader header; + try { + file >> header; + if (fseek(file.Get(), postx.nTxOffset, SEEK_CUR)) { + return error("%s: fseek(...) failed", __func__); + } + file >> tx; + } catch (const std::exception& e) { + return error("%s: Deserialize or I/O error - %s", __func__, e.what()); + } + if (tx->GetHash() != tx_hash) { + return error("%s: txid mismatch", __func__); + } + block_hash = header.GetHash(); + return true; +} diff --git a/src/index/txindex.h b/src/index/txindex.h new file mode 100644 index 0000000000..8202c3c951 --- /dev/null +++ b/src/index/txindex.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef BITCOIN_INDEX_TXINDEX_H +#define BITCOIN_INDEX_TXINDEX_H + +#include <chain.h> +#include <index/base.h> +#include <txdb.h> + +/** + * TxIndex is used to look up transactions included in the blockchain by hash. + * The index is written to a LevelDB database and records the filesystem + * location of each transaction by transaction hash. + */ +class TxIndex final : public BaseIndex +{ +protected: + class DB; + +private: + const std::unique_ptr<DB> m_db; + +protected: + /// Override base class init to migrate from old database. + bool Init() override; + + bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override; + + BaseIndex::DB& GetDB() const override; + + const char* GetName() const override { return "txindex"; } + +public: + /// Constructs the index, which becomes available to be queried. + explicit TxIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); + + // Destructor is declared because this class contains a unique_ptr to an incomplete type. + virtual ~TxIndex() override; + + /// Look up a transaction by hash. + /// + /// @param[in] tx_hash The hash of the transaction to be returned. + /// @param[out] block_hash The hash of the block the transaction is found in. + /// @param[out] tx The transaction itself. + /// @return true if transaction is found, false otherwise + bool FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRef& tx) const; +}; + +/// The global transaction index, used in GetTransaction. May be null. +extern std::unique_ptr<TxIndex> g_txindex; + +#endif // BITCOIN_INDEX_TXINDEX_H diff --git a/src/init.cpp b/src/init.cpp index f403f90b08..c7ea40817d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -19,6 +19,7 @@ #include <fs.h> #include <httpserver.h> #include <httprpc.h> +#include <index/txindex.h> #include <key.h> #include <validation.h> #include <miner.h> @@ -30,11 +31,11 @@ #include <policy/policy.h> #include <rpc/server.h> #include <rpc/register.h> -#include <rpc/safemode.h> #include <rpc/blockchain.h> #include <script/standard.h> #include <script/sigcache.h> #include <scheduler.h> +#include <shutdown.h> #include <timedata.h> #include <txdb.h> #include <txmempool.h> @@ -62,6 +63,7 @@ #if ENABLE_ZMQ #include <zmq/zmqnotificationinterface.h> +#include <zmq/zmqrpc.h> #endif bool fFeeEstimatesInitialized = false; @@ -76,7 +78,7 @@ std::unique_ptr<PeerLogicValidation> peerLogic; class DummyWalletInit : public WalletInitInterface { public: - std::string GetHelpString(bool showDebug) const override {return std::string{};} + void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} void RegisterRPC(CRPCTable &) const override {} bool Verify() const override {return true;} @@ -87,11 +89,16 @@ public: void Close() const override {} }; -const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); -#endif +void DummyWalletInit::AddWalletOptions() const +{ + std::vector<std::string> opts = {"-addresstype", "-changetype", "-disablewallet", "-discardfee=<amt>", "-fallbackfee=<amt>", + "-keypool=<n>", "-mintxfee=<amt>", "-paytxfee=<amt>", "-rescan", "-salvagewallet", "-spendzeroconfchange", "-txconfirmtarget=<n>", + "-upgradewallet", "-wallet=<path>", "-walletbroadcast", "-walletdir=<dir>", "-walletnotify=<cmd>", "-walletrbf", "-zapwallettxes=<mode>", + "-dblogsize=<n>", "-flushwallet", "-privdb", "-walletrejectlongchains"}; + gArgs.AddHiddenArgs(opts); +} -#if ENABLE_ZMQ -static CZMQNotificationInterface* pzmqNotificationInterface = nullptr; +const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); #endif #ifdef WIN32 @@ -117,7 +124,7 @@ static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat"; // created by AppInit() or the Qt main() function. // // A clean exit happens when StartShutdown() or the SIGTERM -// signal handler sets fRequestShutdown, which makes main thread's +// signal handler sets ShutdownRequested(), which makes main thread's // WaitForShutdown() interrupts the thread group. // And then, WaitForShutdown() makes all other on-going threads // in the thread group join the main thread. @@ -126,21 +133,10 @@ static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat"; // threads have exited. // // Shutdown for Qt is very similar, only it uses a QTimer to detect -// fRequestShutdown getting set, and then does the normal Qt +// ShutdownRequested() getting set, and then does the normal Qt // shutdown thing. // -std::atomic<bool> fRequestShutdown(false); - -void StartShutdown() -{ - fRequestShutdown = true; -} -bool ShutdownRequested() -{ - return fRequestShutdown; -} - /** * This is a minimally invasive approach to shutdown on LevelDB read errors from the * chainstate, while keeping user interface out of the common library, which is shared @@ -182,6 +178,9 @@ void Interrupt() InterruptMapPort(); if (g_connman) g_connman->Interrupt(); + if (g_txindex) { + g_txindex->Interrupt(); + } } void Shutdown() @@ -212,6 +211,9 @@ void Shutdown() if (g_connman) g_connman->Stop(); peerLogic.reset(); g_connman.reset(); + if (g_txindex) { + g_txindex.reset(); + } StopTorControl(); @@ -236,7 +238,7 @@ void Shutdown() fFeeEstimatesInitialized = false; } - // FlushStateToDisk generates a SetBestChain callback, which we should avoid missing + // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing if (pcoinsTip != nullptr) { FlushStateToDisk(); } @@ -264,10 +266,10 @@ void Shutdown() g_wallet_init_interface.Stop(); #if ENABLE_ZMQ - if (pzmqNotificationInterface) { - UnregisterValidationInterface(pzmqNotificationInterface); - delete pzmqNotificationInterface; - pzmqNotificationInterface = nullptr; + if (g_zmq_notification_interface) { + UnregisterValidationInterface(g_zmq_notification_interface); + delete g_zmq_notification_interface; + g_zmq_notification_interface = nullptr; } #endif @@ -292,15 +294,24 @@ void Shutdown() * The execution context the handler is invoked in is not guaranteed, * so we restrict handler operations to just touching variables: */ +#ifndef WIN32 static void HandleSIGTERM(int) { - fRequestShutdown = true; + StartShutdown(); } static void HandleSIGHUP(int) { - fReopenDebugLog = true; + g_logger->m_reopen_file = true; } +#else +static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) +{ + StartShutdown(); + Sleep(INFINITE); + return true; +} +#endif #ifndef WIN32 static void registerSignalHandler(int signal, void(*handler)(int)) @@ -313,12 +324,12 @@ static void registerSignalHandler(int signal, void(*handler)(int)) } #endif -void OnRPCStarted() +static void OnRPCStarted() { uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange); } -void OnRPCStopped() +static void OnRPCStopped() { uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange); RPCNotifyBlockChange(false, nullptr); @@ -326,199 +337,190 @@ void OnRPCStopped() LogPrint(BCLog::RPC, "RPC stopped.\n"); } -std::string HelpMessage(HelpMessageMode mode) +void SetupServerArgs() { const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN); const auto testnetChainParams = CreateChainParams(CBaseChainParams::TESTNET); - const bool showDebug = gArgs.GetBoolArg("-help-debug", false); + // Hidden Options + std::vector<std::string> hidden_args = {"-rpcssl", "-benchmark", "-h", "-help", "-socks", "-tor", "-debugnet", "-whitelistalwaysrelay", + "-prematurewitness", "-walletprematurewitness", "-promiscuousmempoolflags", "-blockminsize", "-dbcrashratio", "-forcecompactdb", "-usehd", + // GUI args. These will be overwritten by SetupUIArgs for the GUI + "-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>", "-min", "-resetguisettings", "-rootcertificates=<file>", "-splash", "-uiplatform"}; + + // Set all of the args and their help // When adding new options to the categories, please keep and ensure alphabetical ordering. - // Do not translate _(...) -help-debug options, Many technical terms, and only a very small audience, so is unnecessary stress to translators. - std::string strUsage = HelpMessageGroup(_("Options:")); - strUsage += HelpMessageOpt("-?", _("Print this help message and exit")); - strUsage += HelpMessageOpt("-version", _("Print version and exit")); - strUsage += HelpMessageOpt("-alertnotify=<cmd>", _("Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)")); - strUsage +=HelpMessageOpt("-assumevalid=<hex>", strprintf(_("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: %s, testnet: %s)"), defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex())); - strUsage += HelpMessageOpt("-blocksdir=<dir>", _("Specify blocks directory (default: <datadir>/blocks)")); - strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)")); - strUsage += HelpMessageOpt("-blockreconstructionextratxn=<n>", strprintf(_("Extra transactions to keep in memory for compact block reconstructions (default: %u)"), DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN)); - if (showDebug) - strUsage += HelpMessageOpt("-blocksonly", strprintf(_("Whether to operate in a blocks only mode (default: %u)"), DEFAULT_BLOCKSONLY)); - strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)"), BITCOIN_CONF_FILENAME)); - if (mode == HelpMessageMode::BITCOIND) - { -#if HAVE_DECL_DAEMON - strUsage += HelpMessageOpt("-daemon", _("Run in the background as a daemon and accept commands")); -#endif - } - strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory")); - if (showDebug) { - strUsage += HelpMessageOpt("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize)); - } - strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache)); - strUsage += HelpMessageOpt("-debuglogfile=<file>", strprintf(_("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)"), DEFAULT_DEBUGLOGFILE)); - if (showDebug) - strUsage += HelpMessageOpt("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER)); - strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file on startup")); - strUsage += HelpMessageOpt("-maxmempool=<n>", strprintf(_("Keep the transaction memory pool below <n> megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE)); - strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS)); - strUsage += HelpMessageOpt("-mempoolexpiry=<n>", strprintf(_("Do not keep transactions in the mempool longer than <n> hours (default: %u)"), DEFAULT_MEMPOOL_EXPIRY)); - if (showDebug) { - strUsage += HelpMessageOpt("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex())); - } - strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"), - -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS)); - strUsage += HelpMessageOpt("-persistmempool", strprintf(_("Whether to save the mempool on shutdown and load on restart (default: %u)"), DEFAULT_PERSIST_MEMPOOL)); + gArgs.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-assumevalid=<hex>", strprintf("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: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksdir=<dir>", "Specify blocks directory (default: <datadir>/blocks)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksonly", strprintf("Whether to operate in a blocks only mode (default: %u)", DEFAULT_BLOCKSONLY), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbcache=<n>", strprintf("Set database cache size in megabytes (%d to %d, default: %d)", nMinDbCache, nMaxDbCache, nDefaultDbCache), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (0 to disable; default: %s)", DEFAULT_DEBUGLOGFILE), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-loadblock=<file>", "Imports blocks from external blk000??.dat file on startup", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-par=<n>", strprintf("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)", + -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), false, OptionsCategory::OPTIONS); #ifndef WIN32 - strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)"), BITCOIN_PID_FILENAME)); + gArgs.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-pid"); #endif - strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. " + gArgs.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " - "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >%u = automatically prune block files to stay under the specified target size in MiB)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); - strUsage += HelpMessageOpt("-reindex", _("Rebuild chain state and block index from the blk*.dat files on disk")); - strUsage += HelpMessageOpt("-reindex-chainstate", _("Rebuild chain state from the currently indexed blocks")); + "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks", false, OptionsCategory::OPTIONS); #ifndef WIN32 - strUsage += HelpMessageOpt("-sysperms", _("Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)")); + gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-sysperms"); #endif - strUsage += HelpMessageOpt("-txindex", strprintf(_("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)"), DEFAULT_TXINDEX)); - - strUsage += HelpMessageGroup(_("Connection options:")); - strUsage += HelpMessageOpt("-addnode=<ip>", _("Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info)")); - strUsage += HelpMessageOpt("-banscore=<n>", strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), DEFAULT_BANSCORE_THRESHOLD)); - strUsage += HelpMessageOpt("-bantime=<n>", strprintf(_("Number of seconds to keep misbehaving peers from reconnecting (default: %u)"), DEFAULT_MISBEHAVING_BANTIME)); - strUsage += HelpMessageOpt("-bind=<addr>", _("Bind to given address and always listen on it. Use [host]:port notation for IPv6")); - strUsage += HelpMessageOpt("-connect=<ip>", _("Connect only to the specified node(s); -connect=0 disables automatic connections (the rules for this peer are the same as for -addnode)")); - strUsage += HelpMessageOpt("-discover", _("Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)")); - strUsage += HelpMessageOpt("-dns", _("Allow DNS lookups for -addnode, -seednode and -connect") + " " + strprintf(_("(default: %u)"), DEFAULT_NAME_LOOKUP)); - strUsage += HelpMessageOpt("-dnsseed", _("Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect used)")); - strUsage += HelpMessageOpt("-externalip=<ip>", _("Specify your own public address")); - strUsage += HelpMessageOpt("-forcednsseed", strprintf(_("Always query for peer addresses via DNS lookup (default: %u)"), DEFAULT_FORCEDNSSEED)); - strUsage += HelpMessageOpt("-listen", _("Accept connections from outside (default: 1 if no -proxy or -connect)")); - strUsage += HelpMessageOpt("-listenonion", strprintf(_("Automatically create Tor hidden service (default: %d)"), DEFAULT_LISTEN_ONION)); - strUsage += HelpMessageOpt("-maxconnections=<n>", strprintf(_("Maintain at most <n> connections to peers (default: %u)"), DEFAULT_MAX_PEER_CONNECTIONS)); - strUsage += HelpMessageOpt("-maxreceivebuffer=<n>", strprintf(_("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)"), DEFAULT_MAXRECEIVEBUFFER)); - strUsage += HelpMessageOpt("-maxsendbuffer=<n>", strprintf(_("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)"), DEFAULT_MAXSENDBUFFER)); - strUsage += HelpMessageOpt("-maxtimeadjustment", strprintf(_("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)"), DEFAULT_MAX_TIME_ADJUSTMENT)); - strUsage += HelpMessageOpt("-maxuploadtarget=<n>", strprintf(_("Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)"), DEFAULT_MAX_UPLOAD_TARGET)); - strUsage += HelpMessageOpt("-onion=<ip:port>", strprintf(_("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)"), "-proxy")); - strUsage += HelpMessageOpt("-onlynet=<net>", _("Only connect to nodes in network <net> (ipv4, ipv6 or onion)")); - strUsage += HelpMessageOpt("-peerbloomfilters", strprintf(_("Support filtering of blocks and transaction with bloom filters (default: %u)"), DEFAULT_PEERBLOOMFILTERS)); - strUsage += HelpMessageOpt("-permitbaremultisig", strprintf(_("Relay non-P2SH multisig (default: %u)"), DEFAULT_PERMIT_BAREMULTISIG)); - strUsage += HelpMessageOpt("-port=<port>", strprintf(_("Listen for connections on <port> (default: %u or testnet: %u)"), defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort())); - strUsage += HelpMessageOpt("-proxy=<ip:port>", _("Connect through SOCKS5 proxy")); - strUsage += HelpMessageOpt("-proxyrandomize", strprintf(_("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)"), DEFAULT_PROXYRANDOMIZE)); - strUsage += HelpMessageOpt("-seednode=<ip>", _("Connect to a node to retrieve peer addresses, and disconnect")); - strUsage += HelpMessageOpt("-timeout=<n>", strprintf(_("Specify connection timeout in milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT)); - strUsage += HelpMessageOpt("-torcontrol=<ip>:<port>", strprintf(_("Tor control port to use if onion listening enabled (default: %s)"), DEFAULT_TOR_CONTROL)); - strUsage += HelpMessageOpt("-torpassword=<pass>", _("Tor control port password (default: empty)")); + gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), false, OptionsCategory::OPTIONS); + + gArgs.AddArg("-addnode=<ip>", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-bantime=<n>", strprintf("Number of seconds to keep misbehaving peers from reconnecting (default: %u)", DEFAULT_MISBEHAVING_BANTIME), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -connect=0 disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-dnsseed", "Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect used)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-enablebip61", strprintf("Send reject messages per BIP61 (default: %u)", DEFAULT_ENABLE_BIP61), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-externalip=<ip>", "Specify your own public address", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-listen", "Accept connections from outside (default: 1 if no -proxy or -connect)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-listenonion", strprintf("Automatically create Tor hidden service (default: %d)", DEFAULT_LISTEN_ONION), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u)", DEFAULT_MAX_PEER_CONNECTIONS), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: -proxy)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u or testnet: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort()), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy", false, OptionsCategory::CONNECTION); + 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("-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 #if USE_UPNP - strUsage += HelpMessageOpt("-upnp", _("Use UPnP to map the listening port (default: 1 when listening and no -proxy)")); + gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", false, OptionsCategory::CONNECTION); #else - strUsage += HelpMessageOpt("-upnp", strprintf(_("Use UPnP to map the listening port (default: %u)"), 0)); + gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), false, OptionsCategory::CONNECTION); #endif +#else + hidden_args.emplace_back("-upnp"); #endif - strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6")); - strUsage += HelpMessageOpt("-whitelist=<IP address or network>", _("Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times.") + - " " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway")); + gArgs.AddArg("-whitebind=<addr>", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-whitelist=<IP address or network>", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times." + " Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway", false, OptionsCategory::CONNECTION); - strUsage += g_wallet_init_interface.GetHelpString(showDebug); + g_wallet_init_interface.AddWalletOptions(); #if ENABLE_ZMQ - strUsage += HelpMessageGroup(_("ZeroMQ notification options:")); - strUsage += HelpMessageOpt("-zmqpubhashblock=<address>", _("Enable publish hash block in <address>")); - strUsage += HelpMessageOpt("-zmqpubhashtx=<address>", _("Enable publish hash transaction in <address>")); - strUsage += HelpMessageOpt("-zmqpubrawblock=<address>", _("Enable publish raw block in <address>")); - strUsage += HelpMessageOpt("-zmqpubrawtx=<address>", _("Enable publish raw transaction in <address>")); + gArgs.AddArg("-zmqpubhashblock=<address>", "Enable publish hash block in <address>", false, OptionsCategory::ZMQ); + 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); +#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>"); #endif - strUsage += HelpMessageGroup(_("Debugging/Testing options:")); - if (showDebug) { - strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), DEFAULT_CHECKBLOCKS)); - strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), DEFAULT_CHECKLEVEL)); - strUsage += HelpMessageOpt("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. (default: %u)", defaultChainParams->DefaultConsistencyChecks())); - strUsage += HelpMessageOpt("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u)", defaultChainParams->DefaultConsistencyChecks())); - strUsage += HelpMessageOpt("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED)); - strUsage += HelpMessageOpt("-disablesafemode", strprintf("Disable safemode, override a real safe mode event (default: %u)", DEFAULT_DISABLE_SAFEMODE)); - strUsage += HelpMessageOpt("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used"); - strUsage += HelpMessageOpt("-testsafemode", strprintf("Force safe mode (default: %u)", DEFAULT_TESTSAFEMODE)); - strUsage += HelpMessageOpt("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages"); - strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT)); - strUsage += HelpMessageOpt("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT)); - - strUsage += HelpMessageOpt("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT)); - strUsage += HelpMessageOpt("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT)); - strUsage += HelpMessageOpt("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT)); - strUsage += HelpMessageOpt("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT)); - strUsage += HelpMessageOpt("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)"); - strUsage += HelpMessageOpt("-addrmantest", "Allows to test address relay on localhost"); - } - strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " + - _("If <category> is not supplied or if <category> = 1, output all debugging information.") + " " + _("<category> can be:") + " " + ListLogCategories() + "."); - strUsage += HelpMessageOpt("-debugexclude=<category>", strprintf(_("Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except one or more specified categories."))); - strUsage += HelpMessageOpt("-help-debug", _("Show all debugging options (usage: --help -help-debug)")); - strUsage += HelpMessageOpt("-logips", strprintf(_("Include IP addresses in debug output (default: %u)"), DEFAULT_LOGIPS)); - strUsage += HelpMessageOpt("-logtimestamps", strprintf(_("Prepend debug output with timestamp (default: %u)"), DEFAULT_LOGTIMESTAMPS)); - if (showDebug) - { - strUsage += HelpMessageOpt("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS)); - strUsage += HelpMessageOpt("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)"); - strUsage += HelpMessageOpt("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE)); - strUsage += HelpMessageOpt("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE)); - } - strUsage += HelpMessageOpt("-maxtxfee=<amt>", strprintf(_("Maximum total fees (in %s) to use in a single wallet transaction or raw transaction; setting this too low may abort large transactions (default: %s)"), - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE))); - if (showDebug) - { - strUsage += HelpMessageOpt("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY)); - } - strUsage += HelpMessageOpt("-printtoconsole", _("Send trace/debug info to console instead of debug.log file")); - strUsage += HelpMessageOpt("-shrinkdebugfile", _("Shrink debug.log file on client startup (default: 1 when no -debug)")); - strUsage += HelpMessageOpt("-uacomment=<cmt>", _("Append comment to the user agent string")); - - AppendParamsHelpMessages(strUsage, showDebug); - - strUsage += HelpMessageGroup(_("Node relay options:")); - if (showDebug) { - strUsage += HelpMessageOpt("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard())); - strUsage += HelpMessageOpt("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE))); - strUsage += HelpMessageOpt("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to defined dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE))); - } - strUsage += HelpMessageOpt("-bytespersigop", strprintf(_("Equivalent bytes per sigop in transactions for relay and mining (default: %u)"), DEFAULT_BYTES_PER_SIGOP)); - strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), DEFAULT_ACCEPT_DATACARRIER)); - strUsage += HelpMessageOpt("-datacarriersize", strprintf(_("Maximum size of data in data carrier transactions we relay and mine (default: %u)"), MAX_OP_RETURN_RELAY)); - strUsage += HelpMessageOpt("-mempoolreplacement", strprintf(_("Enable transaction replacement in the memory pool (default: %u)"), DEFAULT_ENABLE_REPLACEMENT)); - strUsage += HelpMessageOpt("-minrelaytxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)"), - CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE))); - strUsage += HelpMessageOpt("-whitelistforcerelay", strprintf(_("Force relay of transactions from whitelisted peers even if they violate local relay policy (default: %d)"), DEFAULT_WHITELISTFORCERELAY)); - strUsage += HelpMessageOpt("-whitelistrelay", strprintf(_("Accept relayed transactions received from whitelisted peers even when not relaying transactions (default: %d)"), DEFAULT_WHITELISTRELAY)); - - strUsage += HelpMessageGroup(_("Block creation options:")); - strUsage += HelpMessageOpt("-blockmaxweight=<n>", strprintf(_("Set maximum BIP141 block weight (default: %d)"), DEFAULT_BLOCK_MAX_WEIGHT)); - strUsage += HelpMessageOpt("-blockmintxfee=<amt>", strprintf(_("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE))); - if (showDebug) - strUsage += HelpMessageOpt("-blockversion=<n>", "Override block version to test forking scenarios"); - - strUsage += HelpMessageGroup(_("RPC server options:")); - strUsage += HelpMessageOpt("-rest", strprintf(_("Accept public REST requests (default: %u)"), DEFAULT_REST_ENABLE)); - strUsage += HelpMessageOpt("-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")); - strUsage += HelpMessageOpt("-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/rpcuser. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times")); - strUsage += HelpMessageOpt("-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)")); - strUsage += HelpMessageOpt("-rpccookiefile=<loc>", _("Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)")); - strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort())); - strUsage += HelpMessageOpt("-rpcserialversion", strprintf(_("Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d)"), DEFAULT_RPC_SERIALIZE_VERSION)); - if (showDebug) - strUsage += HelpMessageOpt("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT)); - strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), DEFAULT_HTTP_THREADS)); - strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); - if (showDebug) - strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE)); - strUsage += HelpMessageOpt("-server", _("Accept command line and JSON-RPC commands")); - - return strUsage; + gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of -checkblocks is (0-4, default: %u)", DEFAULT_CHECKLEVEL), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. (default: %u)", defaultChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u)", defaultChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-debug=<category>", strprintf("Output debugging information (default: %u, supplying <category> is optional)", 0) + ". " + + "If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + ListLogCategories() + ".", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-debugexclude=<category>", strprintf("Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except one or more specified categories."), false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-help-debug", "Show all debugging options (usage: --help -help-debug)", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction or raw transaction; setting this too low may abort large transactions (default: %s)", + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set debuglogfile=0)", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", false, OptionsCategory::DEBUG_TEST); + + SetupChainParamsBaseOptions(); + + gArgs.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), true, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), true, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to defined dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), true, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-mempoolreplacement", strprintf("Enable transaction replacement in the memory pool (default: %u)", DEFAULT_ENABLE_REPLACEMENT), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", + CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted peers even if they violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistrelay", strprintf("Accept relayed transactions received from whitelisted peers even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), false, OptionsCategory::NODE_RELAY); + + + gArgs.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), false, OptionsCategory::BLOCK_CREATION); + gArgs.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), false, OptionsCategory::BLOCK_CREATION); + gArgs.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", true, OptionsCategory::BLOCK_CREATION); + + 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("-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 or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()), false, OptionsCategory::RPC); + gArgs.AddArg("-rpcserialversion", strprintf("Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d)", DEFAULT_RPC_SERIALIZE_VERSION), false, OptionsCategory::RPC); + gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), true, OptionsCategory::RPC); + gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), false, OptionsCategory::RPC); + gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::RPC); + gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), true, OptionsCategory::RPC); + gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", false, OptionsCategory::RPC); + +#if HAVE_DECL_DAEMON + gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-daemon"); +#endif + + // Add the hidden options + gArgs.AddHiddenArgs(hidden_args); } std::string LicenseInfo() @@ -591,7 +593,7 @@ struct CImportingNow // rev files since they'll be rewritten by the reindex anyway. This ensures that vinfoBlockFile // is in sync with what's actually on disk by the time we start downloading, so that pruning // works correctly. -void CleanupBlockRevFiles() +static void CleanupBlockRevFiles() { std::map<std::string, fs::path> mapBlockFiles; @@ -617,7 +619,7 @@ void CleanupBlockRevFiles() // keeping a separate counter. Once we hit a gap (or if 0 doesn't exist) // start removing block files. int nContigCounter = 0; - for (const std::pair<std::string, fs::path>& item : mapBlockFiles) { + for (const std::pair<const std::string, fs::path>& item : mapBlockFiles) { if (atoi(item.first) == nContigCounter) { nContigCounter++; continue; @@ -626,7 +628,7 @@ void CleanupBlockRevFiles() } } -void ThreadImport(std::vector<fs::path> vImportFiles) +static void ThreadImport(std::vector<fs::path> vImportFiles) { const CChainParams& chainparams = Params(); RenameThread("bitcoin-loadblk"); @@ -698,14 +700,14 @@ void ThreadImport(std::vector<fs::path> vImportFiles) if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { LoadMempool(); } - g_is_mempool_loaded = !fRequestShutdown; + g_is_mempool_loaded = !ShutdownRequested(); } /** Sanity checks * Ensure that Bitcoin is running in a usable environment with all * necessary library support. */ -bool InitSanityCheck(void) +static bool InitSanityCheck(void) { if(!ECC_InitSanityCheck()) { InitError("Elliptic curve cryptography sanity check failure. Aborting."); @@ -723,7 +725,7 @@ bool InitSanityCheck(void) return true; } -bool AppInitServers() +static bool AppInitServers() { RPCServer::OnStarted(&OnRPCStarted); RPCServer::OnStopped(&OnRPCStopped); @@ -822,15 +824,18 @@ static std::string ResolveErrMsg(const char * const optname, const std::string& */ void InitLogging() { + g_logger->m_print_to_file = !gArgs.IsArgNegated("-debuglogfile"); + g_logger->m_file_path = AbsPathForConfigVal(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)); + // Add newlines to the logfile to distinguish this execution from the last // one; called before console logging is set up, so this is only sent to // debug.log. LogPrintf("\n\n\n\n\n"); - fPrintToConsole = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false)); - fPrintToDebugLog = !gArgs.IsArgNegated("-debuglogfile"); - fLogTimestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); - fLogTimeMicros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); + g_logger->m_print_to_console = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false)); + g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); + g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); + fLogIPs = gArgs.GetBoolArg("-logips", DEFAULT_LOGIPS); std::string version_string = FormatFullVersion(); @@ -905,6 +910,8 @@ bool AppInitBasicSetup() // Ignore SIGPIPE, otherwise it will bring the daemon down if the client closes unexpectedly signal(SIGPIPE, SIG_IGN); +#else + SetConsoleCtrlHandler(consoleCtrlHandler, true); #endif std::set_new_handler(new_handler_terminate); @@ -941,7 +948,8 @@ bool AppInitParameterInteraction() nMaxConnections = std::max(nUserMaxConnections, 0); // Trim requested connection counts, to fit into system limitations - nMaxConnections = std::max(std::min(nMaxConnections, FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); + // <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); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); @@ -958,24 +966,18 @@ bool AppInitParameterInteraction() if (std::none_of(categories.begin(), categories.end(), [](std::string cat){return cat == "0" || cat == "none";})) { for (const auto& cat : categories) { - uint32_t flag = 0; - if (!GetLogCategory(&flag, &cat)) { + if (!g_logger->EnableCategory(cat)) { InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)); - continue; } - logCategories |= flag; } } } // Now remove the logging categories which were explicitly excluded for (const std::string& cat : gArgs.GetArgs("-debugexclude")) { - uint32_t flag = 0; - if (!GetLogCategory(&flag, &cat)) { + if (!g_logger->DisableCategory(cat)) { InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debugexclude", cat)); - continue; } - logCategories &= ~flag; } // Check for -debugnet @@ -1226,18 +1228,19 @@ bool AppInitMain() #ifndef WIN32 CreatePidFile(GetPidFile(), getpid()); #endif - if (fPrintToDebugLog) { - if (gArgs.GetBoolArg("-shrinkdebugfile", logCategories == BCLog::NONE)) { + if (g_logger->m_print_to_file) { + if (gArgs.GetBoolArg("-shrinkdebugfile", g_logger->DefaultShrinkDebugFile())) { // Do this first since it both loads a bunch of debug.log into memory, // and because this needs to happen before any other debug.log printing - ShrinkDebugFile(); + g_logger->ShrinkDebugFile(); } - if (!OpenDebugLog()) { - return InitError(strprintf("Could not open debug log file %s", GetDebugLogPath().string())); + if (!g_logger->OpenDebugLog()) { + return InitError(strprintf("Could not open debug log file %s", + g_logger->m_file_path.string())); } } - if (!fLogTimestamps) + if (!g_logger->m_log_timestamps) LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); @@ -1274,6 +1277,9 @@ bool AppInitMain() */ RegisterAllCoreRPCCommands(tableRPC); g_wallet_init_interface.RegisterRPC(tableRPC); +#if ENABLE_ZMQ + RegisterZMQRPCCommands(tableRPC); +#endif /* Start the RPC server already. It will be started in "warmup" mode * and not really process calls already (but it will signify connections @@ -1287,8 +1293,6 @@ bool AppInitMain() return InitError(_("Unable to start HTTP server. See debug log for details.")); } - int64_t nStart; - // ********************************************************* Step 5: verify wallet database integrity if (!g_wallet_init_interface.Verify()) return false; @@ -1302,7 +1306,7 @@ bool AppInitMain() 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)); + peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61))); RegisterValidationInterface(peerLogic.get()); // sanitize comments per BIP-0014, format user agent and check total size @@ -1340,7 +1344,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_TOR); + SetLimited(NET_ONION); if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) { @@ -1353,9 +1357,9 @@ bool AppInitMain() SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); - SetProxy(NET_TOR, addrProxy); + SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); - SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later + SetLimited(NET_ONION, false); // 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 @@ -1364,7 +1368,7 @@ bool AppInitMain() std::string onionArg = gArgs.GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetLimited(NET_TOR); // set onions as unreachable + SetLimited(NET_ONION); // set onions as unreachable } else { CService onionProxy; if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) { @@ -1373,8 +1377,8 @@ bool AppInitMain() proxyType addrOnion = proxyType(onionProxy, proxyRandomize); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); - SetProxy(NET_TOR, addrOnion); - SetLimited(NET_TOR, false); + SetProxy(NET_ONION, addrOnion); + SetLimited(NET_ONION, false); } } @@ -1392,10 +1396,10 @@ bool AppInitMain() } #if ENABLE_ZMQ - pzmqNotificationInterface = CZMQNotificationInterface::Create(); + g_zmq_notification_interface = CZMQNotificationInterface::Create(); - if (pzmqNotificationInterface) { - RegisterValidationInterface(pzmqNotificationInterface); + if (g_zmq_notification_interface) { + RegisterValidationInterface(g_zmq_notification_interface); } #endif uint64_t nMaxOutboundLimit = 0; //unlimited unless -maxuploadtarget is set @@ -1414,9 +1418,10 @@ bool AppInitMain() int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache - int64_t nBlockTreeDBCache = nTotalCache / 8; - nBlockTreeDBCache = std::min(nBlockTreeDBCache, (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20); + int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); nTotalCache -= nBlockTreeDBCache; + int64_t nTxIndexCache = std::min(nTotalCache / 8, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); + nTotalCache -= nTxIndexCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache nTotalCache -= nCoinDBCache; @@ -1424,11 +1429,14 @@ bool AppInitMain() int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); + if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { + LogPrintf("* Using %.1fMiB for transaction index database\n", nTxIndexCache * (1.0 / 1024 / 1024)); + } LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for in-memory UTXO set (plus up to %.1fMiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); bool fLoaded = false; - while (!fLoaded && !fRequestShutdown) { + while (!fLoaded && !ShutdownRequested()) { bool fReset = fReindex; std::string strLoadError; @@ -1436,8 +1444,8 @@ bool AppInitMain() LOCK(cs_main); - nStart = GetTimeMillis(); do { + const int64_t load_block_index_start_time = GetTimeMillis(); try { UnloadBlockIndex(); pcoinsTip.reset(); @@ -1455,11 +1463,10 @@ bool AppInitMain() CleanupBlockRevFiles(); } - if (fRequestShutdown) break; + if (ShutdownRequested()) break; - // LoadBlockIndex will load fTxIndex from the db, or set it if - // we're reindexing. It will also load fHavePruned if we've - // ever removed a block file from disk. + // LoadBlockIndex will load fHavePruned if we've ever removed a + // block file from disk. // Note that it also sets fReindex based on the disk flag! // From here on out fReindex and fReset mean something different! if (!LoadBlockIndex(chainparams)) { @@ -1473,12 +1480,6 @@ bool AppInitMain() return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); } - // Check for changed -txindex state - if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { - strLoadError = _("You need to rebuild the database using -reindex to change -txindex"); - break; - } - // Check for changed -prune state. What we are concerned about is a user who has pruned blocks // in the past, but is now trying to run unpruned. if (fHavePruned && !fPruneMode) { @@ -1567,9 +1568,10 @@ bool AppInitMain() } fLoaded = true; + LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); } while(false); - if (!fLoaded && !fRequestShutdown) { + if (!fLoaded && !ShutdownRequested()) { // first suggest a reindex if (!fReset) { bool fRet = uiInterface.ThreadSafeQuestion( @@ -1578,7 +1580,7 @@ bool AppInitMain() "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (fRet) { fReindex = true; - fRequestShutdown = false; + AbortShutdown(); } else { LogPrintf("Aborted block database rebuild. Exiting.\n"); return false; @@ -1592,14 +1594,10 @@ bool AppInitMain() // As LoadBlockIndex can take several minutes, it's possible the user // requested to kill the GUI during the last operation. If so, exit. // As the program has not fully started yet, Shutdown() is possibly overkill. - if (fRequestShutdown) - { + if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exiting.\n"); return false; } - if (fLoaded) { - LogPrintf(" block index %15dms\n", GetTimeMillis() - nStart); - } fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION); @@ -1608,10 +1606,16 @@ bool AppInitMain() ::feeEstimator.Read(est_filein); fFeeEstimatesInitialized = true; - // ********************************************************* Step 8: load wallet + // ********************************************************* Step 8: start indexers + if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { + g_txindex = MakeUnique<TxIndex>(nTxIndexCache, false, fReindex); + g_txindex->Start(); + } + + // ********************************************************* Step 9: load wallet if (!g_wallet_init_interface.Open()) return false; - // ********************************************************* Step 9: data directory maintenance + // ********************************************************* Step 10: data directory maintenance // if pruning, unset the service bit and perform the initial blockstore prune // after any wallet rescanning has taken place. @@ -1633,7 +1637,7 @@ bool AppInitMain() nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS); } - // ********************************************************* Step 10: import blocks + // ********************************************************* Step 11: import blocks if (!CheckDiskSpace() && !CheckDiskSpace(0, true)) return false; @@ -1672,7 +1676,7 @@ bool AppInitMain() return false; } - // ********************************************************* Step 11: start node + // ********************************************************* Step 12: start node int chain_active_height; @@ -1750,7 +1754,7 @@ bool AppInitMain() return false; } - // ********************************************************* Step 12: finished + // ********************************************************* Step 13: finished SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); diff --git a/src/init.h b/src/init.h index 000c8c95e4..0c85d3c9dc 100644 --- a/src/init.h +++ b/src/init.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -8,6 +8,7 @@ #include <memory> #include <string> +#include <util.h> class CScheduler; class CWallet; @@ -20,8 +21,6 @@ namespace boost class thread_group; } // namespace boost -void StartShutdown(); -bool ShutdownRequested(); /** Interrupt threads */ void Interrupt(); void Shutdown(); @@ -60,14 +59,11 @@ bool AppInitLockDataDirectory(); */ bool AppInitMain(); -/** The help message mode determines what help message to show */ -enum class HelpMessageMode { - BITCOIND, - BITCOIN_QT -}; +/** + * Setup the arguments for gArgs + */ +void SetupServerArgs(); -/** Help for options shared between UI and daemon (for -help) */ -std::string HelpMessage(HelpMessageMode mode); /** Returns licensing information (for -version) */ std::string LicenseInfo(); diff --git a/src/interfaces/handler.cpp b/src/interfaces/handler.cpp index 8e45faa2a5..80f461f4d3 100644 --- a/src/interfaces/handler.cpp +++ b/src/interfaces/handler.cpp @@ -4,7 +4,7 @@ #include <interfaces/handler.h> -#include <util.h> +#include <utilmemory.h> #include <boost/signals2/connection.hpp> #include <utility> diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 55786c807f..db371d104e 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -21,6 +21,7 @@ #include <primitives/block.h> #include <rpc/server.h> #include <scheduler.h> +#include <shutdown.h> #include <sync.h> #include <txmempool.h> #include <ui_interface.h> @@ -48,11 +49,11 @@ namespace { class NodeImpl : public Node { - void parseParameters(int argc, const char* const argv[]) override + bool parseParameters(int argc, const char* const argv[], std::string& error) override { - gArgs.ParseParameters(argc, argv); + return gArgs.ParseParameters(argc, argv, error); } - void readConfigFile(const std::string& conf_path) override { gArgs.ReadConfigFile(conf_path); } + bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error); } 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); } @@ -60,7 +61,7 @@ class NodeImpl : public Node void initLogging() override { InitLogging(); } void initParameterInteraction() override { InitParameterInteraction(); } std::string getWarnings(const std::string& type) override { return GetWarnings(type); } - uint32_t getLogCategories() override { return ::logCategories; } + uint32_t getLogCategories() override { return g_logger->GetCategoryMask(); } bool baseInitialize() override { return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && @@ -83,7 +84,7 @@ class NodeImpl : public Node StopMapPort(); } } - std::string helpMessage(HelpMessageMode mode) override { return HelpMessage(mode); } + void setupServerArgs() override { return SetupServerArgs(); } bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { @@ -222,8 +223,8 @@ class NodeImpl : public Node { #ifdef ENABLE_WALLET std::vector<std::unique_ptr<Wallet>> wallets; - for (CWallet* wallet : GetWallets()) { - wallets.emplace_back(MakeWallet(*wallet)); + for (const std::shared_ptr<CWallet>& wallet : GetWallets()) { + wallets.emplace_back(MakeWallet(wallet)); } return wallets; #else @@ -249,7 +250,7 @@ class NodeImpl : public Node std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) override { CHECK_WALLET( - return MakeHandler(::uiInterface.LoadWallet.connect([fn](CWallet* wallet) { fn(MakeWallet(*wallet)); }))); + return MakeHandler(::uiInterface.LoadWallet.connect([fn](std::shared_ptr<CWallet> wallet) { fn(MakeWallet(wallet)); }))); } std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) override { diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 3cebe53eb0..8185c015a9 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -7,7 +7,6 @@ #include <addrdb.h> // For banmap_t #include <amount.h> // For CAmount -#include <init.h> // For HelpMessageMode #include <net.h> // For CConnman::NumConnections #include <netaddress.h> // For Network @@ -39,7 +38,7 @@ public: virtual ~Node() {} //! Set command line arguments. - virtual void parseParameters(int argc, const char* const argv[]) = 0; + virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0; //! Set a command line argument if it doesn't already have a value virtual bool softSetArg(const std::string& arg, const std::string& value) = 0; @@ -48,7 +47,7 @@ public: virtual bool softSetBoolArg(const std::string& arg, bool value) = 0; //! Load settings from configuration file. - virtual void readConfigFile(const std::string& conf_path) = 0; + virtual bool readConfigFiles(std::string& error) = 0; //! Choose network parameters. virtual void selectParams(const std::string& network) = 0; @@ -83,8 +82,8 @@ public: //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; - //! Get help message string. - virtual std::string helpMessage(HelpMessageMode mode) = 0; + //! Setup arguments + virtual void setupServerArgs() = 0; //! Map port. virtual void mapPort(bool use_upnp) = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index f4dfdae8ae..b42c9b63df 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -93,7 +93,6 @@ WalletTxStatus MakeWalletTxStatus(const CWalletTx& wtx) 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.request_count = wtx.GetRequestCount(); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); @@ -118,7 +117,7 @@ WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int de class WalletImpl : public Wallet { public: - WalletImpl(CWallet& wallet) : m_wallet(wallet) {} + WalletImpl(const std::shared_ptr<CWallet>& wallet) : m_shared_wallet(wallet), m_wallet(*wallet.get()) {} bool encryptWallet(const SecureString& wallet_passphrase) override { @@ -152,7 +151,10 @@ public: { return m_wallet.DelAddressBook(dest); } - bool getAddress(const CTxDestination& dest, std::string* name, isminetype* is_mine) override + bool getAddress(const CTxDestination& dest, + std::string* name, + isminetype* is_mine, + std::string* purpose) override { LOCK(m_wallet.cs_wallet); auto it = m_wallet.mapAddressBook.find(dest); @@ -165,6 +167,9 @@ public: if (is_mine) { *is_mine = IsMine(m_wallet, dest); } + if (purpose) { + *purpose = it->second.purpose; + } return true; } std::vector<WalletAddress> getAddresses() override @@ -333,7 +338,7 @@ public: result.immature_balance = m_wallet.GetImmatureBalance(); result.have_watch_only = m_wallet.HaveWatchOnly(); if (result.have_watch_only) { - result.watch_only_balance = m_wallet.GetWatchOnlyBalance(); + result.watch_only_balance = m_wallet.GetBalance(ISMINE_WATCH_ONLY); result.unconfirmed_watch_only_balance = m_wallet.GetUnconfirmedWatchOnlyBalance(); result.immature_watch_only_balance = m_wallet.GetImmatureWatchOnlyBalance(); } @@ -423,6 +428,10 @@ public: bool hdEnabled() override { return m_wallet.IsHDEnabled(); } OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; } OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; } + std::unique_ptr<Handler> handleUnload(UnloadFn fn) override + { + return MakeHandler(m_wallet.NotifyUnload.connect(fn)); + } std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override { return MakeHandler(m_wallet.ShowProgress.connect(fn)); @@ -447,11 +456,12 @@ public: return MakeHandler(m_wallet.NotifyWatchonlyChanged.connect(fn)); } + std::shared_ptr<CWallet> m_shared_wallet; CWallet& m_wallet; }; } // namespace -std::unique_ptr<Wallet> MakeWallet(CWallet& wallet) { return MakeUnique<WalletImpl>(wallet); } +std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return MakeUnique<WalletImpl>(wallet); } } // namespace interfaces diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 2a03f6c60a..96e742eaca 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -99,8 +99,9 @@ public: //! Look up address in wallet, return whether exists. virtual bool getAddress(const CTxDestination& dest, - std::string* name = nullptr, - isminetype* is_mine = nullptr) = 0; + std::string* name, + isminetype* is_mine, + std::string* purpose) = 0; //! Get wallet address list. virtual std::vector<WalletAddress> getAddresses() = 0; @@ -241,6 +242,10 @@ public: // Get default change type. virtual OutputType getDefaultChangeType() = 0; + //! Register handler for unload message. + using UnloadFn = std::function<void()>; + virtual std::unique_ptr<Handler> handleUnload(UnloadFn fn) = 0; + //! Register handler for show progress messages. using ShowProgressFn = std::function<void(const std::string& title, int progress)>; virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0; @@ -341,7 +346,6 @@ struct WalletTxStatus int block_height; int blocks_to_maturity; int depth_in_main_chain; - int request_count; unsigned int time_received; uint32_t lock_time; bool is_final; @@ -362,7 +366,7 @@ struct WalletTxOut //! Return implementation of Wallet interface. This function will be undefined //! in builds where ENABLE_WALLET is false. -std::unique_ptr<Wallet> MakeWallet(CWallet& wallet); +std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet); } // namespace interfaces diff --git a/src/key.cpp b/src/key.cpp index 042e687772..94be179bfb 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -273,7 +273,7 @@ bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { return key.Derive(out.key, out.chaincode, _nChild, chaincode); } -void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) { +void CExtKey::SetSeed(const unsigned char *seed, unsigned int nSeedLen) { static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; std::vector<unsigned char, secure_allocator<unsigned char>> vout(64); CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data()); @@ -158,7 +158,7 @@ struct CExtKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; - void SetMaster(const unsigned char* seed, unsigned int nSeedLen); + void SetSeed(const unsigned char* seed, unsigned int nSeedLen); template <typename Stream> void Serialize(Stream& s) const { diff --git a/src/keystore.cpp b/src/keystore.cpp index e69d518890..ea93ed69fa 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -195,3 +195,10 @@ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) } return CKeyID(); } + +bool HaveKey(const CKeyStore& store, const CKey& key) +{ + CKey key2; + key2.Set(key.begin(), key.end(), !key.IsCompressed()); + return store.HaveKey(key.GetPubKey().GetID()) || store.HaveKey(key2.GetPubKey().GetID()); +} diff --git a/src/keystore.h b/src/keystore.h index fa912cb195..f64024c7e7 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -38,23 +38,23 @@ public: virtual bool HaveWatchOnly() const =0; }; -typedef std::map<CKeyID, CKey> KeyMap; -typedef std::map<CKeyID, CPubKey> WatchKeyMap; -typedef std::map<CScriptID, CScript > ScriptMap; -typedef std::set<CScript> WatchOnlySet; - /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore { protected: mutable CCriticalSection cs_KeyStore; - KeyMap mapKeys; - WatchKeyMap mapWatchKeys; - ScriptMap mapScripts; - WatchOnlySet setWatchOnly; + using KeyMap = std::map<CKeyID, CKey>; + using WatchKeyMap = std::map<CKeyID, CPubKey>; + using ScriptMap = std::map<CScriptID, CScript>; + using WatchOnlySet = std::set<CScript>; + + KeyMap mapKeys GUARDED_BY(cs_KeyStore); + WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); + ScriptMap mapScripts GUARDED_BY(cs_KeyStore); + WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); - void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey); + void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; @@ -74,10 +74,10 @@ public: bool HaveWatchOnly() const override; }; -typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial; -typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap; - /** Return the CKeyID of the key involved in a script (if there is a unique one). */ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest); +/** Checks if a CKey is in the given CKeyStore compressed or otherwise*/ +bool HaveKey(const CKeyStore& store, const CKey& key); + #endif // BITCOIN_KEYSTORE_H diff --git a/src/logging.cpp b/src/logging.cpp index e481582321..e8e22cbf97 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -4,97 +4,93 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <logging.h> -#include <util.h> -#include <utilstrencodings.h> - -#include <list> -#include <mutex> +#include <utiltime.h> const char * const DEFAULT_DEBUGLOGFILE = "debug.log"; -bool fPrintToConsole = false; -bool fPrintToDebugLog = true; - -bool fLogTimestamps = DEFAULT_LOGTIMESTAMPS; -bool fLogTimeMicros = DEFAULT_LOGTIMEMICROS; -bool fLogIPs = DEFAULT_LOGIPS; -std::atomic<bool> fReopenDebugLog(false); - -/** Log categories bitfield. */ -std::atomic<uint32_t> logCategories(0); -/** - * LogPrintf() has been broken a couple of times now - * by well-meaning people adding mutexes in the most straightforward way. - * It breaks because it may be called by global destructors during shutdown. - * Since the order of destruction of static/global objects is undefined, - * defining a mutex as a global object doesn't work (the mutex gets - * destroyed, and then some later destructor calls OutputDebugStringF, - * maybe indirectly, and you get a core dump at shutdown trying to lock - * the mutex). - */ - -static std::once_flag debugPrintInitFlag; - /** - * We use std::call_once() to make sure mutexDebugLog and - * vMsgsBeforeOpenLog are initialized in a thread-safe manner. + * NOTE: the logger instances is leaked on exit. This is ugly, but will be + * cleaned up by the OS/libc. Defining a logger as a global object doesn't work + * since the order of destruction of static/global objects is undefined. + * Consider if the logger gets destroyed, and then some later destructor calls + * LogPrintf, maybe indirectly, and you get a core dump at shutdown trying to + * access the logger. When the shutdown sequence is fully audited and tested, + * explicit destruction of these objects can be implemented by changing this + * from a raw pointer to a std::unique_ptr. * - * NOTE: fileout, mutexDebugLog and sometimes vMsgsBeforeOpenLog - * are leaked on exit. This is ugly, but will be cleaned up by - * the OS/libc. When the shutdown sequence is fully audited and - * tested, explicit destruction of these objects can be implemented. + * This method of initialization was originally introduced in + * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c. */ -static FILE* fileout = nullptr; -static std::mutex* mutexDebugLog = nullptr; -static std::list<std::string>* vMsgsBeforeOpenLog; +BCLog::Logger* const g_logger = new BCLog::Logger(); + +bool fLogIPs = DEFAULT_LOGIPS; static int FileWriteStr(const std::string &str, FILE *fp) { return fwrite(str.data(), 1, str.size(), fp); } -static void DebugPrintInit() +bool BCLog::Logger::OpenDebugLog() { - assert(mutexDebugLog == nullptr); - mutexDebugLog = new std::mutex(); - vMsgsBeforeOpenLog = new std::list<std::string>; + std::lock_guard<std::mutex> scoped_lock(m_file_mutex); + + assert(m_fileout == nullptr); + assert(!m_file_path.empty()); + + m_fileout = fsbridge::fopen(m_file_path, "a"); + if (!m_fileout) { + return false; + } + + setbuf(m_fileout, nullptr); // unbuffered + // dump buffered messages from before we opened the log + while (!m_msgs_before_open.empty()) { + FileWriteStr(m_msgs_before_open.front(), m_fileout); + m_msgs_before_open.pop_front(); + } + + return true; } -fs::path GetDebugLogPath() +void BCLog::Logger::EnableCategory(BCLog::LogFlags flag) { - fs::path logfile(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)); - return AbsPathForConfigVal(logfile); + m_categories |= flag; } -bool OpenDebugLog() +bool BCLog::Logger::EnableCategory(const std::string& str) { - std::call_once(debugPrintInitFlag, &DebugPrintInit); - std::lock_guard<std::mutex> scoped_lock(*mutexDebugLog); + BCLog::LogFlags flag; + if (!GetLogCategory(flag, str)) return false; + EnableCategory(flag); + return true; +} - assert(fileout == nullptr); - assert(vMsgsBeforeOpenLog); - fs::path pathDebug = GetDebugLogPath(); +void BCLog::Logger::DisableCategory(BCLog::LogFlags flag) +{ + m_categories &= ~flag; +} - fileout = fsbridge::fopen(pathDebug, "a"); - if (!fileout) { - return false; - } +bool BCLog::Logger::DisableCategory(const std::string& str) +{ + BCLog::LogFlags flag; + if (!GetLogCategory(flag, str)) return false; + DisableCategory(flag); + return true; +} - setbuf(fileout, nullptr); // unbuffered - // dump buffered messages from before we opened the log - while (!vMsgsBeforeOpenLog->empty()) { - FileWriteStr(vMsgsBeforeOpenLog->front(), fileout); - vMsgsBeforeOpenLog->pop_front(); - } +bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const +{ + return (m_categories.load(std::memory_order_relaxed) & category) != 0; +} - delete vMsgsBeforeOpenLog; - vMsgsBeforeOpenLog = nullptr; - return true; +bool BCLog::Logger::DefaultShrinkDebugFile() const +{ + return m_categories == BCLog::NONE; } struct CLogCategoryDesc { - uint32_t flag; + BCLog::LogFlags flag; std::string category; }; @@ -127,19 +123,17 @@ const CLogCategoryDesc LogCategories[] = {BCLog::ALL, "all"}, }; -bool GetLogCategory(uint32_t *f, const std::string *str) +bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) { - if (f && str) { - if (*str == "") { - *f = BCLog::ALL; + if (str == "") { + flag = BCLog::ALL; + return true; + } + for (const CLogCategoryDesc& category_desc : LogCategories) { + if (category_desc.category == str) { + flag = category_desc.flag; return true; } - for (unsigned int i = 0; i < ARRAYLEN(LogCategories); i++) { - if (LogCategories[i].category == *str) { - *f = LogCategories[i].flag; - return true; - } - } } return false; } @@ -148,11 +142,11 @@ std::string ListLogCategories() { std::string ret; int outcount = 0; - for (unsigned int i = 0; i < ARRAYLEN(LogCategories); i++) { + for (const CLogCategoryDesc& category_desc : LogCategories) { // Omit the special cases. - if (LogCategories[i].flag != BCLog::NONE && LogCategories[i].flag != BCLog::ALL) { + if (category_desc.flag != BCLog::NONE && category_desc.flag != BCLog::ALL) { if (outcount != 0) ret += ", "; - ret += LogCategories[i].category; + ret += category_desc.category; outcount++; } } @@ -162,34 +156,29 @@ std::string ListLogCategories() std::vector<CLogCategoryActive> ListActiveLogCategories() { std::vector<CLogCategoryActive> ret; - for (unsigned int i = 0; i < ARRAYLEN(LogCategories); i++) { + for (const CLogCategoryDesc& category_desc : LogCategories) { // Omit the special cases. - if (LogCategories[i].flag != BCLog::NONE && LogCategories[i].flag != BCLog::ALL) { + if (category_desc.flag != BCLog::NONE && category_desc.flag != BCLog::ALL) { CLogCategoryActive catActive; - catActive.category = LogCategories[i].category; - catActive.active = LogAcceptCategory(LogCategories[i].flag); + catActive.category = category_desc.category; + catActive.active = LogAcceptCategory(category_desc.flag); ret.push_back(catActive); } } return ret; } -/** - * fStartedNewLine is a state variable held by the calling context that will - * suppress printing of the timestamp when multiple calls are made that don't - * end in a newline. Initialize it to true, and hold it, in the calling context. - */ -static std::string LogTimestampStr(const std::string &str, std::atomic_bool *fStartedNewLine) +std::string BCLog::Logger::LogTimestampStr(const std::string &str) { std::string strStamped; - if (!fLogTimestamps) + if (!m_log_timestamps) return str; - if (*fStartedNewLine) { + if (m_started_new_line) { int64_t nTimeMicros = GetTimeMicros(); strStamped = FormatISO8601DateTime(nTimeMicros/1000000); - if (fLogTimeMicros) { + if (m_log_time_micros) { strStamped.pop_back(); strStamped += strprintf(".%06dZ", nTimeMicros%1000000); } @@ -202,63 +191,60 @@ static std::string LogTimestampStr(const std::string &str, std::atomic_bool *fSt strStamped = str; if (!str.empty() && str[str.size()-1] == '\n') - *fStartedNewLine = true; + m_started_new_line = true; else - *fStartedNewLine = false; + m_started_new_line = false; return strStamped; } -int LogPrintStr(const std::string &str) +void BCLog::Logger::LogPrintStr(const std::string &str) { - int ret = 0; // Returns total number of characters written - static std::atomic_bool fStartedNewLine(true); - - std::string strTimestamped = LogTimestampStr(str, &fStartedNewLine); + std::string strTimestamped = LogTimestampStr(str); - if (fPrintToConsole) { + if (m_print_to_console) { // print to console - ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); + fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); fflush(stdout); } - if (fPrintToDebugLog) { - std::call_once(debugPrintInitFlag, &DebugPrintInit); - std::lock_guard<std::mutex> scoped_lock(*mutexDebugLog); + if (m_print_to_file) { + std::lock_guard<std::mutex> scoped_lock(m_file_mutex); // buffer if we haven't opened the log yet - if (fileout == nullptr) { - assert(vMsgsBeforeOpenLog); - ret = strTimestamped.length(); - vMsgsBeforeOpenLog->push_back(strTimestamped); + if (m_fileout == nullptr) { + m_msgs_before_open.push_back(strTimestamped); } else { // reopen the log file, if requested - if (fReopenDebugLog) { - fReopenDebugLog = false; - fs::path pathDebug = GetDebugLogPath(); - if (fsbridge::freopen(pathDebug,"a",fileout) != nullptr) - setbuf(fileout, nullptr); // unbuffered + if (m_reopen_file) { + m_reopen_file = false; + m_fileout = fsbridge::freopen(m_file_path, "a", m_fileout); + if (!m_fileout) { + return; + } + setbuf(m_fileout, nullptr); // unbuffered } - ret = FileWriteStr(strTimestamped, fileout); + FileWriteStr(strTimestamped, m_fileout); } } - return ret; } -void ShrinkDebugFile() +void BCLog::Logger::ShrinkDebugFile() { // Amount of debug.log to save at end when shrinking (must fit in memory) constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000; + + assert(!m_file_path.empty()); + // Scroll debug.log if it's getting too big - fs::path pathLog = GetDebugLogPath(); - FILE* file = fsbridge::fopen(pathLog, "r"); + FILE* file = fsbridge::fopen(m_file_path, "r"); // Special files (e.g. device nodes) may not have a size. size_t log_size = 0; try { - log_size = fs::file_size(pathLog); + log_size = fs::file_size(m_file_path); } catch (boost::filesystem::filesystem_error &) {} // If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE @@ -267,11 +253,15 @@ void ShrinkDebugFile() { // Restart the file with some of the end std::vector<char> vch(RECENT_DEBUG_HISTORY_SIZE, 0); - fseek(file, -((long)vch.size()), SEEK_END); + if (fseek(file, -((long)vch.size()), SEEK_END)) { + LogPrintf("Failed to shrink debug log file: fseek(...) failed\n"); + fclose(file); + return; + } int nBytes = fread(vch.data(), 1, vch.size(), file); fclose(file); - file = fsbridge::fopen(pathLog, "w"); + file = fsbridge::fopen(m_file_path, "w"); if (file) { fwrite(vch.data(), 1, nBytes, file); diff --git a/src/logging.h b/src/logging.h index 4053f75acf..6400b131c2 100644 --- a/src/logging.h +++ b/src/logging.h @@ -11,6 +11,8 @@ #include <atomic> #include <cstdint> +#include <list> +#include <mutex> #include <string> #include <vector> @@ -19,15 +21,7 @@ static const bool DEFAULT_LOGIPS = false; static const bool DEFAULT_LOGTIMESTAMPS = true; extern const char * const DEFAULT_DEBUGLOGFILE; -extern bool fPrintToConsole; -extern bool fPrintToDebugLog; - -extern bool fLogTimestamps; -extern bool fLogTimeMicros; extern bool fLogIPs; -extern std::atomic<bool> fReopenDebugLog; - -extern std::atomic<uint32_t> logCategories; struct CLogCategoryActive { @@ -61,11 +55,65 @@ namespace BCLog { LEVELDB = (1 << 20), ALL = ~(uint32_t)0, }; -} + + class Logger + { + private: + FILE* m_fileout = nullptr; + std::mutex m_file_mutex; + std::list<std::string> m_msgs_before_open; + + /** + * m_started_new_line is a state variable that will suppress printing of + * the timestamp when multiple calls are made that don't end in a + * newline. + */ + std::atomic_bool m_started_new_line{true}; + + /** Log categories bitfield. */ + std::atomic<uint32_t> m_categories{0}; + + std::string LogTimestampStr(const std::string& str); + + public: + bool m_print_to_console = false; + bool m_print_to_file = false; + + bool m_log_timestamps = DEFAULT_LOGTIMESTAMPS; + bool m_log_time_micros = DEFAULT_LOGTIMEMICROS; + + fs::path m_file_path; + std::atomic<bool> m_reopen_file{false}; + + /** Send a string to the log output */ + void LogPrintStr(const std::string &str); + + /** Returns whether logs will be written to any output */ + bool Enabled() const { return m_print_to_console || m_print_to_file; } + + bool OpenDebugLog(); + void ShrinkDebugFile(); + + uint32_t GetCategoryMask() const { return m_categories.load(); } + + void EnableCategory(LogFlags flag); + bool EnableCategory(const std::string& str); + void DisableCategory(LogFlags flag); + bool DisableCategory(const std::string& str); + + bool WillLogCategory(LogFlags category) const; + + bool DefaultShrinkDebugFile() const; + }; + +} // namespace BCLog + +extern BCLog::Logger* const g_logger; + /** Return true if log accepts specified category */ -static inline bool LogAcceptCategory(uint32_t category) +static inline bool LogAcceptCategory(BCLog::LogFlags category) { - return (logCategories.load(std::memory_order_relaxed) & category) != 0; + return g_logger->WillLogCategory(category); } /** Returns a string with the log categories. */ @@ -74,11 +122,8 @@ std::string ListLogCategories(); /** Returns a vector of the active log categories. */ std::vector<CLogCategoryActive> ListActiveLogCategories(); -/** Return true if str parses as a log category and set the flags in f */ -bool GetLogCategory(uint32_t *f, const std::string *str); - -/** Send a string to the log output */ -int LogPrintStr(const std::string &str); +/** Return true if str parses as a log category and set the flag */ +bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str); /** Get format string from VA_ARGS for error reporting */ template<typename... Args> std::string FormatStringFromLogArgs(const char *fmt, const Args&... args) { return fmt; } @@ -99,7 +144,7 @@ template<typename T, typename... Args> static inline void MarkUsed(const T& t, c #define LogPrint(category, ...) do { MarkUsed(__VA_ARGS__); } while(0) #else #define LogPrintf(...) do { \ - if (fPrintToConsole || fPrintToDebugLog) { \ + if (g_logger->Enabled()) { \ std::string _log_msg_; /* Unlikely name to avoid shadowing variables */ \ try { \ _log_msg_ = tfm::format(__VA_ARGS__); \ @@ -107,7 +152,7 @@ template<typename T, typename... Args> static inline void MarkUsed(const T& t, c /* Original format string will have newline so don't add one here */ \ _log_msg_ = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + FormatStringFromLogArgs(__VA_ARGS__); \ } \ - LogPrintStr(_log_msg_); \ + g_logger->LogPrintStr(_log_msg_); \ } \ } while(0) @@ -118,8 +163,4 @@ template<typename T, typename... Args> static inline void MarkUsed(const T& t, c } while(0) #endif -fs::path GetDebugLogPath(); -bool OpenDebugLog(); -void ShrinkDebugFile(); - #endif // BITCOIN_LOGGING_H diff --git a/src/merkleblock.h b/src/merkleblock.h index 0976e21c3a..984e33a961 100644 --- a/src/merkleblock.h +++ b/src/merkleblock.h @@ -115,6 +115,12 @@ public: * returns the merkle root, or 0 in case of failure */ uint256 ExtractMatches(std::vector<uint256> &vMatch, std::vector<unsigned int> &vnIndex); + + /** Get number of transactions the merkle proof is indicating for cross-reference with + * local blockchain knowledge. + */ + unsigned int GetNumTransactions() const { return nTransactions; }; + }; diff --git a/src/miner.cpp b/src/miner.cpp index 0660df928c..738ccad1b9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -14,7 +14,6 @@ #include <consensus/merkle.h> #include <consensus/validation.h> #include <hash.h> -#include <validation.h> #include <net.h> #include <policy/feerate.h> #include <policy/policy.h> @@ -210,7 +209,7 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost // segwit activation) bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) { - for (const CTxMemPool::txiter it : package) { + for (CTxMemPool::txiter it : package) { if (!IsFinalTx(it->GetTx(), nHeight, nLockTimeCutoff)) return false; if (!fIncludeWitness && it->GetTx().HasWitness()) @@ -242,7 +241,7 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already indexed_modified_transaction_set &mapModifiedTx) { int nDescendantsUpdated = 0; - for (const CTxMemPool::txiter it : alreadyAdded) { + for (CTxMemPool::txiter it : alreadyAdded) { CTxMemPool::setEntries descendants; mempool.CalculateDescendants(it, descendants); // Insert all descendants (not yet in block) into the modified set diff --git a/src/miner.h b/src/miner.h index 33a22ba75f..ed1b4434f9 100644 --- a/src/miner.h +++ b/src/miner.h @@ -8,6 +8,7 @@ #include <primitives/block.h> #include <txmempool.h> +#include <validation.h> #include <stdint.h> #include <memory> @@ -169,7 +170,7 @@ private: /** Add transactions based on feerate including unconfirmed ancestors * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ - void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated); + void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ @@ -183,13 +184,13 @@ private: bool TestPackageTransactions(const CTxMemPool::setEntries& package); /** Return true if given transaction from mapTx has already been evaluated, * or if the transaction's cached data in mapTx is incorrect. */ - bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx); + bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); /** Sort the package in an order that is valid to appear in a block */ void SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries); /** Add descendants of given transactions to mapModifiedTx with ancestor * state updated assuming given transactions are inBlock. Returns number * of updated descendants. */ - int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx); + int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); }; /** Modify the extranonce in a block */ diff --git a/src/net.cpp b/src/net.cpp index cd076c1ce2..34dc4aec5c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -160,7 +160,7 @@ CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices) return ret; } -int GetnScore(const CService& addr) +static int GetnScore(const CService& addr) { LOCK(cs_mapLocalHost); if (mapLocalHost.count(addr) == LOCAL_NONE) @@ -395,7 +395,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE); if (!addrConnect.IsValid()) { - LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s", addrConnect.ToString(), pszDest); + LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); return nullptr; } // It is possible that we already have a connection to the IP/port pszDest resolved to. @@ -1466,7 +1466,7 @@ void CConnman::WakeMessageHandler() #ifdef USE_UPNP static CThreadInterrupt g_upnp_interrupt; static std::thread g_upnp_thread; -void ThreadMapPort() +static void ThreadMapPort() { std::string port = strprintf("%u", GetListenPort()); const char * multicastif = nullptr; @@ -2042,7 +2042,7 @@ void CConnman::ThreadMessageHandler() // Send messages { LOCK(pnode->cs_sendProcessing); - m_msgproc->SendMessages(pnode, flagInterruptMsgProc); + m_msgproc->SendMessages(pnode); } if (flagInterruptMsgProc) @@ -2254,7 +2254,8 @@ bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<C if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; - fBound |= Bind(CService(in6addr_any, GetListenPort()), BF_NONE); + struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; + fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); } return fBound; @@ -2864,8 +2865,20 @@ bool CConnman::ForNode(NodeId id, std::function<bool(CNode* pnode)> func) return found != nullptr && NodeFullyConnected(found) && func(found); } -int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds) { - return nNow + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); +int64_t CConnman::PoissonNextSendInbound(int64_t now, int average_interval_seconds) +{ + if (m_next_send_inv_to_incoming < now) { + // If this function were called from multiple threads simultaneously + // it would possible that both update the next send variable, and return a different result to their caller. + // This is not possible in practice as only the net processing thread invokes this function. + m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval_seconds); + } + return m_next_send_inv_to_incoming; +} + +int64_t PoissonNextSend(int64_t now, int average_interval_seconds) +{ + return now + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); } CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const @@ -310,6 +310,13 @@ public: unsigned int GetReceiveFloodSize() const; void WakeMessageHandler(); + + /** Attempts to obfuscate tx time through exponentially distributed emitting. + Works assuming that a single interval is used. + Variable intervals will result in privacy decrease. + */ + int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds); + private: struct ListenSocket { SOCKET socket; @@ -434,6 +441,8 @@ private: * This takes the place of a feeler connection */ std::atomic_bool m_try_another_outbound_peer; + std::atomic<int64_t> m_next_send_inv_to_incoming{0}; + friend struct CConnmanTest; }; extern std::unique_ptr<CConnman> g_connman; @@ -466,7 +475,7 @@ class NetEventsInterface { public: virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; - virtual bool SendMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; + virtual bool SendMessages(CNode* pnode) = 0; virtual void InitializeNode(CNode* pnode) = 0; virtual void FinalizeNode(NodeId id, bool& update_connection_time) = 0; @@ -863,6 +872,6 @@ public: /** Return a timestamp in the future (in microseconds) for exponentially distributed events. */ -int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds); +int64_t PoissonNextSend(int64_t now, int average_interval_seconds); #endif // BITCOIN_NET_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ee4e9e61bc..a0136675f3 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -11,7 +11,6 @@ #include <chainparams.h> #include <consensus/validation.h> #include <hash.h> -#include <init.h> #include <validation.h> #include <merkleblock.h> #include <netmessagemaker.h> @@ -36,16 +35,34 @@ # error "Bitcoin cannot be compiled without assertions." #endif -std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block - -struct IteratorComparator -{ - template<typename I> - bool operator()(const I& a, const I& b) const - { - return &(*a) < &(*b); - } -}; +/** Expiration time for orphan transactions in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; +/** Minimum time between orphan transactions expire time checks in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; +/** Headers download timeout expressed in microseconds + * Timeout = base + per_header * (expected number of headers) */ +static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes +static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/header +/** Protect at least this many outbound peers from disconnection due to slow/ + * behind headers chain. + */ +static constexpr int32_t MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT = 4; +/** Timeout for (unprotected) outbound peers to sync to our chainwork, in seconds */ +static constexpr int64_t CHAIN_SYNC_TIMEOUT = 20 * 60; // 20 minutes +/** How frequently to check for stale tips, in seconds */ +static constexpr int64_t STALE_CHECK_INTERVAL = 10 * 60; // 10 minutes +/** How frequently to check for extra outbound peers and disconnect, in seconds */ +static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45; +/** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict, in seconds */ +static constexpr int64_t MINIMUM_CONNECT_TIME = 30; +/** SHA256("main address relay")[0:8] */ +static constexpr uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; +/// Age after which a stale block will no longer be served if requested as +/// protection against fingerprinting. Set to one month, denominated in seconds. +static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; +/// Age after which a block is considered historical for purposes of rate +/// limiting block relay. Set to one week, denominated in seconds. +static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; struct COrphanTx { // When modifying, adapt the copy of this definition in tests/DoS_tests. @@ -55,21 +72,26 @@ struct COrphanTx { }; static CCriticalSection g_cs_orphans; std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); -std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); -void EraseOrphansFor(NodeId peer); - -static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; -static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); - -static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8] -/// Age after which a stale block will no longer be served if requested as -/// protection against fingerprinting. Set to one month, denominated in seconds. -static const int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; +void EraseOrphansFor(NodeId peer); -/// Age after which a block is considered historical for purposes of rate -/// limiting block relay. Set to one week, denominated in seconds. -static const int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; +/** Increase a node's misbehavior score. */ +void Misbehaving(NodeId nodeid, int howmuch, const std::string& message=""); + +/** Average delay between local address broadcasts in seconds. */ +static constexpr unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24 * 60 * 60; +/** Average delay between peer address broadcasts in seconds. */ +static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30; +/** Average delay between trickled inventory transmissions in seconds. + * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */ +static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; +/** Maximum number of inventory items to send per transmission. + * Limits the impact of low-fee transaction floods. */ +static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL; +/** Average delay between feefilter broadcasts in seconds. */ +static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; +/** Maximum feefilter broadcast delay after significant change. */ +static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; // Internal stuff namespace { @@ -137,10 +159,24 @@ namespace { MapRelay mapRelay; /** Expiration-time ordered list of (expire time, relay map entry) pairs, protected by cs_main). */ std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration; + + std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block + + struct IteratorComparator + { + template<typename I> + bool operator()(const I& a, const I& b) const + { + return &(*a) < &(*b); + } + }; + std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); + + static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); } // namespace namespace { - struct CBlockReject { unsigned char chRejectCode; std::string strRejectReason; @@ -267,17 +303,17 @@ struct CNodeState { }; /** Map maintaining per-node state. Requires cs_main. */ -std::map<NodeId, CNodeState> mapNodeState; +static std::map<NodeId, CNodeState> mapNodeState; // Requires cs_main. -CNodeState *State(NodeId pnode) { +static CNodeState *State(NodeId pnode) { std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode); if (it == mapNodeState.end()) return nullptr; return &it->second; } -void UpdatePreferredDownload(CNode* node, CNodeState* state) +static void UpdatePreferredDownload(CNode* node, CNodeState* state) { nPreferredDownload -= state->fPreferredDownload; @@ -287,7 +323,7 @@ void UpdatePreferredDownload(CNode* node, CNodeState* state) nPreferredDownload += state->fPreferredDownload; } -void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) +static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) { ServiceFlags nLocalNodeServices = pnode->GetLocalServices(); uint64_t nonce = pnode->GetLocalNonce(); @@ -311,7 +347,7 @@ void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) // Requires cs_main. // Returns a bool indicating whether we requested this block. // Also used if a block was /not/ received and timed out or started with another peer -bool MarkBlockAsReceived(const uint256& hash) { +static bool MarkBlockAsReceived(const uint256& hash) { std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash); if (itInFlight != mapBlocksInFlight.end()) { CNodeState *state = State(itInFlight->second.first); @@ -337,7 +373,7 @@ bool MarkBlockAsReceived(const uint256& hash) { // Requires cs_main. // returns false, still setting pit, if the block was already in flight from the same peer // pit will only be valid as long as the same cs_main lock is being held -bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list<QueuedBlock>::iterator** pit = nullptr) { +static bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list<QueuedBlock>::iterator** pit = nullptr) { CNodeState *state = State(nodeid); assert(state != nullptr); @@ -371,7 +407,7 @@ bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* } /** Check whether the last unknown block a peer advertised is not yet known. */ -void ProcessBlockAvailability(NodeId nodeid) { +static void ProcessBlockAvailability(NodeId nodeid) { CNodeState *state = State(nodeid); assert(state != nullptr); @@ -387,7 +423,7 @@ void ProcessBlockAvailability(NodeId nodeid) { } /** Update tracking information about which blocks a peer is assumed to have. */ -void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { +static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { CNodeState *state = State(nodeid); assert(state != nullptr); @@ -411,7 +447,7 @@ void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { * lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by * removing the first element if necessary. */ -void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) { +static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) { AssertLockHeld(cs_main); CNodeState* nodestate = State(nodeid); if (!nodestate || !nodestate->fSupportsDesiredCmpctVersion) { @@ -444,7 +480,7 @@ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) { } } -bool TipMayBeStale(const Consensus::Params &consensusParams) +static bool TipMayBeStale(const Consensus::Params &consensusParams) { AssertLockHeld(cs_main); if (g_last_tip_update == 0) { @@ -454,13 +490,13 @@ bool TipMayBeStale(const Consensus::Params &consensusParams) } // Requires cs_main -bool CanDirectFetch(const Consensus::Params &consensusParams) +static bool CanDirectFetch(const Consensus::Params &consensusParams) { return chainActive.Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; } // Requires cs_main -bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) +static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) { if (state->pindexBestKnownBlock && pindex == state->pindexBestKnownBlock->GetAncestor(pindex->nHeight)) return true; @@ -471,7 +507,7 @@ bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) /** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has * at most count entries. */ -void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller, const Consensus::Params& consensusParams) { +static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller, const Consensus::Params& consensusParams) { if (count == 0) return; @@ -560,7 +596,7 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<con } // namespace // This function is used for testing the stale tip eviction logic, see -// DoS_tests.cpp +// denialofservice_tests.cpp void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); @@ -570,7 +606,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) // Returns true for outbound peers, excluding manual connections, feelers, and // one-shots -bool IsOutboundDisconnectionCandidate(const CNode *node) +static bool IsOutboundDisconnectionCandidate(const CNode *node) { return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); } @@ -642,7 +678,7 @@ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) { // mapOrphanTransactions // -void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) +static void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) { size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN); if (max_extra_txn <= 0) @@ -664,10 +700,10 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE // large transaction with a missing parent then we assume // it will rebroadcast it later, after the parent transaction(s) // have been mined or received. - // 100 orphans, each of which is at most 99,999 bytes big is + // 100 orphans, each of which is at most 100,000 bytes big is // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): unsigned int sz = GetTransactionWeight(*tx); - if (sz >= MAX_STANDARD_TX_WEIGHT) + if (sz > MAX_STANDARD_TX_WEIGHT) { LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); return false; @@ -809,7 +845,9 @@ static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Para (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); } -PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler) : connman(connmanIn), m_stale_tip_check_time(0) { +PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler, bool enable_bip61) + : connman(connmanIn), 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)); @@ -866,7 +904,7 @@ static uint256 most_recent_block_hash; static bool fWitnessesPresentInMostRecentCompactBlock; /** - * Maintain state about the best-seen block and fast-announce a compact block + * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. */ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { @@ -911,7 +949,7 @@ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std: } /** - * Update our best height and announce any block hashes which weren't previously + * Update our best height and announce any block hashes which weren't previously * in chainActive to our peers. */ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { @@ -947,7 +985,7 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB } /** - * Handle invalid block rejection and consequent peer banning, maintain which + * Handle invalid block rejection and consequent peer banning, maintain which * peers announce compact blocks. */ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationState& state) { @@ -1070,12 +1108,13 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma connman->ForEachNodeThen(std::move(sortfunc), std::move(pushfunc)); } -void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensusParams, const CInv& inv, CConnman* connman, const std::atomic<bool>& interruptMsgProc) +void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, const CInv& inv, CConnman* connman) { bool send = false; std::shared_ptr<const CBlock> a_recent_block; std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block; bool fWitnessesPresentInARecentCompactBlock; + const Consensus::Params& consensusParams = chainparams.GetConsensus(); { LOCK(cs_most_recent_block); a_recent_block = most_recent_block; @@ -1142,6 +1181,15 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus std::shared_ptr<const CBlock> pblock; if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { pblock = a_recent_block; + } else if (inv.type == MSG_WITNESS_BLOCK) { + // Fast-path: in this case it is possible to serve the block directly from disk, + // as the network format matches the format on disk + std::vector<uint8_t> block_data; + if (!ReadRawBlockFromDisk(block_data, pindex, chainparams.MessageStart())) { + assert(!"cannot load block from disk"); + } + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data))); + // Don't set pblock as we've sent the block } else { // Send block from disk std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>(); @@ -1149,53 +1197,55 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus assert(!"cannot load block from disk"); pblock = pblockRead; } - if (inv.type == MSG_BLOCK) - connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); - else if (inv.type == MSG_WITNESS_BLOCK) - connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); - else if (inv.type == MSG_FILTERED_BLOCK) - { - bool sendMerkleBlock = false; - CMerkleBlock merkleBlock; + if (pblock) { + if (inv.type == MSG_BLOCK) + connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); + else if (inv.type == MSG_WITNESS_BLOCK) + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); + else if (inv.type == MSG_FILTERED_BLOCK) { - LOCK(pfrom->cs_filter); - if (pfrom->pfilter) { - sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter); + bool sendMerkleBlock = false; + CMerkleBlock merkleBlock; + { + LOCK(pfrom->cs_filter); + if (pfrom->pfilter) { + sendMerkleBlock = true; + merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter); + } + } + if (sendMerkleBlock) { + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); + // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see + // This avoids hurting performance by pointlessly requiring a round-trip + // Note that there is currently no way for a node to request any single transactions we didn't send here - + // they must either disconnect and retry or request the full block. + // Thus, the protocol spec specified allows for us to provide duplicate txn here, + // however we MUST always provide at least what the remote peer needs + typedef std::pair<unsigned int, uint256> PairType; + for (PairType& pair : merkleBlock.vMatchedTxn) + connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); } + // else + // no response } - if (sendMerkleBlock) { - connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); - // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see - // This avoids hurting performance by pointlessly requiring a round-trip - // Note that there is currently no way for a node to request any single transactions we didn't send here - - // they must either disconnect and retry or request the full block. - // Thus, the protocol spec specified allows for us to provide duplicate txn here, - // however we MUST always provide at least what the remote peer needs - typedef std::pair<unsigned int, uint256> PairType; - for (PairType& pair : merkleBlock.vMatchedTxn) - connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); - } - // else - // no response - } - else if (inv.type == MSG_CMPCT_BLOCK) - { - // If a peer is asking for old blocks, we're almost guaranteed - // they won't have a useful mempool to match against a compact block, - // and we don't feel like constructing the object for them, so - // instead we respond with the full, non-compact block. - bool fPeerWantsWitness = State(pfrom->GetId())->fWantsCmpctWitness; - int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - if (CanDirectFetch(consensusParams) && pindex->nHeight >= chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) { - if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { - connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); + else if (inv.type == MSG_CMPCT_BLOCK) + { + // If a peer is asking for old blocks, we're almost guaranteed + // they won't have a useful mempool to match against a compact block, + // and we don't feel like constructing the object for them, so + // instead we respond with the full, non-compact block. + bool fPeerWantsWitness = State(pfrom->GetId())->fWantsCmpctWitness; + int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; + if (CanDirectFetch(consensusParams) && pindex->nHeight >= chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) { + if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); + } else { + CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + } } else { - CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); - connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); } - } else { - connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); } } @@ -1213,7 +1263,7 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus } } -void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParams, CConnman* connman, const std::atomic<bool>& interruptMsgProc) +void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc) { AssertLockNotHeld(cs_main); @@ -1252,9 +1302,6 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam if (!push) { vNotFound.push_back(inv); } - - // Track requests for our stuff. - GetMainSignals().Inventory(inv.hash); } } // release cs_main @@ -1262,7 +1309,7 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam const CInv &inv = *it; if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) { it++; - ProcessGetBlockData(pfrom, consensusParams, inv, connman, interruptMsgProc); + ProcessGetBlockData(pfrom, chainparams, inv, connman); } } @@ -1280,7 +1327,7 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } -uint32_t GetFetchFlags(CNode* pfrom) { +static uint32_t GetFetchFlags(CNode* pfrom) { uint32_t nFetchFlags = 0; if ((pfrom->GetLocalServices() & NODE_WITNESS) && State(pfrom->GetId())->fHaveWitness) { nFetchFlags |= MSG_WITNESS_FLAG; @@ -1525,7 +1572,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve return true; } -bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc) +bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc, bool enable_bip61) { LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->GetId()); if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) @@ -1571,6 +1618,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrint(BCLog::NET, "Unparseable reject message received\n"); } } + return true; } else if (strCommand == NetMsgType::VERSION) @@ -1578,7 +1626,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Each connection can only send one version message if (pfrom->nVersion != 0) { - connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE, std::string("Duplicate version message"))); + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE, std::string("Duplicate version message"))); + } LOCK(cs_main); Misbehaving(pfrom->GetId(), 1); return false; @@ -1607,8 +1657,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (!pfrom->fInbound && !pfrom->fFeeler && !pfrom->m_manual_connection && !HasAllDesirableServiceFlags(nServices)) { LogPrint(BCLog::NET, "peer=%d does not offer the expected services (%08x offered, %08x expected); disconnecting\n", pfrom->GetId(), nServices, GetDesirableServiceFlags(nServices)); - connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD, - strprintf("Expected to offer services %08x", GetDesirableServiceFlags(nServices)))); + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD, + strprintf("Expected to offer services %08x", GetDesirableServiceFlags(nServices)))); + } pfrom->fDisconnect = true; return false; } @@ -1628,8 +1680,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr { // disconnect from peers older than this proto version LogPrint(BCLog::NET, "peer=%d using obsolete version %i; disconnecting\n", pfrom->GetId(), nVersion); - connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, - strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION))); + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, + strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION))); + } pfrom->fDisconnect = true; return false; } @@ -1946,9 +2000,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr pfrom->AskFor(inv); } } - - // Track requests for our stuff - GetMainSignals().Inventory(inv.hash); } } @@ -1971,7 +2022,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end()); - ProcessGetData(pfrom, chainparams.GetConsensus(), connman, interruptMsgProc); + ProcessGetData(pfrom, chainparams, connman, interruptMsgProc); } @@ -2059,7 +2110,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr const CBlockIndex* pindex = LookupBlockIndex(req.blockhash); if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { - LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have", pfrom->GetId()); + LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have\n", pfrom->GetId()); return true; } @@ -2071,7 +2122,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // might maliciously send lots of getblocktxn requests to trigger // expensive disk reads, because it will require the peer to // actually receive all the data read from disk over the network. - LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep", pfrom->GetId(), MAX_BLOCKTXN_DEPTH); + LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep\n", pfrom->GetId(), MAX_BLOCKTXN_DEPTH); CInv inv; inv.type = State(pfrom->GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK; inv.hash = req.blockhash; @@ -2327,9 +2378,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); - if (state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P + if (enable_bip61 && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { // Never send AcceptToMemoryPool's internal codes over P2P connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash)); + } if (nDoS > 0) { Misbehaving(pfrom->GetId(), nDoS); } @@ -2511,7 +2563,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } // cs_main if (fProcessBLOCKTXN) - return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc); + return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc, enable_bip61); if (fRevertToHeaderProcessing) { // Headers received from HB compact block peers are permitted to be @@ -2897,13 +2949,15 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } -static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman) +static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool enable_bip61) { AssertLockHeld(cs_main); CNodeState &state = *State(pnode->GetId()); - for (const CBlockReject& reject : state.rejects) { - connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, std::string(NetMsgType::BLOCK), reject.chRejectCode, reject.strRejectReason, reject.hashBlock)); + if (enable_bip61) { + for (const CBlockReject& reject : state.rejects) { + connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, std::string(NetMsgType::BLOCK), reject.chRejectCode, reject.strRejectReason, reject.hashBlock)); + } } state.rejects.clear(); @@ -2941,7 +2995,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter bool fMoreWork = false; if (!pfrom->vRecvGetData.empty()) - ProcessGetData(pfrom, chainparams.GetConsensus(), connman, interruptMsgProc); + ProcessGetData(pfrom, chainparams, connman, interruptMsgProc); if (pfrom->fDisconnect) return false; @@ -3002,7 +3056,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter bool fRet = false; try { - fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime, chainparams, connman, interruptMsgProc); + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime, chainparams, connman, interruptMsgProc, m_enable_bip61); if (interruptMsgProc) return false; if (!pfrom->vRecvGetData.empty()) @@ -3010,7 +3064,9 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter } catch (const std::ios_base::failure& e) { - connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_MALFORMED, std::string("error parsing message"))); + if (m_enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_MALFORMED, std::string("error parsing message"))); + } if (strstr(e.what(), "end of data")) { // Allow exceptions from under-length message on vRecv @@ -3042,7 +3098,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter } LOCK(cs_main); - SendRejectsAndCheckIfBanned(pfrom, connman); + SendRejectsAndCheckIfBanned(pfrom, connman, m_enable_bip61); return fMoreWork; } @@ -3177,6 +3233,7 @@ void PeerLogicValidation::CheckForStaleTipAndEvictPeers(const Consensus::Params } } +namespace { class CompareInvMempoolOrder { CTxMemPool *mp; @@ -3193,8 +3250,9 @@ public: return mp->CompareDepthAndScore(*b, *a); } }; +} -bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptMsgProc) +bool PeerLogicValidation::SendMessages(CNode* pto) { const Consensus::Params& consensusParams = Params().GetConsensus(); { @@ -3238,7 +3296,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptM if (!lockMain) return true; - if (SendRejectsAndCheckIfBanned(pto, connman)) + if (SendRejectsAndCheckIfBanned(pto, connman, m_enable_bip61)) return true; CNodeState &state = *State(pto->GetId()); @@ -3472,8 +3530,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptM bool fSendTrickle = pto->fWhitelisted; if (pto->nNextInvSend < nNow) { fSendTrickle = true; - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> !pto->fInbound); + if (pto->fInbound) { + pto->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); + } else { + // Use half the delay for outbound peers, as there is less privacy concern for them. + pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + } } // Time to send but the peer has requested we not relay transactions. diff --git a/src/net_processing.h b/src/net_processing.h index 195d0d2033..4dab0ada2b 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -12,35 +12,17 @@ /** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; -/** Expiration time for orphan transactions in seconds */ -static const int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; -/** Minimum time between orphan transactions expire time checks in seconds */ -static const int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; /** Default number of orphan+recently-replaced txn to keep around for block reconstruction */ static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100; -/** Headers download timeout expressed in microseconds - * Timeout = base + per_header * (expected number of headers) */ -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/header -/** Protect at least this many outbound peers from disconnection due to slow/ - * behind headers chain. - */ -static constexpr int32_t MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT = 4; -/** Timeout for (unprotected) outbound peers to sync to our chainwork, in seconds */ -static constexpr int64_t CHAIN_SYNC_TIMEOUT = 20 * 60; // 20 minutes -/** How frequently to check for stale tips, in seconds */ -static constexpr int64_t STALE_CHECK_INTERVAL = 10 * 60; // 10 minutes -/** How frequently to check for extra outbound peers and disconnect, in seconds */ -static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45; -/** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict, in seconds */ -static constexpr int64_t MINIMUM_CONNECT_TIME = 30; +/** Default for BIP61 (sending reject messages) */ +static constexpr bool DEFAULT_ENABLE_BIP61 = true; class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: CConnman* const connman; public: - explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler); + explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler, bool enable_bip61); /** * Overridden from CValidationInterface. @@ -63,16 +45,20 @@ public: void InitializeNode(CNode* pnode) override; /** Handle removal of a peer by updating various state and removing it from mapNodeState */ void FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTime) override; - /** Process protocol messages received from a given node */ + /** + * Process protocol messages received from a given node + * + * @param[in] pfrom The node which we have received messages from. + * @param[in] interrupt Interrupt condition for processing threads + */ bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; /** * Send queued protocol messages to be sent to a give node. * * @param[in] pto The node which we are sending messages to. - * @param[in] interrupt Interrupt condition for processing threads * @return True if there is more work to be done */ - bool SendMessages(CNode* pto, std::atomic<bool>& interrupt) override; + bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ void ConsiderEviction(CNode *pto, int64_t time_in_seconds); @@ -83,6 +69,9 @@ public: private: int64_t m_stale_tip_check_time; //! Next time to check for stale tip + + /** Enable BIP61 (sending reject messages) */ + const bool m_enable_bip61; }; struct CNodeStateStats { @@ -94,7 +83,5 @@ struct CNodeStateStats { /** Get statistics from node state */ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats); -/** Increase a node's misbehavior score. */ -void Misbehaving(NodeId nodeid, int howmuch, const std::string& message=""); #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 18d5948f85..5ccbabd03d 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -246,7 +246,7 @@ enum Network CNetAddr::GetNetwork() const return NET_IPV4; if (IsTor()) - return NET_TOR; + return NET_ONION; return NET_IPV6; } @@ -355,7 +355,7 @@ std::vector<unsigned char> CNetAddr::GetGroup() const } else if (IsTor()) { - nClass = NET_TOR; + nClass = NET_ONION; nStartByte = 6; nBits = 4; } @@ -433,11 +433,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_IPV4: return REACH_IPV4; case NET_IPV6: return fTunnel ? REACH_IPV6_WEAK : REACH_IPV6_STRONG; // only prefer giving our IPv6 address if it's not tunnelled } - case NET_TOR: + case NET_ONION: switch(ourNet) { default: return REACH_DEFAULT; case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well - case NET_TOR: return REACH_PRIVATE; + case NET_ONION: return REACH_PRIVATE; } case NET_TEREDO: switch(ourNet) { @@ -454,7 +454,7 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_TEREDO: return REACH_TEREDO; case NET_IPV6: return REACH_IPV6_WEAK; case NET_IPV4: return REACH_IPV4; - case NET_TOR: return REACH_PRIVATE; // either from Tor, or don't care about our address + case NET_ONION: return REACH_PRIVATE; // either from Tor, or don't care about our address } } } diff --git a/src/netaddress.h b/src/netaddress.h index f8f2ab99ff..966bef8cdf 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -22,7 +22,7 @@ enum Network NET_UNROUTABLE = 0, NET_IPV4, NET_IPV6, - NET_TOR, + NET_ONION, NET_INTERNAL, NET_MAX, diff --git a/src/netbase.cpp b/src/netbase.cpp index 57835b5427..4ce63cb0ec 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -41,7 +41,11 @@ enum Network ParseNetwork(std::string net) { boost::to_lower(net); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; - if (net == "tor" || net == "onion") return NET_TOR; + if (net == "onion") return NET_ONION; + if (net == "tor") { + LogPrintf("Warning: net name 'tor' is deprecated and will be removed in the future. You should use 'onion' instead.\n"); + return NET_ONION; + } return NET_UNROUTABLE; } @@ -50,7 +54,7 @@ std::string GetNetworkName(enum Network net) { { case NET_IPV4: return "ipv4"; case NET_IPV6: return "ipv6"; - case NET_TOR: return "onion"; + case NET_ONION: return "onion"; default: return ""; } } @@ -289,7 +293,7 @@ struct ProxyCredentials }; /** Convert SOCKS5 reply to an error message */ -std::string Socks5ErrorString(uint8_t err) +static std::string Socks5ErrorString(uint8_t err) { switch(err) { case SOCKS5Reply::GENFAILURE: diff --git a/src/outputtype.cpp b/src/outputtype.cpp new file mode 100644 index 0000000000..3ff28bf9c2 --- /dev/null +++ b/src/outputtype.cpp @@ -0,0 +1,101 @@ +// 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 <outputtype.h> + +#include <keystore.h> +#include <pubkey.h> +#include <script/script.h> +#include <script/standard.h> + +#include <assert.h> +#include <string> + +static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; +static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; +static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; + +bool ParseOutputType(const std::string& type, OutputType& output_type) +{ + if (type == OUTPUT_TYPE_STRING_LEGACY) { + output_type = OutputType::LEGACY; + return true; + } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { + output_type = OutputType::P2SH_SEGWIT; + return true; + } else if (type == OUTPUT_TYPE_STRING_BECH32) { + output_type = OutputType::BECH32; + return true; + } + return false; +} + +const std::string& FormatOutputType(OutputType type) +{ + switch (type) { + case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; + case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; + case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; + default: assert(false); + } +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) +{ + switch (type) { + case OutputType::LEGACY: return key.GetID(); + case OutputType::P2SH_SEGWIT: + case OutputType::BECH32: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + if (type == OutputType::P2SH_SEGWIT) { + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) +{ + CKeyID keyid = key.GetID(); + if (key.IsCompressed()) { + CTxDestination segwit = WitnessV0KeyHash(keyid); + CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); + return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)}; + } else { + return std::vector<CTxDestination>{std::move(keyid)}; + } +} + +CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType type) +{ + // Add script to keystore + keystore.AddCScript(script); + // Note that scripts over 520 bytes are not yet supported. + switch (type) { + case OutputType::LEGACY: + return CScriptID(script); + case OutputType::P2SH_SEGWIT: + case OutputType::BECH32: { + CTxDestination witdest = WitnessV0ScriptHash(script); + CScript witprog = GetScriptForDestination(witdest); + // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) + if (!IsSolvable(keystore, witprog)) return CScriptID(script); + // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. + keystore.AddCScript(witprog); + if (type == OutputType::BECH32) { + return witdest; + } else { + return CScriptID(witprog); + } + } + default: assert(false); + } +} + diff --git a/src/outputtype.h b/src/outputtype.h new file mode 100644 index 0000000000..21623e3b49 --- /dev/null +++ b/src/outputtype.h @@ -0,0 +1,49 @@ +// 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_OUTPUTTYPE_H +#define BITCOIN_OUTPUTTYPE_H + +#include <keystore.h> +#include <script/standard.h> + +#include <string> +#include <vector> + +enum class OutputType { + LEGACY, + P2SH_SEGWIT, + BECH32, + + /** + * Special output type for change outputs only. Automatically choose type + * based on address type setting and the types other of non-change outputs + * (see -changetype option documentation and implementation in + * CWallet::TransactionChangeType for details). + */ + CHANGE_AUTO, +}; + +bool ParseOutputType(const std::string& str, OutputType& output_type); +const std::string& FormatOutputType(OutputType type); + +/** + * Get a destination of the requested type (if possible) to the specified key. + * The caller must make sure LearnRelatedScripts has been called beforehand. + */ +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); + +/** Get all destinations (potentially) supported by the wallet for the given key. */ +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); + +/** + * Get a destination of the requested type (if possible) to the specified script. + * This function will automatically add the script (and any other + * necessary scripts) to the keystore. + */ +CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType); + +#endif // BITCOIN_OUTPUTTYPE_H + diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 5963bf371a..3a592e40d3 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -54,7 +54,7 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn)); } -bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled) +bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) { std::vector<std::vector<unsigned char> > vSolutions; if (!Solver(scriptPubKey, whichType, vSolutions)) @@ -73,13 +73,10 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool w (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes)) return false; - else if (!witnessEnabled && (whichType == TX_WITNESS_V0_KEYHASH || whichType == TX_WITNESS_V0_SCRIPTHASH)) - return false; - return whichType != TX_NONSTANDARD && whichType != TX_WITNESS_UNKNOWN; } -bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled) +bool IsStandardTx(const CTransaction& tx, std::string& reason) { if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { reason = "version"; @@ -91,7 +88,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes // computing signature hashes is O(ninputs*txsize). Limiting transactions // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. unsigned int sz = GetTransactionWeight(tx); - if (sz >= MAX_STANDARD_TX_WEIGHT) { + if (sz > MAX_STANDARD_TX_WEIGHT) { reason = "tx-size"; return false; } @@ -118,7 +115,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes unsigned int nDataOut = 0; txnouttype whichType; for (const CTxOut& txout : tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) { + if (!::IsStandard(txout.scriptPubKey, whichType)) { reason = "scriptpubkey"; return false; } diff --git a/src/policy/policy.h b/src/policy/policy.h index e4eda4b635..035627bd60 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -22,6 +22,8 @@ static const unsigned int DEFAULT_BLOCK_MAX_WEIGHT = MAX_BLOCK_WEIGHT - 4000; static const unsigned int DEFAULT_BLOCK_MIN_TX_FEE = 1000; /** The maximum weight for transactions we're willing to relay/mine */ static const unsigned int MAX_STANDARD_TX_WEIGHT = 400000; +/** The minimum non-witness size for transactions we're willing to relay/mine (1 segwit input + 1 P2WPKH output = 82 bytes) */ +static const unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE = 82; /** Maximum number of signature check operations in an IsStandard() P2SH script */ static const unsigned int MAX_P2SH_SIGOPS = 15; /** The maximum number of sigops we're willing to relay/mine in a single tx */ @@ -63,7 +65,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VE SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | - SCRIPT_VERIFY_WITNESS_PUBKEYTYPE; + SCRIPT_VERIFY_WITNESS_PUBKEYTYPE | + SCRIPT_VERIFY_CONST_SCRIPTCODE; /** For convenience, standard but not mandatory verify flags. */ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; @@ -76,12 +79,12 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); -bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled = false); +bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); /** * Check for standard transaction types * @return True if all outputs (scriptPubKeys) use only standard transaction forms */ -bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled = false); +bool IsStandardTx(const CTransaction& tx, std::string& reason); /** * Check for standard transaction types * @param[in] mapInputs Map of previous transactions that have outputs we're spending diff --git a/src/policy/rbf.h b/src/policy/rbf.h index b10532addf..c0a25f75b5 100644 --- a/src/policy/rbf.h +++ b/src/policy/rbf.h @@ -23,6 +23,6 @@ bool SignalsOptInRBF(const CTransaction &tx); // according to BIP 125 // This involves checking sequence numbers of the transaction, as well // as the sequence numbers of all in-mempool ancestors. -RBFTransactionState IsRBFOptIn(const CTransaction &tx, CTxMemPool &pool); +RBFTransactionState IsRBFOptIn(const CTransaction &tx, CTxMemPool &pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs); #endif // BITCOIN_POLICY_RBF_H diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 6f463cabf5..230f762a1b 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -67,18 +67,18 @@ uint256 CTransaction::ComputeHash() const return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS); } -uint256 CTransaction::GetWitnessHash() const +uint256 CTransaction::ComputeWitnessHash() const { if (!HasWitness()) { - return GetHash(); + return hash; } return SerializeHash(*this, SER_GETHASH, 0); } /* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */ -CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nLockTime(0), hash() {} -CTransaction::CTransaction(const CMutableTransaction &tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash(ComputeHash()) {} -CTransaction::CTransaction(CMutableTransaction &&tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash(ComputeHash()) {} +CTransaction::CTransaction() : vin(), vout(), nVersion(CTransaction::CURRENT_VERSION), nLockTime(0), hash{}, m_witness_hash{} {} +CTransaction::CTransaction(const CMutableTransaction& tx) : vin(tx.vin), vout(tx.vout), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {} +CTransaction::CTransaction(CMutableTransaction&& tx) : vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nLockTime(tx.nLockTime), hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {} CAmount CTransaction::GetValueOut() const { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index cd348fdbe4..360615ec56 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -286,8 +286,10 @@ public: private: /** Memory only. */ const uint256 hash; + const uint256 m_witness_hash; uint256 ComputeHash() const; + uint256 ComputeWitnessHash() const; public: /** Construct a CTransaction that qualifies as IsNull() */ @@ -311,12 +313,8 @@ public: return vin.empty() && vout.empty(); } - const uint256& GetHash() const { - return hash; - } - - // Compute a hash that includes both transaction and witness data - uint256 GetWitnessHash() const; + const uint256& GetHash() const { return hash; } + const uint256& GetWitnessHash() const { return m_witness_hash; }; // Return sum of txouts. CAmount GetValueOut() const; @@ -367,7 +365,7 @@ struct CMutableTransaction uint32_t nLockTime; CMutableTransaction(); - CMutableTransaction(const CTransaction& tx); + explicit CMutableTransaction(const CTransaction& tx); template <typename Stream> inline void Serialize(Stream& s) const { @@ -390,11 +388,6 @@ struct CMutableTransaction */ uint256 GetHash() const; - friend bool operator==(const CMutableTransaction& a, const CMutableTransaction& b) - { - return a.GetHash() == b.GetHash(); - } - bool HasWitness() const { for (size_t i = 0; i < vin.size(); i++) { diff --git a/src/pubkey.h b/src/pubkey.h index 9c6c6b085e..0985273f34 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -33,10 +33,10 @@ public: /** * secp256k1: */ - static const unsigned int PUBLIC_KEY_SIZE = 65; - static const unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; - static const unsigned int SIGNATURE_SIZE = 72; - static const unsigned int COMPACT_SIGNATURE_SIZE = 65; + static constexpr unsigned int PUBLIC_KEY_SIZE = 65; + static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; + static constexpr unsigned int SIGNATURE_SIZE = 72; + static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65; /** * see www.keylength.com * script supports up to 75 for single byte push @@ -107,6 +107,7 @@ public: //! Simple read-only vector-like interface to the pubkey data. unsigned int size() const { return GetLen(vch[0]); } + const unsigned char* data() const { return vch; } const unsigned char* begin() const { return vch; } const unsigned char* end() const { return vch + size(); } const unsigned char& operator[](unsigned int pos) const { return vch[pos]; } diff --git a/src/qt/README.md b/src/qt/README.md index 7c23ccadcc..3ec538b4f4 100644 --- a/src/qt/README.md +++ b/src/qt/README.md @@ -1,10 +1,10 @@ This directory contains the BitcoinQT graphical user interface (GUI). It uses the cross-platform framework [Qt](https://www1.qt.io/developers/). -The current precise version for Qt 5 is specified in [qt.mk](/depends/packages/qt.mk). Qt 4 is also supported (see [#8263](https://github.com/bitcoin/bitcoin/issues/8263)). +The current precise version for Qt 5 is specified in [qt.mk](/depends/packages/qt.mk). ## Compile and run -See build instructions ([OSX](/doc/build-osx.md), [Windows](/doc/build-windows.md), [Unix](/doc/build-unix.md), etc). +See build instructions ([macOS](/doc/build-osx.md), [Windows](/doc/build-windows.md), [Unix](/doc/build-unix.md), etc). To run: @@ -16,7 +16,7 @@ To run: ### forms -Contains [Designer UI](http://doc.qt.io/qt-5.9/designer-using-a-ui-file.html) files. They are created with [Qt Creator](#use-qt-Creator-as IDE), but can be edited using any text editor. +Contains [Designer UI](https://doc.qt.io/qt-5.9/designer-using-a-ui-file.html) files. They are created with [Qt Creator](#using-qt-creator-as-ide), but can be edited using any text editor. ### locale @@ -36,7 +36,7 @@ Represents the main window of the Bitcoin UI. ### \*model.(h/cpp) -The model. When it has a corresponding controller, it generally inherits from [QAbstractTableModel](http://doc.qt.io/qt-5/qabstracttablemodel.html). Models that are used by controllers as helpers inherit from other Qt classes like [QValidator](http://doc.qt.io/qt-5/qvalidator.html). +The model. When it has a corresponding controller, it generally inherits from [QAbstractTableModel](https://doc.qt.io/qt-5/qabstracttablemodel.html). Models that are used by controllers as helpers inherit from other Qt classes like [QValidator](https://doc.qt.io/qt-5/qvalidator.html). ClientModel is used by the main application `bitcoingui` and several models like `peertablemodel`. @@ -46,7 +46,7 @@ A controller. `:NAMEpage.cpp` generally includes `:NAMEmodel.h` and `forms/:NAME ### \*dialog.(h/cpp) -Various dialogs, e.g. to open a URL. Inherit from [QDialog](http://doc.qt.io/qt-4.8/qdialog.html). +Various dialogs, e.g. to open a URL. Inherit from [QDialog](https://doc.qt.io/qt-5/qdialog.html). ### paymentserver.(h/cpp) @@ -65,7 +65,7 @@ Represents the view to a single wallet. * `guiconstants.h`: UI colors, app name, etc * `guiutil.h`: several helper functions * `macdockiconhandler.(h/cpp)` -* `macdockiconhandler.(h/cpp)`: display notifications in OSX +* `macdockiconhandler.(h/cpp)`: display notifications in macOS ## Contribute @@ -81,9 +81,9 @@ the UI layout. Download and install the community edition of [Qt Creator](https://www.qt.io/download/). Uncheck everything except Qt Creator during the installation process. -Instructions for OSX: +Instructions for macOS: -1. Make sure you installed everything through Homebrew mentioned in the [OSX build instructions](/doc/build-osx.md) +1. Make sure you installed everything through Homebrew mentioned in the [macOS build instructions](/doc/build-osx.md) 2. Use `./configure` with the `--enable-debug` flag 3. In Qt Creator do "New Project" -> Import Project -> Import Existing Project 4. Enter "bitcoin-qt" as project name, enter src/qt as location diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index f2ddbf259b..d51069d922 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -160,13 +160,8 @@ void AddressBookPage::setModel(AddressTableModel *_model) ui->tableView->sortByColumn(0, Qt::AscendingOrder); // Set column widths -#if QT_VERSION < 0x050000 - ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Label, QHeaderView::Stretch); - ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#else ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#endif connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged())); diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 8877d07330..ba420c5e15 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); + explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent = 0); ~AddressBookPage(); void setModel(AddressTableModel *model); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 1e3acd75c0..25b615e6f8 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -159,7 +159,7 @@ public: }; AddressTableModel::AddressTableModel(WalletModel *parent) : - QAbstractTableModel(parent),walletModel(parent),priv(0) + QAbstractTableModel(parent), walletModel(parent) { columns << tr("Label") << tr("Address"); priv = new AddressTablePriv(this); @@ -266,7 +266,8 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, } // Check for duplicate addresses to prevent accidental deletion of addresses, if you try // to paste an existing address over another address (with a different label) - if (walletModel->wallet().getAddress(newAddress)) + if (walletModel->wallet().getAddress( + newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr)) { editStatus = DUPLICATE_ADDRESS; return false; @@ -351,7 +352,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con } // Check for duplicate addresses { - if(walletModel->wallet().getAddress(DecodeDestination(strAddress))) + if (walletModel->wallet().getAddress( + DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr)) { editStatus = DUPLICATE_ADDRESS; return QString(); @@ -405,21 +407,31 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent return true; } -/* Look up label for address in address book, if not found return empty string. - */ QString AddressTableModel::labelForAddress(const QString &address) const { - { - CTxDestination destination = DecodeDestination(address.toStdString()); - std::string name; - if (walletModel->wallet().getAddress(destination, &name)) - { - return QString::fromStdString(name); - } + std::string name; + if (getAddressData(address, &name, /* purpose= */ nullptr)) { + return QString::fromStdString(name); + } + return QString(); +} + +QString AddressTableModel::purposeForAddress(const QString &address) const +{ + std::string purpose; + if (getAddressData(address, /* name= */ nullptr, &purpose)) { + return QString::fromStdString(purpose); } return QString(); } +bool AddressTableModel::getAddressData(const QString &address, + std::string* name, + std::string* purpose) const { + CTxDestination destination = DecodeDestination(address.toStdString()); + return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose); +} + int AddressTableModel::lookupAddress(const QString &address) const { QModelIndexList lst = match(index(0, Address, QModelIndex()), diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index d7aeda9d8e..6e1b53b049 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -67,10 +67,12 @@ public: */ QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type); - /* Look up label for address in address book, if not found return empty string. - */ + /** Look up label for address in address book, if not found return empty string. */ QString labelForAddress(const QString &address) const; + /** Look up purpose for address in address book, if not found return empty string. */ + QString purposeForAddress(const QString &address) const; + /* Look up row index of an address in the model. Return -1 if not found. */ @@ -81,10 +83,13 @@ public: OutputType GetDefaultAddressType() const; private: - WalletModel *walletModel; - AddressTablePriv *priv; + WalletModel* const walletModel; + AddressTablePriv *priv = nullptr; QStringList columns; - EditStatus editStatus; + EditStatus editStatus = OK; + + /** Look up address book data given an address string. */ + bool getAddressData(const QString &address, std::string* name, std::string* purpose) const; /** Notify listeners that data changed. */ void emitDataChanged(int index); diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index 26cb03c2c7..aa0d4a31d3 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -52,9 +52,7 @@ public: node.getBanned(banMap); cachedBanlist.clear(); -#if QT_VERSION >= 0x040700 cachedBanlist.reserve(banMap.size()); -#endif for (const auto& entry : banMap) { CCombinedBan banEntry; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 57fe4552a1..3454d3421e 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -26,7 +26,6 @@ #include <qt/walletmodel.h> #endif -#include <init.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <rpc/server.h> @@ -55,13 +54,6 @@ #if defined(QT_STATICPLUGIN) #include <QtPlugin> -#if QT_VERSION < 0x050000 -Q_IMPORT_PLUGIN(qcncodecs) -Q_IMPORT_PLUGIN(qjpcodecs) -Q_IMPORT_PLUGIN(qtwcodecs) -Q_IMPORT_PLUGIN(qkrcodecs) -Q_IMPORT_PLUGIN(qtaccessiblewidgets) -#else #if QT_VERSION < 0x050400 Q_IMPORT_PLUGIN(AccessibleFactory) #endif @@ -73,11 +65,6 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif -#endif - -#if QT_VERSION < 0x050000 -#include <QTextCodec> -#endif // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool*) @@ -151,16 +138,6 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans } /* qDebug() message handler --> debug.log */ -#if QT_VERSION < 0x050000 -void DebugMessageHandler(QtMsgType type, const char *msg) -{ - if (type == QtDebugMsg) { - LogPrint(BCLog::QT, "GUI: %s\n", msg); - } else { - LogPrintf("GUI: %s\n", msg); - } -} -#else void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg) { Q_UNUSED(context); @@ -170,7 +147,6 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons LogPrintf("GUI: %s\n", msg.toStdString()); } } -#endif /** Class encapsulating Bitcoin Core startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. @@ -229,11 +205,16 @@ public: /// 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(); @@ -251,6 +232,7 @@ private: #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; @@ -313,10 +295,14 @@ BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char * paymentServer(0), m_wallet_models(), #endif - returnValue(0) + returnValue(0), + platformStyle(0) { setQuitOnLastWindowClosed(false); +} +void BitcoinApplication::setupPlatformStyle() +{ // UI per-platform customization // This must be done inside the BitcoinApplication constructor, or after it, because // PlatformStyle::instantiate requires a QApplication @@ -447,6 +433,33 @@ 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, SIGNAL(coinsSent(WalletModel*, SendCoinsRecipient, QByteArray)), + paymentServer, SLOT(fetchPaymentACK(WalletModel*, const SendCoinsRecipient&, QByteArray))); + connect(walletModel, SIGNAL(unload()), this, SLOT(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; @@ -465,21 +478,15 @@ void BitcoinApplication::initializeResult(bool success) window->setClientModel(clientModel); #ifdef ENABLE_WALLET - bool fFirstWallet = true; - auto wallets = m_node.getWallets(); - for (auto& wallet : wallets) { - WalletModel * const walletModel = new WalletModel(std::move(wallet), m_node, platformStyle, optionsModel); - - window->addWallet(walletModel); - if (fFirstWallet) { - window->setCurrentWallet(walletModel->getWalletName()); - fFirstWallet = false; - } - - connect(walletModel, SIGNAL(coinsSent(WalletModel*,SendCoinsRecipient,QByteArray)), - paymentServer, SLOT(fetchPaymentACK(WalletModel*,const SendCoinsRecipient&,QByteArray))); - - m_wallet_models.push_back(walletModel); + 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)); } #endif @@ -531,6 +538,20 @@ WId BitcoinApplication::getMainWinId() const return window->winId(); } +static void SetupUIArgs() +{ +#ifdef ENABLE_WALLET + 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); + gArgs.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", false, OptionsCategory::GUI); + gArgs.AddArg("-min", "Start minimized", false, OptionsCategory::GUI); + gArgs.AddArg("-resetguisettings", "Reset all settings changed in the GUI", false, OptionsCategory::GUI); + gArgs.AddArg("-rootcertificates=<file>", "Set SSL root certificates for payment request (default: -system-)", false, OptionsCategory::GUI); + gArgs.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), false, OptionsCategory::GUI); + gArgs.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM), true, OptionsCategory::GUI); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -538,19 +559,9 @@ int main(int argc, char *argv[]) std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(); - /// 1. Parse command-line options. These take precedence over anything else. - // Command-line options take precedence: - node->parseParameters(argc, argv); - // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory - /// 2. Basic Qt initialization (not dependent on parameters or configuration) -#if QT_VERSION < 0x050000 - // Internal string conversion is all UTF-8 - QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); - QTextCodec::setCodecForCStrings(QTextCodec::codecForTr()); -#endif - + /// 1. Basic Qt initialization (not dependent on parameters or configuration) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); @@ -579,6 +590,23 @@ 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(void)> >("std::function<void(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: + node->setupServerArgs(); + SetupUIArgs(); + std::string error; + if (!node->parseParameters(argc, argv, error)) { + QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QObject::tr("Error parsing command line arguments: %1.").arg(QString::fromStdString(error))); + return EXIT_FAILURE; + } + + // Now that the QApplication is setup and we have parsed our parameters, we can set the platform style + app.setupPlatformStyle(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are loaded, @@ -615,11 +643,9 @@ int main(int argc, char *argv[]) QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } - try { - node->readConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); - } catch (const std::exception& e) { + if (!node->readConfigFiles(error)) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), - QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e.what())); + QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } @@ -666,17 +692,12 @@ int main(int argc, char *argv[]) /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); -#if QT_VERSION < 0x050000 - // Install qDebug() message handler to route to debug.log - qInstallMsgHandler(DebugMessageHandler); -#else #if defined(Q_OS_WIN) // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) qApp->installNativeEventFilter(new WinShutdownMonitor()); #endif // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); -#endif // Allow parameter interaction before we create the options model app.parameterSetup(); // Load GUI settings from QSettings @@ -697,7 +718,7 @@ int main(int argc, char *argv[]) // so the GUI thread won't be held up. if (node->baseInitialize()) { app.requestInitialize(); -#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 +#if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif app.exec(); diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index 451d391237..fddc2a5685 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -53,6 +53,7 @@ <file alias="hd_enabled">res/icons/hd_enabled.png</file> <file alias="hd_disabled">res/icons/hd_disabled.png</file> <file alias="network_disabled">res/icons/network_disabled.png</file> + <file alias="proxy">res/icons/proxy.png</file> </qresource> <qresource prefix="/movies"> <file alias="spinner-000">res/movies/spinner-000.png</file> diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index e8307ff125..68a9dc4c27 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -197,7 +197,7 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) : amount = new AmountSpinBox(this); amount->setLocale(QLocale::c()); amount->installEventFilter(this); - amount->setMaximumWidth(170); + amount->setMaximumWidth(240); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(amount); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index aed5374a7d..2438361a58 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -29,7 +29,6 @@ #endif #include <chainparams.h> -#include <init.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <ui_interface.h> @@ -55,14 +54,8 @@ #include <QStyle> #include <QTimer> #include <QToolBar> -#include <QVBoxLayout> - -#if QT_VERSION < 0x050000 -#include <QTextDocument> -#include <QUrl> -#else #include <QUrlQuery> -#endif +#include <QVBoxLayout> const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) @@ -76,49 +69,7 @@ const std::string BitcoinGUI::DEFAULT_UIPLATFORM = BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), - enableWallet(false), m_node(node), - clientModel(0), - walletFrame(0), - unitDisplayControl(0), - labelWalletEncryptionIcon(0), - labelWalletHDStatusIcon(0), - connectionsControl(0), - labelBlocksIcon(0), - progressBarLabel(0), - progressBar(0), - progressDialog(0), - appMenuBar(0), - appToolBar(0), - overviewAction(0), - historyAction(0), - quitAction(0), - sendCoinsAction(0), - sendCoinsMenuAction(0), - usedSendingAddressesAction(0), - usedReceivingAddressesAction(0), - signMessageAction(0), - verifyMessageAction(0), - aboutAction(0), - receiveCoinsAction(0), - receiveCoinsMenuAction(0), - optionsAction(0), - toggleHideAction(0), - encryptWalletAction(0), - backupWalletAction(0), - changePassphraseAction(0), - aboutQtAction(0), - openRPCConsoleAction(0), - openAction(0), - showHelpMessageAction(0), - trayIcon(0), - trayIconMenu(0), - notificator(0), - rpcConsole(0), - helpMessageDialog(0), - modalOverlay(0), - prevBlocks(0), - spinnerFrame(0), platformStyle(_platformStyle) { QSettings settings; @@ -146,12 +97,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty #endif setWindowTitle(windowTitle); -#if defined(Q_OS_MAC) && QT_VERSION < 0x050000 - // This property is not implemented in Qt 5. Setting it has no effect. - // A replacement API (QtMacUnifiedToolBar) is available in QtMacExtras. - setUnifiedTitleAndToolBarOnMac(true); -#endif - rpcConsole = new RPCConsole(node, _platformStyle, 0); helpMessageDialog = new HelpMessageDialog(node, this, false); #ifdef ENABLE_WALLET @@ -201,6 +146,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelWalletEncryptionIcon = new QLabel(); labelWalletHDStatusIcon = new QLabel(); + labelProxyIcon = new QLabel(); connectionsControl = new GUIUtil::ClickableLabel(); labelBlocksIcon = new GUIUtil::ClickableLabel(); if(enableWallet) @@ -211,6 +157,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty frameBlocksLayout->addWidget(labelWalletEncryptionIcon); frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } + frameBlocksLayout->addWidget(labelProxyIcon); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(connectionsControl); frameBlocksLayout->addStretch(); @@ -226,7 +173,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty // Override style sheet for progress bar for styles that have a segmented progress bar, // as they make the text unreadable (workaround for issue #1071) - // See https://qt-project.org/doc/qt-4.8/gallery.html + // See https://doc.qt.io/qt-5/gallery.html QString curStyle = QApplication::style()->metaObject()->className(); if(curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") { @@ -473,7 +420,17 @@ void BitcoinGUI::createToolBars() toolbar->addWidget(spacer); m_wallet_selector = new QComboBox(); - connect(m_wallet_selector, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(setCurrentWallet(const QString&))); + connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentWalletBySelectorIndex(int))); + + m_wallet_selector_label = new QLabel(); + m_wallet_selector_label->setText(tr("Wallet:") + " "); + m_wallet_selector_label->setBuddy(m_wallet_selector); + + m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label); + m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector); + + m_wallet_selector_label_action->setVisible(false); + m_wallet_selector_action->setVisible(false); #endif } } @@ -503,6 +460,9 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) connect(_clientModel, SIGNAL(showProgress(QString,int)), this, SLOT(showProgress(QString,int))); rpcConsole->setClientModel(_clientModel); + + updateProxyIcon(); + #ifdef ENABLE_WALLET if(walletFrame) { @@ -546,19 +506,33 @@ bool BitcoinGUI::addWallet(WalletModel *walletModel) if(!walletFrame) return false; const QString name = walletModel->getWalletName(); + QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name; setWalletActionsEnabled(true); - m_wallet_selector->addItem(name); + m_wallet_selector->addItem(display_name, name); if (m_wallet_selector->count() == 2) { - m_wallet_selector_label = new QLabel(); - m_wallet_selector_label->setText(tr("Wallet:") + " "); - m_wallet_selector_label->setBuddy(m_wallet_selector); - appToolBar->addWidget(m_wallet_selector_label); - appToolBar->addWidget(m_wallet_selector); + 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) +{ + if (!walletFrame) return false; + QString name = walletModel->getWalletName(); + int index = m_wallet_selector->findData(name); + m_wallet_selector->removeItem(index); + if (m_wallet_selector->count() == 0) { + setWalletActionsEnabled(false); + } else if (m_wallet_selector->count() == 1) { + m_wallet_selector_label_action->setVisible(false); + m_wallet_selector_action->setVisible(false); + } + rpcConsole->removeWallet(walletModel); + return walletFrame->removeWallet(name); +} + bool BitcoinGUI::setCurrentWallet(const QString& name) { if(!walletFrame) @@ -566,6 +540,12 @@ bool BitcoinGUI::setCurrentWallet(const QString& name) return walletFrame->setCurrentWallet(name); } +bool BitcoinGUI::setCurrentWalletBySelectorIndex(int index) +{ + QString internal_name = m_wallet_selector->itemData(index).toString(); + return setCurrentWallet(internal_name); +} + void BitcoinGUI::removeAllWallets() { if(!walletFrame) @@ -1125,6 +1105,24 @@ void BitcoinGUI::updateWalletStatus() } #endif // ENABLE_WALLET +void BitcoinGUI::updateProxyIcon() +{ + std::string ip_port; + bool proxy_enabled = clientModel->getProxyInfo(ip_port); + + if (proxy_enabled) { + if (labelProxyIcon->pixmap() == 0) { + 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)); + } else { + labelProxyIcon->show(); + } + } else { + labelProxyIcon->hide(); + } +} + void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if(!clientModel) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index e59c71cd4f..4deeb325b3 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -70,9 +70,10 @@ public: functionality. */ bool addWallet(WalletModel *walletModel); + bool removeWallet(WalletModel* walletModel); void removeAllWallets(); #endif // ENABLE_WALLET - bool enableWallet; + bool enableWallet = false; protected: void changeEvent(QEvent *e); @@ -86,55 +87,58 @@ private: interfaces::Node& m_node; std::unique_ptr<interfaces::Handler> m_handler_message_box; std::unique_ptr<interfaces::Handler> m_handler_question; - ClientModel *clientModel; - WalletFrame *walletFrame; - - UnitDisplayStatusBarControl *unitDisplayControl; - QLabel *labelWalletEncryptionIcon; - QLabel *labelWalletHDStatusIcon; - QLabel *connectionsControl; - QLabel *labelBlocksIcon; - QLabel *progressBarLabel; - QProgressBar *progressBar; - QProgressDialog *progressDialog; - - QMenuBar *appMenuBar; - QToolBar *appToolBar; - QAction *overviewAction; - QAction *historyAction; - QAction *quitAction; - QAction *sendCoinsAction; - QAction *sendCoinsMenuAction; - QAction *usedSendingAddressesAction; - QAction *usedReceivingAddressesAction; - QAction *signMessageAction; - QAction *verifyMessageAction; - QAction *aboutAction; - QAction *receiveCoinsAction; - QAction *receiveCoinsMenuAction; - QAction *optionsAction; - QAction *toggleHideAction; - QAction *encryptWalletAction; - QAction *backupWalletAction; - QAction *changePassphraseAction; - QAction *aboutQtAction; - QAction *openRPCConsoleAction; - QAction *openAction; - QAction *showHelpMessageAction; - - QLabel *m_wallet_selector_label; - QComboBox *m_wallet_selector; - - QSystemTrayIcon *trayIcon; - QMenu *trayIconMenu; - Notificator *notificator; - RPCConsole *rpcConsole; - HelpMessageDialog *helpMessageDialog; - ModalOverlay *modalOverlay; + ClientModel* clientModel = nullptr; + WalletFrame* walletFrame = nullptr; + + UnitDisplayStatusBarControl* unitDisplayControl = nullptr; + QLabel* labelWalletEncryptionIcon = nullptr; + QLabel* labelWalletHDStatusIcon = nullptr; + QLabel* labelProxyIcon = nullptr; + QLabel* connectionsControl = nullptr; + QLabel* labelBlocksIcon = nullptr; + QLabel* progressBarLabel = nullptr; + QProgressBar* progressBar = nullptr; + QProgressDialog* progressDialog = nullptr; + + QMenuBar* appMenuBar = nullptr; + QToolBar* appToolBar = nullptr; + QAction* overviewAction = nullptr; + QAction* historyAction = nullptr; + QAction* quitAction = nullptr; + QAction* sendCoinsAction = nullptr; + QAction* sendCoinsMenuAction = nullptr; + QAction* usedSendingAddressesAction = nullptr; + QAction* usedReceivingAddressesAction = nullptr; + QAction* signMessageAction = nullptr; + QAction* verifyMessageAction = nullptr; + QAction* aboutAction = nullptr; + QAction* receiveCoinsAction = nullptr; + QAction* receiveCoinsMenuAction = nullptr; + QAction* optionsAction = nullptr; + QAction* toggleHideAction = nullptr; + QAction* encryptWalletAction = nullptr; + QAction* backupWalletAction = nullptr; + QAction* changePassphraseAction = nullptr; + QAction* aboutQtAction = nullptr; + QAction* openRPCConsoleAction = nullptr; + QAction* openAction = nullptr; + QAction* showHelpMessageAction = nullptr; + QAction* m_wallet_selector_label_action = nullptr; + QAction* m_wallet_selector_action = nullptr; + + QLabel *m_wallet_selector_label = nullptr; + QComboBox* m_wallet_selector = nullptr; + + QSystemTrayIcon* trayIcon = nullptr; + QMenu* trayIconMenu = nullptr; + Notificator* notificator = nullptr; + RPCConsole* rpcConsole = nullptr; + HelpMessageDialog* helpMessageDialog = nullptr; + ModalOverlay* modalOverlay = nullptr; /** Keep track of previous number of blocks, to detect progress */ - int prevBlocks; - int spinnerFrame; + int prevBlocks = 0; + int spinnerFrame = 0; const PlatformStyle *platformStyle; @@ -185,6 +189,7 @@ public Q_SLOTS: #ifdef ENABLE_WALLET bool setCurrentWallet(const QString& name); + bool setCurrentWalletBySelectorIndex(int index); /** Set the UI status indicators based on the currently selected wallet. */ void updateWalletStatus(); @@ -209,6 +214,10 @@ public Q_SLOTS: void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName); #endif // ENABLE_WALLET +private: + /** Set the proxy-enabled icon as shown in the UI. */ + void updateProxyIcon(); + private Q_SLOTS: #ifdef ENABLE_WALLET /** Switch to overview (home) page */ @@ -248,7 +257,7 @@ private Q_SLOTS: /** Simply calls showNormalIfMinimized(true) for use in SLOT() macro */ void toggleHidden(); - /** called by a timer to check if fRequestShutdown has been set **/ + /** called by a timer to check if ShutdownRequested() has been set **/ void detectShutdown(); /** Show progress dialog e.g. for verifychain */ diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 9df05d2a13..30625f419a 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -20,6 +20,7 @@ QList<BitcoinUnits::Unit> BitcoinUnits::availableUnits() unitlist.append(BTC); unitlist.append(mBTC); unitlist.append(uBTC); + unitlist.append(SAT); return unitlist; } @@ -30,6 +31,7 @@ bool BitcoinUnits::valid(int unit) case BTC: case mBTC: case uBTC: + case SAT: return true; default: return false; @@ -43,6 +45,7 @@ QString BitcoinUnits::longName(int unit) case BTC: return QString("BTC"); case mBTC: return QString("mBTC"); case uBTC: return QString::fromUtf8("µBTC (bits)"); + case SAT: return QString("Satoshi (sat)"); default: return QString("???"); } } @@ -52,7 +55,8 @@ QString BitcoinUnits::shortName(int unit) switch(unit) { case uBTC: return QString::fromUtf8("bits"); - default: return longName(unit); + case SAT: return QString("sat"); + default: return longName(unit); } } @@ -63,6 +67,7 @@ QString BitcoinUnits::description(int unit) case BTC: return QString("Bitcoins"); case mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)"); case uBTC: return QString("Micro-Bitcoins (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); + case SAT: return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); default: return QString("???"); } } @@ -71,10 +76,11 @@ qint64 BitcoinUnits::factor(int unit) { switch(unit) { - case BTC: return 100000000; + case BTC: return 100000000; case mBTC: return 100000; case uBTC: return 100; - default: return 100000000; + case SAT: return 1; + default: return 100000000; } } @@ -85,6 +91,7 @@ int BitcoinUnits::decimals(int unit) case BTC: return 8; case mBTC: return 5; case uBTC: return 2; + case SAT: return 0; default: return 0; } } @@ -100,9 +107,7 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator int num_decimals = decimals(unit); qint64 n_abs = (n > 0 ? n : -n); qint64 quotient = n_abs / coin; - qint64 remainder = n_abs % coin; QString quotient_str = QString::number(quotient); - QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0'); // Use SI-style thin space separators as these are locale independent and can't be // confused with the decimal marker. @@ -116,7 +121,14 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator quotient_str.insert(0, '-'); else if (fPlus && n > 0) quotient_str.insert(0, '+'); - return quotient_str + QString(".") + remainder_str; + + if (num_decimals > 0) { + qint64 remainder = n_abs % coin; + QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0'); + return quotient_str + QString(".") + remainder_str; + } else { + return quotient_str; + } } diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index 310f651815..9b01b4678a 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -58,7 +58,8 @@ public: { BTC, mBTC, - uBTC + uBTC, + SAT }; enum SeparatorStyle diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 37fd06ccc9..a623771aa0 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -17,6 +17,7 @@ #include <interfaces/node.h> #include <validation.h> #include <net.h> +#include <netbase.h> #include <txmempool.h> #include <ui_interface.h> #include <util.h> @@ -268,3 +269,13 @@ void ClientModel::unsubscribeFromCoreSignals() m_handler_notify_block_tip->disconnect(); m_handler_notify_header_tip->disconnect(); } + +bool ClientModel::getProxyInfo(std::string& ip_port) const +{ + proxyType ipv4, ipv6; + if (m_node.getProxy((Network) 1, ipv4) && m_node.getProxy((Network) 2, ipv6)) { + ip_port = ipv4.proxy.ToStringIPPort(); + return true; + } + return false; +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index a609222f7d..9d4fa74b7a 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -70,6 +70,8 @@ public: QString formatClientStartupTime() const; QString dataDir() const; + bool getProxyInfo(std::string& ip_port) const; + // caches for the best header mutable std::atomic<int> cachedBestHeaderHeight; mutable std::atomic<int64_t> cachedBestHeaderTime; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index b08de27041..9b6480a915 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -109,11 +109,7 @@ CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidge connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(viewItemChanged(QTreeWidgetItem*, int))); // click on header -#if QT_VERSION < 0x050000 - ui->treeWidget->header()->setClickable(true); -#else ui->treeWidget->header()->setSectionsClickable(true); -#endif connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); // ok button @@ -122,10 +118,6 @@ CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidge // (un)select all connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); - // change coin control first column label due Qt4 bug. - // see https://github.com/bitcoin/bitcoin/issues/5716 - ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); - ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); @@ -392,13 +384,11 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 -#if QT_VERSION >= 0x050000 else if (column == COLUMN_CHECKBOX && item->childCount() > 0) { if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } -#endif } // shows count of locked unspent outputs diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index 38411c499f..f26a31158e 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -109,7 +109,7 @@ void EditAddressDialog::accept() break; case AddressTableModel::DUPLICATE_ADDRESS: QMessageBox::warning(this, windowTitle(), - tr("The entered address \"%1\" is already in the address book.").arg(ui->addressEdit->text()), + getDuplicateAddressWarning(), QMessageBox::Ok, QMessageBox::Ok); break; case AddressTableModel::WALLET_UNLOCK_FAILURE: @@ -129,6 +129,25 @@ void EditAddressDialog::accept() QDialog::accept(); } +QString EditAddressDialog::getDuplicateAddressWarning() const +{ + QString dup_address = ui->addressEdit->text(); + QString existing_label = model->labelForAddress(dup_address); + QString existing_purpose = model->purposeForAddress(dup_address); + + if (existing_purpose == "receive" && + (mode == NewSendingAddress || mode == EditSendingAddress)) { + return tr( + "Address \"%1\" already exists as a receiving address with label " + "\"%2\" and so cannot be added as a sending address." + ).arg(dup_address).arg(existing_label); + } + return tr( + "The entered address \"%1\" is already in the address book with " + "label \"%2\"." + ).arg(dup_address).arg(existing_label); +} + QString EditAddressDialog::getAddress() const { return address; diff --git a/src/qt/editaddressdialog.h b/src/qt/editaddressdialog.h index 41c5d1708a..3aba74bf08 100644 --- a/src/qt/editaddressdialog.h +++ b/src/qt/editaddressdialog.h @@ -30,7 +30,7 @@ public: EditSendingAddress }; - explicit EditAddressDialog(Mode mode, QWidget *parent); + explicit EditAddressDialog(Mode mode, QWidget *parent = 0); ~EditAddressDialog(); void setModel(AddressTableModel *model); @@ -45,6 +45,9 @@ public Q_SLOTS: private: bool saveCurrentRow(); + /** Return a descriptive string when adding an already-existing address fails. */ + QString getDuplicateAddressWarning() const; + Ui::EditAddressDialog *ui; QDataWidgetMapper *mapper; Mode mode; diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index a3721991ee..8f34e6bc82 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -38,6 +38,69 @@ </widget> </item> <item> + <spacer name="horizontalSpacer_0_Main"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_Main_Prune"> + <item> + <widget class="QCheckBox" name="prune"> + <property name="toolTip"> + <string>Disables some advanced features but all blocks will still be fully validated. Reverting this setting requires re-downloading the entire blockchain. Actual disk usage may be somewhat higher.</string> + </property> + <property name="text"> + <string>Prune &block storage to</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="pruneSize"/> + </item> + <item> + <widget class="QLabel" name="pruneSizeUnitLabel"> + <property name="text"> + <string>GB</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_Main_Prune"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="pruneWarning"> + <property name="text"> + <string>Reverting this setting requires re-downloading the entire blockchain.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout_2_Main"> <item> <widget class="QLabel" name="databaseCacheLabel"> @@ -81,7 +144,7 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_3_Main"> + <layout class="QHBoxLayout" name="horizontalLayout_Main_VerifyLabel"> <item> <widget class="QLabel" name="threadsScriptVerifLabel"> <property name="text"> @@ -103,7 +166,7 @@ </widget> </item> <item> - <spacer name="horizontalSpacer_3_Main"> + <spacer name="horizontalSpacer_Main_Threads"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 09fb435a58..2f916d0b44 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -206,10 +206,10 @@ <enum>Qt::StrongFocus</enum> </property> <property name="toolTip"> - <string>Bech32 addresses (BIP-173) are cheaper to spend from and offer better protection against typos. When unchecked a P2SH wrapped SegWit address will be created, compatible with older wallets.</string> + <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</string> </property> <property name="text"> - <string>Generate Bech32 address</string> + <string>Generate native segwit (Bech32) address</string> </property> </widget> </item> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 1d21d8c766..ff47653fb7 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -27,8 +27,6 @@ static const bool DEFAULT_SPLASHSCREEN = true; #define COLOR_BAREADDRESS QColor(140, 140, 140) /* Transaction list -- TX status decoration - open until date */ #define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255) -/* Transaction list -- TX status decoration - offline */ -#define COLOR_TX_STATUS_OFFLINE QColor(192, 192, 192) /* Transaction list -- TX status decoration - danger, tx needs attention */ #define COLOR_TX_STATUS_DANGER QColor(200, 100, 100) /* Transaction list -- TX status decoration - default color */ diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 563f930dec..acd9f7b35c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -53,13 +53,9 @@ #include <QSettings> #include <QTextDocument> // for Qt::mightBeRichText #include <QThread> +#include <QUrlQuery> #include <QMouseEvent> -#if QT_VERSION < 0x050000 -#include <QUrl> -#else -#include <QUrlQuery> -#endif #if QT_VERSION >= 0x50200 #include <QFontDatabase> @@ -95,11 +91,7 @@ QFont fixedPitchFont() return QFontDatabase::systemFont(QFontDatabase::FixedFont); #else QFont font("Monospace"); -#if QT_VERSION >= 0x040800 font.setStyleHint(QFont::Monospace); -#else - font.setStyleHint(QFont::TypeWriter); -#endif return font; #endif } @@ -127,12 +119,10 @@ void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); -#if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)").arg( QString::fromStdString(DummyAddress(Params())))); -#endif widget->setValidator(new BitcoinAddressEntryValidator(parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } @@ -151,12 +141,8 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) } rv.amount = 0; -#if QT_VERSION < 0x050000 - QList<QPair<QString, QString> > items = uri.queryItems(); -#else QUrlQuery uriQuery(uri); QList<QPair<QString, QString> > items = uriQuery.queryItems(); -#endif for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++) { bool fShouldReturnFalse = false; @@ -242,11 +228,7 @@ bool isDust(interfaces::Node& node, const QString& address, const CAmount& amoun QString HtmlEscape(const QString& str, bool fMultiLine) { -#if QT_VERSION < 0x050000 - QString escaped = Qt::escape(str); -#else QString escaped = str.toHtmlEscaped(); -#endif if(fMultiLine) { escaped = escaped.replace("\n", "<br>\n"); @@ -287,11 +269,7 @@ QString getSaveFileName(QWidget *parent, const QString &caption, const QString & QString myDir; if(dir.isEmpty()) // Default to user documents location { -#if QT_VERSION < 0x050000 - myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); -#else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); -#endif } else { @@ -337,11 +315,7 @@ QString getOpenFileName(QWidget *parent, const QString &caption, const QString & QString myDir; if(dir.isEmpty()) // Default to user documents location { -#if QT_VERSION < 0x050000 - myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); -#else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); -#endif } else { @@ -495,11 +469,7 @@ void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() // Refactored here for readability. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode) { -#if QT_VERSION < 0x050000 - tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); -#else tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); -#endif } void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 4a26964098..e965a91d18 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -233,7 +233,7 @@ namespace GUIUtil void mouseReleaseEvent(QMouseEvent *event); }; -#if defined(Q_OS_MAC) && QT_VERSION >= 0x050000 +#if defined(Q_OS_MAC) // workaround for Qt OSX Bug: // https://bugreports.qt-project.org/browse/QTBUG-15631 // QProgressBar uses around 10% CPU even when app is in background diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index 9e7de0f98f..a0b62ae000 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -14,10 +14,6 @@ #include <objc/objc.h> #include <objc/message.h> -#if QT_VERSION < 0x050000 -extern void qt_mac_set_dock_menu(QMenu *); -#endif - static MacDockIconHandler *s_instance = nullptr; bool dockClickHandler(id self,SEL _cmd,...) { @@ -54,9 +50,7 @@ MacDockIconHandler::MacDockIconHandler() : QObject() this->m_dummyWidget = new QWidget(); this->m_dockMenu = new QMenu(this->m_dummyWidget); this->setMainWindow(nullptr); -#if QT_VERSION < 0x050000 - qt_mac_set_dock_menu(this->m_dockMenu); -#elif QT_VERSION >= 0x050200 +#if QT_VERSION >= 0x050200 this->m_dockMenu->setAsDockMenu(); #endif [pool release]; diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index 2816633b0f..c4b732e3e0 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -68,11 +68,7 @@ NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, } //convert back to QPixmap -#if QT_VERSION >= 0x040700 pixmap.convertFromImage(img); -#else - pixmap = QPixmap::fromImage(img); -#endif } appIcon = QIcon(pixmap); diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 751cfa8b43..d211f908c8 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -15,9 +15,7 @@ OpenURIDialog::OpenURIDialog(QWidget *parent) : ui(new Ui::OpenURIDialog) { ui->setupUi(this); -#if QT_VERSION >= 0x040700 ui->uriEdit->setPlaceholderText("bitcoin:"); -#endif } OpenURIDialog::~OpenURIDialog() diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index c0ddb89b40..a57343f036 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -36,8 +36,17 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : /* Main elements init */ ui->databaseCache->setMinimum(nMinDbCache); ui->databaseCache->setMaximum(nMaxDbCache); + static const uint64_t GiB = 1024 * 1024 * 1024; + static const uint64_t nMinDiskSpace = MIN_DISK_SPACE_FOR_BLOCK_FILES / GiB + + (MIN_DISK_SPACE_FOR_BLOCK_FILES % GiB) ? 1 : 0; + ui->pruneSize->setMinimum(nMinDiskSpace); ui->threadsScriptVerif->setMinimum(-GetNumCores()); ui->threadsScriptVerif->setMaximum(MAX_SCRIPTCHECK_THREADS); + ui->pruneWarning->setVisible(false); + ui->pruneWarning->setStyleSheet("QLabel { color: red; }"); + + ui->pruneSize->setEnabled(false); + connect(ui->prune, SIGNAL(toggled(bool)), ui->pruneSize, SLOT(setEnabled(bool))); /* Network elements init */ #ifndef USE_UPNP @@ -88,28 +97,16 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : /** check if the locale name consists of 2 parts (language_country) */ if(langStr.contains("_")) { -#if QT_VERSION >= 0x040800 /** display language strings as "native language - native country (locale name)", e.g. "Deutsch - Deutschland (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") + locale.nativeCountryName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#else - /** display language strings as "language - country (locale name)", e.g. "German - Germany (de)" */ - ui->lang->addItem(QLocale::languageToString(locale.language()) + QString(" - ") + QLocale::countryToString(locale.country()) + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#endif } else { -#if QT_VERSION >= 0x040800 /** display language strings as "native language (locale name)", e.g. "Deutsch (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#else - /** display language strings as "language (locale name)", e.g. "German (de)" */ - ui->lang->addItem(QLocale::languageToString(locale.language()) + QString(" (") + langStr + QString(")"), QVariant(langStr)); -#endif } } -#if QT_VERSION >= 0x040700 ui->thirdPartyTxUrls->setPlaceholderText("https://example.com/tx/%s"); -#endif ui->unit->setModel(new BitcoinUnits(this)); @@ -157,6 +154,9 @@ void OptionsDialog::setModel(OptionsModel *_model) /* warn when one of the following settings changes by user action (placed here so init via mapper doesn't trigger them) */ /* Main */ + connect(ui->prune, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning())); + connect(ui->prune, SIGNAL(clicked(bool)), this, SLOT(togglePruneWarning(bool))); + connect(ui->pruneSize, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); connect(ui->databaseCache, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); connect(ui->threadsScriptVerif, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); /* Wallet */ @@ -176,6 +176,8 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup); mapper->addMapping(ui->threadsScriptVerif, OptionsModel::ThreadsScriptVerif); mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache); + mapper->addMapping(ui->prune, OptionsModel::Prune); + mapper->addMapping(ui->pruneSize, OptionsModel::PruneSize); /* Wallet */ mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); @@ -266,6 +268,11 @@ void OptionsDialog::on_hideTrayIcon_stateChanged(int fState) } } +void OptionsDialog::togglePruneWarning(bool enabled) +{ + ui->pruneWarning->setVisible(!ui->pruneWarning->isVisible()); +} + void OptionsDialog::showRestartWarning(bool fPersistent) { ui->statusLabel->setStyleSheet("QLabel { color: red; }"); @@ -324,7 +331,7 @@ void OptionsDialog::updateDefaultProxyNets() strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv6->setChecked(true) : ui->proxyReachIPv6->setChecked(false); - model->node().getProxy(NET_TOR, proxy); + model->node().getProxy(NET_ONION, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false); diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index faf9ff8959..5aad484ce7 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -53,6 +53,7 @@ private Q_SLOTS: void on_hideTrayIcon_stateChanged(int fState); + void togglePruneWarning(bool enabled); void showRestartWarning(bool fPersistent = false); void clearStatusLabel(); void updateProxyValidationState(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index cae9dace4c..31a85f4e23 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -88,6 +88,16 @@ void OptionsModel::Init(bool resetSettings) // by command-line and show this in the UI. // Main + if (!settings.contains("bPrune")) + settings.setValue("bPrune", false); + if (!settings.contains("nPruneSize")) + settings.setValue("nPruneSize", 2); + // Convert prune size to MB: + const uint64_t nPruneSizeMB = settings.value("nPruneSize").toInt() * 1000; + if (!m_node.softSetArg("-prune", settings.value("bPrune").toBool() ? std::to_string(nPruneSizeMB) : "0")) { + addOverriddenOption("-prune"); + } + if (!settings.contains("nDatabaseCache")) settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); if (!m_node.softSetArg("-dbcache", settings.value("nDatabaseCache").toString().toStdString())) @@ -281,6 +291,10 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return settings.value("language"); case CoinControlFeatures: return fCoinControlFeatures; + case Prune: + return settings.value("bPrune"); + case PruneSize: + return settings.value("nPruneSize"); case DatabaseCache: return settings.value("nDatabaseCache"); case ThreadsScriptVerif: @@ -405,6 +419,18 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in settings.setValue("fCoinControlFeatures", fCoinControlFeatures); Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); break; + case Prune: + if (settings.value("bPrune") != value) { + settings.setValue("bPrune", value); + setRestartRequired(true); + } + break; + case PruneSize: + if (settings.value("nPruneSize") != value) { + settings.setValue("nPruneSize", value); + setRestartRequired(true); + } + break; case DatabaseCache: if (settings.value("nDatabaseCache") != value) { settings.setValue("nDatabaseCache", value); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index fc1d119a71..2777cbeaf2 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -50,6 +50,8 @@ public: Language, // QString CoinControlFeatures, // bool ThreadsScriptVerif, // int + Prune, // bool + PruneSize, // int DatabaseCache, // int SpendZeroConfChange, // bool Listen, // bool diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp index b0ef475b35..dfeb70d669 100644 --- a/src/qt/paymentrequestplus.cpp +++ b/src/qt/paymentrequestplus.cpp @@ -97,12 +97,10 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c qWarning() << "PaymentRequestPlus::getMerchant: Payment request: certificate expired or not yet active: " << qCert; return false; } -#if QT_VERSION >= 0x050000 if (qCert.isBlacklisted()) { qWarning() << "PaymentRequestPlus::getMerchant: Payment request: certificate blacklisted: " << qCert; return false; } -#endif const unsigned char *data = (const unsigned char *)certChain.certificate(i).data(); X509 *cert = d2i_X509(nullptr, &data, certChain.certificate(i).size()); if (cert) diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 70cdb3361c..e5e6430959 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -41,12 +41,7 @@ #include <QSslSocket> #include <QStringList> #include <QTextDocument> - -#if QT_VERSION < 0x050000 -#include <QUrl> -#else #include <QUrlQuery> -#endif const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); @@ -100,11 +95,7 @@ static QList<QString> savedPaymentRequests; static void ReportInvalidCertificate(const QSslCertificate& cert) { -#if QT_VERSION < 0x050000 - qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); -#else 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); -#endif } // @@ -157,13 +148,11 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) continue; } -#if QT_VERSION >= 0x050000 // Blacklisted certificate if (cert.isBlacklisted()) { ReportInvalidCertificate(cert); continue; } -#endif QByteArray certData = cert.toDer(); const unsigned char *data = (const unsigned char *)certData.data(); @@ -413,11 +402,7 @@ void PaymentServer::handleURIOrFile(const QString& s) } else if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI { -#if QT_VERSION < 0x050000 - QUrl uri(s); -#else QUrlQuery uri((QUrl(s))); -#endif if (uri.hasQueryItem("r")) // payment request URI { QByteArray temp; @@ -648,7 +633,7 @@ void PaymentServer::fetchPaymentACK(WalletModel* walletModel, const SendCoinsRec // use for change. Despite an actual payment and not change, this is a close match: // it's the output type we use subject to privacy issues, but not restricted by what // other software supports. - const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::NONE ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); + const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); walletModel->wallet().learnRelatedScripts(newKey, change_type); CTxDestination dest = GetDestinationForKey(newKey, change_type); std::string label = tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString(); diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 7e318e3035..1c90504e9e 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -64,9 +64,7 @@ public: interfaces::Node::NodesStats nodes_stats; node.getNodesStats(nodes_stats); -#if QT_VERSION >= 0x040700 cachedNodeStats.reserve(nodes_stats.size()); -#endif for (auto& node_stats : nodes_stats) { CNodeCombinedStats stats; @@ -162,7 +160,8 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const case NetNodeId: return (qint64)rec->nodeStats.nodeid; case Address: - return QString::fromStdString(rec->nodeStats.addrName); + // prepend to peer address down-arrow symbol for inbound connection and up-arrow for outbound connection + return QString(rec->nodeStats.fInbound ? "↓ " : "↑ ") + QString::fromStdString(rec->nodeStats.addrName); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); case Ping: diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp index fce71f661a..a3a10aac18 100644 --- a/src/qt/platformstyle.cpp +++ b/src/qt/platformstyle.cpp @@ -46,7 +46,7 @@ void MakeSingleColorImage(QImage& img, const QColor& colorbase) QIcon ColorizeIcon(const QIcon& ico, const QColor& colorbase) { QIcon new_ico; - for (const QSize sz : ico.availableSizes()) + for (const QSize& sz : ico.availableSizes()) { QImage img(ico.pixmap(sz).toImage()); MakeSingleColorImage(img, colorbase); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 70e11f0296..e458a52856 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -94,14 +94,11 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); - // configure bech32 checkbox, disable if launched with legacy as default: if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { ui->useBech32->setCheckState(Qt::Checked); } else { ui->useBech32->setCheckState(Qt::Unchecked); } - - ui->useBech32->setVisible(model->wallet().getDefaultAddressType() != OutputType::LEGACY); } } @@ -144,9 +141,14 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() QString address; QString label = ui->reqLabel->text(); /* Generate new receiving address */ - OutputType address_type = model->wallet().getDefaultAddressType(); - if (address_type != OutputType::LEGACY) { - address_type = ui->useBech32->isChecked() ? OutputType::BECH32 : OutputType::P2SH_SEGWIT; + OutputType address_type; + if (ui->useBech32->isChecked()) { + address_type = OutputType::BECH32; + } else { + address_type = model->wallet().getDefaultAddressType(); + if (address_type == OutputType::BECH32) { + address_type = OutputType::P2SH_SEGWIT; + } } address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type); SendCoinsRecipient info(address, label, diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 75146e2214..f9610f2d3b 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -16,9 +16,6 @@ #include <QMimeData> #include <QMouseEvent> #include <QPixmap> -#if QT_VERSION < 0x050000 -#include <QUrl> -#endif #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> /* for USE_QRCODE */ diff --git a/src/qt/res/icons/proxy.png b/src/qt/res/icons/proxy.png Binary files differnew file mode 100644 index 0000000000..67c552d0de --- /dev/null +++ b/src/qt/res/icons/proxy.png diff --git a/src/qt/res/movies/makespinner.sh b/src/qt/res/movies/makespinner.sh index d0deb1238c..76e36e4f31 100755 --- a/src/qt/res/movies/makespinner.sh +++ b/src/qt/res/movies/makespinner.sh @@ -2,6 +2,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C FRAMEDIR=$(dirname $0) for i in {0..35} do diff --git a/src/qt/res/src/proxy.svg b/src/qt/res/src/proxy.svg new file mode 100644 index 0000000000..b42fa63948 --- /dev/null +++ b/src/qt/res/src/proxy.svg @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="128px" + height="128px" + id="svg2991" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="proxy.svg" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <defs + id="defs2993" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="3.8890872" + inkscape:cx="4.0410731" + inkscape:cy="31.916897" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:window-width="1920" + inkscape:window-height="1056" + inkscape:window-x="0" + inkscape:window-y="24" + inkscape:window-maximized="1" /> + <metadata + id="metadata2996"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="26.981934" + y="110.45972" + id="text2999" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3001" + x="26.981934" + y="110.45972" + style="font-size:111px">P</tspan></text> + </g> +</svg> diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 5122bab36f..f222357f27 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -40,10 +40,6 @@ #include <QTimer> #include <QStringList> -#if QT_VERSION < 0x050000 -#include <QUrl> -#endif - // TODO: add a scrollback limit, as there is currently none // TODO: make it possible to filter out categories (esp debug messages when implemented) // TODO: receive errors and debug messages through ClientModel @@ -70,6 +66,7 @@ namespace { const QStringList historyFilter = QStringList() << "importprivkey" << "importmulti" + << "sethdseed" << "signmessagewithprivkey" << "signrawtransaction" << "signrawtransactionwithkey" @@ -311,7 +308,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes std::string method = stack.back()[0]; std::string uri; #ifdef ENABLE_WALLET - if (walletID && !walletID->empty()) { + if (walletID) { QByteArray encodedName = QUrl::toPercentEncoding(QString::fromStdString(*walletID)); uri = "/wallet/"+std::string(encodedName.constData(), encodedName.length()); } @@ -424,7 +421,7 @@ void RPCExecutor::request(const QString &command, const QString &walletID) return; } std::string wallet_id = walletID.toStdString(); - if(!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, &wallet_id)) + if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, walletID.isNull() ? nullptr : &wallet_id)) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); return; @@ -455,12 +452,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty QWidget(parent), m_node(node), ui(new Ui::RPCConsole), - clientModel(0), - historyPtr(0), - platformStyle(_platformStyle), - peersTableContextMenu(0), - banTableContextMenu(0), - consoleFontSize(0) + platformStyle(_platformStyle) { ui->setupUi(this); QSettings settings; @@ -706,7 +698,8 @@ 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) - ui->WalletSelector->addItem(name, name); + QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name; + ui->WalletSelector->addItem(display_name, name); 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); @@ -716,6 +709,16 @@ void RPCConsole::addWallet(WalletModel * const walletModel) ui->WalletSelectorLabel->setVisible(true); } } + +void RPCConsole::removeWallet(WalletModel * const walletModel) +{ + const QString name = walletModel->getWalletName(); + ui->WalletSelector->removeItem(ui->WalletSelector->findData(name)); + if (ui->WalletSelector->count() == 2) { + ui->WalletSelector->setVisible(false); + ui->WalletSelectorLabel->setVisible(false); + } +} #endif static QString categoryClass(int category) @@ -913,7 +916,7 @@ void RPCConsole::on_lineEdit_returnPressed() } if (m_last_wallet_id != walletID) { - if (walletID.isEmpty()) { + if (walletID.isNull()) { message(CMD_REQUEST, tr("Executing command without any wallet")); } else { message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(walletID)); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index a9a60d09f1..0a1a469934 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -48,6 +48,7 @@ public: void setClientModel(ClientModel *model); void addWallet(WalletModel * const walletModel); + void removeWallet(WalletModel* const walletModel); enum MessageClass { MC_ERROR, @@ -145,18 +146,18 @@ private: }; interfaces::Node& m_node; - Ui::RPCConsole *ui; - ClientModel *clientModel; + Ui::RPCConsole* const ui; + ClientModel *clientModel = nullptr; QStringList history; - int historyPtr; + int historyPtr = 0; QString cmdBeforeBrowsing; QList<NodeId> cachedNodeids; - const PlatformStyle *platformStyle; - RPCTimerInterface *rpcTimerInterface; - QMenu *peersTableContextMenu; - QMenu *banTableContextMenu; - int consoleFontSize; - QCompleter *autoCompleter; + const PlatformStyle* const platformStyle; + RPCTimerInterface *rpcTimerInterface = nullptr; + QMenu *peersTableContextMenu = nullptr; + QMenu *banTableContextMenu = nullptr; + int consoleFontSize = 0; + QCompleter *autoCompleter = nullptr; QThread thread; QString m_last_wallet_id; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 261ab7a948..5c946f7c77 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -288,44 +288,60 @@ void SendCoinsDialog::on_sendButton_clicked() address.append("</span>"); QString recipientElement; + recipientElement = "<br />"; if (!rcp.paymentRequest.IsInitialized()) // normal payment { if(rcp.label.length() > 0) // label with address { - recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); + recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label))); recipientElement.append(QString(" (%1)").arg(address)); } else // just address { - recipientElement = tr("%1 to %2").arg(amount, address); + recipientElement.append(tr("%1 to %2").arg(amount, address)); } } else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request { - recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); + recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant))); } else // unauthenticated payment request { - recipientElement = tr("%1 to %2").arg(amount, address); + recipientElement.append(tr("%1 to %2").arg(amount, address)); } formatted.append(recipientElement); } QString questionString = tr("Are you sure you want to send?"); - questionString.append("<br /><br />%1"); + questionString.append("<br /><span style='font-size:10pt;'>"); + questionString.append(tr("Please, review your transaction.")); + questionString.append("</span><br />%1"); if(txFee > 0) { // append fee string if a fee is required - questionString.append("<hr /><span style='color:#aa0000;'>"); - questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); - questionString.append("</span> "); - questionString.append(tr("added as transaction fee")); + questionString.append("<hr /><b>"); + questionString.append(tr("Transaction fee")); + questionString.append("</b>"); // append transaction size - questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)"); + questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB): "); + + // append transaction fee value + questionString.append("<span style='color:#aa0000; font-weight:bold;'>"); + questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); + questionString.append("</span><br />"); + + // append RBF message according to transaction's signalling + questionString.append("<span style='font-size:10pt; font-weight:normal;'>"); + if (ui->optInRBF->isChecked()) { + questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125).")); + } else { + questionString.append(tr("Not signalling Replace-By-Fee, BIP-125.")); + } + questionString.append("</span>"); } // add total amount in all subdivision units @@ -337,19 +353,10 @@ void SendCoinsDialog::on_sendButton_clicked() if(u != model->getOptionsModel()->getDisplayUnit()) alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); } - questionString.append(tr("Total Amount %1") + questionString.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount")) .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))); - questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%1)</span>") - .arg(alternativeUnits.join(" " + tr("or") + "<br />"))); - - questionString.append("<hr /><span>"); - if (ui->optInRBF->isChecked()) { - questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125).")); - } else { - questionString.append(tr("Not signalling Replace-By-Fee, BIP-125.")); - } - questionString.append("</span>"); - + questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>") + .arg(alternativeUnits.join(" " + tr("or") + " "))); SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), questionString.arg(formatted.join("<br />")), SEND_CONFIRM_DELAY, this); diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 977425f7e3..e8c85bc2a1 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -32,9 +32,7 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par if (platformStyle->getUseExtraSpacing()) ui->payToLayout->setSpacing(4); -#if QT_VERSION >= 0x040700 ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); -#endif // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 94a3ad7987..223b39dc86 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -10,7 +10,6 @@ #include <qt/platformstyle.h> #include <qt/walletmodel.h> -#include <init.h> #include <key_io.h> #include <validation.h> // For strMessageMagic #include <wallet/wallet.h> @@ -37,9 +36,7 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformS ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/transaction_0")); ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); -#if QT_VERSION >= 0x040700 ui->signatureOut_SM->setPlaceholderText(tr("Click \"Sign Message\" to generate signature")); -#endif GUIUtil::setupAddressWidget(ui->addressIn_SM, this); GUIUtil::setupAddressWidget(ui->addressIn_VM, this); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 4d972b431c..831ef68cab 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -11,7 +11,6 @@ #include <qt/networkstyle.h> #include <clientversion.h> -#include <init.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <interfaces/wallet.h> diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp new file mode 100644 index 0000000000..c3d33c76d4 --- /dev/null +++ b/src/qt/test/addressbooktests.cpp @@ -0,0 +1,143 @@ +#include <qt/test/addressbooktests.h> +#include <qt/test/util.h> +#include <test/test_bitcoin.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 <wallet/wallet.h> + +#include <QTimer> +#include <QMessageBox> + +namespace +{ + +/** + * Fill the edit address dialog box with data, submit it, and ensure that + * the resulting message meets expectations. + */ +void EditAddressAndSubmit( + EditAddressDialog* dialog, + const QString& label, const QString& address, QString expected_msg) +{ + QString warning_text; + + dialog->findChild<QLineEdit*>("labelEdit")->setText(label); + dialog->findChild<QValidatedLineEdit*>("addressEdit")->setText(address); + + ConfirmMessage(&warning_text, 5); + dialog->accept(); + QCOMPARE(warning_text, expected_msg); +} + +/** + * Test adding various send addresses to the address book. + * + * There are three cases tested: + * + * - new_address: a new address which should add as a send address successfully. + * - existing_s_address: an existing sending address which won't add successfully. + * - existing_r_address: an existing receiving address which won't add successfully. + * + * In each case, verify the resulting state of the address book and optionally + * the warning message presented to the user. + */ +void TestAddAddressesToSendBook() +{ + TestChain100Setup test; + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock()); + bool firstRun; + wallet->LoadWallet(firstRun); + + auto build_address = [&wallet]() { + CKey key; + key.MakeNewKey(true); + CTxDestination dest(GetDestinationForKey( + key.GetPubKey(), wallet->m_default_address_type)); + + return std::make_pair(dest, QString::fromStdString(EncodeDestination(dest))); + }; + + CTxDestination r_key_dest, s_key_dest; + + // Add a preexisting "receive" entry in the address book. + QString preexisting_r_address; + QString r_label("already here (r)"); + + // Add a preexisting "send" entry in the address book. + QString preexisting_s_address; + QString s_label("already here (s)"); + + // Define a new address (which should add to the address book successfully). + QString new_address; + + std::tie(r_key_dest, preexisting_r_address) = build_address(); + std::tie(s_key_dest, preexisting_s_address) = build_address(); + std::tie(std::ignore, new_address) = build_address(); + + { + LOCK(wallet->cs_wallet); + wallet->SetAddressBook(r_key_dest, r_label.toStdString(), "receive"); + wallet->SetAddressBook(s_key_dest, s_label.toStdString(), "send"); + } + + auto check_addbook_size = [&wallet](int expected_size) { + QCOMPARE(static_cast<int>(wallet->mapAddressBook.size()), expected_size); + }; + + // We should start with the two addresses we added earlier and nothing else. + check_addbook_size(2); + + // Initialize relevant QT models. + std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); + auto node = interfaces::MakeNode(); + OptionsModel optionsModel(*node); + AddWallet(wallet); + WalletModel walletModel(std::move(node->getWallets()[0]), *node, platformStyle.get(), &optionsModel); + RemoveWallet(wallet); + EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); + editAddressDialog.setModel(walletModel.getAddressTableModel()); + + EditAddressAndSubmit( + &editAddressDialog, QString("uhoh"), preexisting_r_address, + QString( + "Address \"%1\" already exists as a receiving address with label " + "\"%2\" and so cannot be added as a sending address." + ).arg(preexisting_r_address).arg(r_label)); + + check_addbook_size(2); + + EditAddressAndSubmit( + &editAddressDialog, QString("uhoh, different"), preexisting_s_address, + QString( + "The entered address \"%1\" is already in the address book with " + "label \"%2\"." + ).arg(preexisting_s_address).arg(s_label)); + + check_addbook_size(2); + + // Submit a new address which should add successfully - we expect the + // warning message to be blank. + EditAddressAndSubmit( + &editAddressDialog, QString("new"), new_address, QString("")); + + check_addbook_size(3); +} + +} // namespace + +void AddressBookTests::addressBookTests() +{ + TestAddAddressesToSendBook(); +} diff --git a/src/qt/test/addressbooktests.h b/src/qt/test/addressbooktests.h new file mode 100644 index 0000000000..beeb9e76a9 --- /dev/null +++ b/src/qt/test/addressbooktests.h @@ -0,0 +1,15 @@ +#ifndef BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H +#define BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H + +#include <QObject> +#include <QTest> + +class AddressBookTests : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void addressBookTests(); +}; + +#endif // BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 25ee66dc6b..f0ac43a58c 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -13,6 +13,7 @@ #include <qt/test/compattests.h> #ifdef ENABLE_WALLET +#include <qt/test/addressbooktests.h> #include <qt/test/paymentservertests.h> #include <qt/test/wallettests.h> #endif @@ -25,12 +26,6 @@ #if defined(QT_STATICPLUGIN) #include <QtPlugin> -#if QT_VERSION < 0x050000 -Q_IMPORT_PLUGIN(qcncodecs) -Q_IMPORT_PLUGIN(qjpcodecs) -Q_IMPORT_PLUGIN(qtwcodecs) -Q_IMPORT_PLUGIN(qkrcodecs) -#else #if defined(QT_QPA_PLATFORM_MINIMAL) Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin); #endif @@ -42,7 +37,6 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif -#endif extern void noui_connect(); @@ -99,6 +93,10 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test5) != 0) { fInvalid = true; } + AddressBookTests test6; + if (QTest::qExec(&test6) != 0) { + fInvalid = true; + } #endif fs::remove_all(pathTemp); diff --git a/src/qt/test/util.cpp b/src/qt/test/util.cpp new file mode 100644 index 0000000000..261caaaee5 --- /dev/null +++ b/src/qt/test/util.cpp @@ -0,0 +1,22 @@ +#include <qt/callback.h> + +#include <QApplication> +#include <QMessageBox> +#include <QTimer> +#include <QString> +#include <QPushButton> +#include <QWidget> + +void ConfirmMessage(QString* text, int msec) +{ + QTimer::singleShot(msec, makeCallback([text](Callback* callback) { + for (QWidget* widget : QApplication::topLevelWidgets()) { + if (widget->inherits("QMessageBox")) { + QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget); + if (text) *text = messageBox->text(); + messageBox->defaultButton()->click(); + } + } + delete callback; + }), SLOT(call())); +} diff --git a/src/qt/test/util.h b/src/qt/test/util.h new file mode 100644 index 0000000000..324386c139 --- /dev/null +++ b/src/qt/test/util.h @@ -0,0 +1,12 @@ +#ifndef BITCOIN_QT_TEST_UTIL_H +#define BITCOIN_QT_TEST_UTIL_H + +/** + * Press "Ok" button in message box dialog. + * + * @param text - Optionally store dialog text. + * @param msec - Number of miliseconds to pause before triggering the callback. + */ +void ConfirmMessage(QString* text = nullptr, int msec = 0); + +#endif // BITCOIN_QT_TEST_UTIL_H diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 56d2d38194..c314dadde4 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,4 +1,5 @@ #include <qt/test/wallettests.h> +#include <qt/test/util.h> #include <interfaces/node.h> #include <qt/bitcoinamountfield.h> @@ -35,21 +36,6 @@ namespace { -//! Press "Ok" button in message box dialog. -void ConfirmMessage(QString* text = nullptr) -{ - QTimer::singleShot(0, makeCallback([text](Callback* callback) { - for (QWidget* widget : QApplication::topLevelWidgets()) { - if (widget->inherits("QMessageBox")) { - QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget); - if (text) *text = messageBox->text(); - messageBox->defaultButton()->click(); - } - } - delete callback; - }), SLOT(call())); -} - //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSend(QString* text = nullptr, bool cancel = false) { @@ -101,17 +87,6 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid) return {}; } -//! Request context menu (call method that is public in qt5, but protected in qt4). -void RequestContextMenu(QWidget* widget) -{ - class Qt4Hack : public QWidget - { - public: - using QWidget::customContextMenuRequested; - }; - static_cast<Qt4Hack*>(widget)->customContextMenuRequested({}); -} - //! Invoke bumpfee on txid and check results. void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, std::string expectError, bool cancel) { @@ -124,7 +99,7 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st QAction* action = view.findChild<QAction*>("bumpFeeAction"); table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); action->setEnabled(expectDisabled); - RequestContextMenu(table); + table->customContextMenuRequested({}); QCOMPARE(action->isEnabled(), !expectDisabled); action->setEnabled(true); @@ -158,21 +133,21 @@ void TestGUI() for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - CWallet wallet("mock", WalletDatabase::CreateMock()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock()); bool firstRun; - wallet.LoadWallet(firstRun); + wallet->LoadWallet(firstRun); { - LOCK(wallet.cs_wallet); - wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet.m_default_address_type), "", "receive"); - wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); + LOCK(wallet->cs_wallet); + wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); + wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { LOCK(cs_main); - WalletRescanReserver reserver(&wallet); + WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); + wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); } - wallet.SetBroadcastTransactions(true); + wallet->SetBroadcastTransactions(true); // Create widgets for sending coins and listing transactions. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); @@ -180,17 +155,17 @@ void TestGUI() TransactionView transactionView(platformStyle.get()); auto node = interfaces::MakeNode(); OptionsModel optionsModel(*node); - AddWallet(&wallet); + AddWallet(wallet); WalletModel walletModel(std::move(node->getWallets().back()), *node, platformStyle.get(), &optionsModel); - RemoveWallet(&wallet); + RemoveWallet(wallet); sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); // Send two transactions, and verify they are added to transaction list. TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel(); QCOMPARE(transactionTableModel->rowCount({}), 105); - uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CKeyID(), 5 * COIN, false /* rbf */); - uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CKeyID(), 10 * COIN, true /* rbf */); + uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, CKeyID(), 5 * COIN, false /* rbf */); + uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, CKeyID(), 10 * COIN, true /* rbf */); QCOMPARE(transactionTableModel->rowCount({}), 107); QVERIFY(FindTx(*transactionTableModel, txid1).isValid()); QVERIFY(FindTx(*transactionTableModel, txid2).isValid()); @@ -264,7 +239,7 @@ void TestGUI() QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1); } -} +} // namespace void WalletTests::walletTests() { diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index f316c3ca45..8297f75799 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -37,8 +37,6 @@ QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const i int nDepth = status.depth_in_main_chain; if (nDepth < 0) return tr("conflicted with a transaction with %1 confirmations").arg(-nDepth); - else if (adjustedTime - status.time_received > 2 * 60 && status.request_count == 0) - return tr("%1/offline").arg(nDepth); else if (nDepth == 0) return tr("0/unconfirmed, %1").arg((inMempool ? tr("in memory pool") : tr("not in memory pool"))) + (status.is_abandoned ? ", "+tr("abandoned") : ""); else if (nDepth < 6) @@ -68,14 +66,6 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall CAmount nNet = nCredit - nDebit; strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks, adjustedTime); - int nRequests = status.request_count; - if (nRequests != -1) - { - if (nRequests == 0) - strHTML += tr(", has not been successfully broadcast yet"); - else if (nRequests > 0) - strHTML += tr(", broadcast through %n node(s)", "", nRequests); - } strHTML += "<br>"; strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>"; @@ -102,7 +92,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall if (IsValidDestination(address)) { std::string name; isminetype ismine; - if (wallet.getAddress(address, &name, &ismine)) + if (wallet.getAddress(address, &name, &ismine, /* purpose= */ nullptr)) { strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>"; strHTML += "<b>" + tr("To") + ":</b> "; @@ -128,7 +118,8 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall strHTML += "<b>" + tr("To") + ":</b> "; CTxDestination dest = DecodeDestination(strAddress); std::string name; - if (wallet.getAddress(dest, &name) && !name.empty()) + if (wallet.getAddress( + dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty()) strHTML += GUIUtil::HtmlEscape(name) + " "; strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>"; } @@ -196,7 +187,8 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall { strHTML += "<b>" + tr("To") + ":</b> "; std::string name; - if (wallet.getAddress(address, &name) && !name.empty()) + if (wallet.getAddress( + address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty()) strHTML += GUIUtil::HtmlEscape(name) + " "; strHTML += GUIUtil::HtmlEscape(EncodeDestination(address)); if(toSelf == ISMINE_SPENDABLE) @@ -319,7 +311,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall if (ExtractDestination(vout.scriptPubKey, address)) { std::string name; - if (wallet.getAddress(address, &name) && !name.empty()) + if (wallet.getAddress(address, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr) && !name.empty()) strHTML += GUIUtil::HtmlEscape(name) + " "; strHTML += QString::fromStdString(EncodeDestination(address)); } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index b6ed66ad96..65f5e87d15 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -195,10 +195,6 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int if (wtx.is_in_main_chain) { status.matures_in = wtx.blocks_to_maturity; - - // Check if the block was requested by anyone - if (adjustedTime - wtx.time_received > 2 * 60 && wtx.request_count == 0) - status.status = TransactionStatus::MaturesWarning; } else { @@ -216,10 +212,6 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int { status.status = TransactionStatus::Conflicted; } - else if (adjustedTime - wtx.time_received > 2 * 60 && wtx.request_count == 0) - { - status.status = TransactionStatus::Offline; - } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 62961434ed..a6424e74fa 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -25,7 +25,7 @@ class TransactionStatus public: TransactionStatus(): countsForBalance(false), sortKey(""), - matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1) + matures_in(0), status(Unconfirmed), depth(0), open_for(0), cur_num_blocks(-1) { } enum Status { @@ -33,14 +33,12 @@ public: /// Normal (sent/received) transactions OpenUntilDate, /**< Transaction not yet final, waiting for date */ OpenUntilBlock, /**< Transaction not yet final, waiting for block */ - Offline, /**< Not sent to any other nodes **/ Unconfirmed, /**< Not yet mined into a block **/ Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/ Conflicted, /**< Conflicts with other transaction or mempool **/ Abandoned, /**< Abandoned from the wallet **/ /// Generated (mined) transactions Immature, /**< Mined but waiting for maturity */ - MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */ NotAccepted /**< Mined but not accepted */ }; diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 46169a91d1..63a4afe191 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -286,9 +286,6 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons case TransactionStatus::OpenUntilDate: status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for)); break; - case TransactionStatus::Offline: - status = tr("Offline"); - break; case TransactionStatus::Unconfirmed: status = tr("Unconfirmed"); break; @@ -307,9 +304,6 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons case TransactionStatus::Immature: status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); break; - case TransactionStatus::MaturesWarning: - status = tr("This block was not received by any other nodes and will probably not be accepted!"); - break; case TransactionStatus::NotAccepted: status = tr("Generated but not accepted"); break; @@ -447,8 +441,6 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) case TransactionStatus::OpenUntilBlock: case TransactionStatus::OpenUntilDate: return COLOR_TX_STATUS_OPENUNTILDATE; - case TransactionStatus::Offline: - return COLOR_TX_STATUS_OFFLINE; case TransactionStatus::Unconfirmed: return QIcon(":/icons/transaction_0"); case TransactionStatus::Abandoned: @@ -471,7 +463,6 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) int part = (wtx->status.depth * 4 / total) + 1; return QIcon(QString(":/icons/transaction_%1").arg(part)); } - case TransactionStatus::MaturesWarning: case TransactionStatus::NotAccepted: return QIcon(":/icons/transaction_0"); default: diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index aa6444245a..e60a387934 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -95,15 +95,11 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa hlayout->addWidget(typeWidget); search_widget = new QLineEdit(this); -#if QT_VERSION >= 0x040700 search_widget->setPlaceholderText(tr("Enter address, transaction id, or label to search")); -#endif hlayout->addWidget(search_widget); amountWidget = new QLineEdit(this); -#if QT_VERSION >= 0x040700 amountWidget->setPlaceholderText(tr("Min amount")); -#endif if (platformStyle->getUseExtraSpacing()) { amountWidget->setFixedWidth(97); } else { diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index d5b98486ae..1da25b0761 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -70,29 +70,15 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo ui->helpMessage->setVisible(false); } else { setWindowTitle(tr("Command-line options")); - QString header = tr("Usage:") + "\n" + - " bitcoin-qt [" + tr("command-line options") + "] " + "\n"; + QString header = "Usage:\n" + " bitcoin-qt [command-line options] \n"; QTextCursor cursor(ui->helpMessage->document()); cursor.insertText(version); cursor.insertBlock(); cursor.insertText(header); cursor.insertBlock(); - std::string strUsage = node.helpMessage(HelpMessageMode::BITCOIN_QT); - const bool showDebug = gArgs.GetBoolArg("-help-debug", false); - strUsage += HelpMessageGroup(tr("UI Options:").toStdString()); - if (showDebug) { - strUsage += HelpMessageOpt("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS)); - } - strUsage += HelpMessageOpt("-choosedatadir", strprintf(tr("Choose data directory on startup (default: %u)").toStdString(), DEFAULT_CHOOSE_DATADIR)); - strUsage += HelpMessageOpt("-lang=<lang>", tr("Set language, for example \"de_DE\" (default: system locale)").toStdString()); - strUsage += HelpMessageOpt("-min", tr("Start minimized").toStdString()); - strUsage += HelpMessageOpt("-resetguisettings", tr("Reset all settings changed in the GUI").toStdString()); - strUsage += HelpMessageOpt("-rootcertificates=<file>", tr("Set SSL root certificates for payment request (default: -system-)").toStdString()); - strUsage += HelpMessageOpt("-splash", strprintf(tr("Show splash screen on startup (default: %u)").toStdString(), DEFAULT_SPLASHSCREEN)); - if (showDebug) { - strUsage += HelpMessageOpt("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM)); - } + std::string strUsage = gArgs.GetHelpMessage(); QString coreOptions = QString::fromStdString(strUsage); text = version + "\n" + header + "\n" + coreOptions; diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 5b13353d7b..c5a13f61f4 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -57,8 +57,13 @@ bool WalletFrame::addWallet(WalletModel *walletModel) walletView->setWalletModel(walletModel); walletView->showOutOfSyncWarning(bOutOfSync); - /* TODO we should goto the currently selected page once dynamically adding wallets is supported */ - walletView->gotoOverviewPage(); + WalletView* current_wallet_view = currentWalletView(); + if (current_wallet_view) { + walletView->setCurrentIndex(current_wallet_view->currentIndex()); + } else { + walletView->gotoOverviewPage(); + } + walletStack->addWidget(walletView); mapWalletViews[name] = walletView; @@ -89,6 +94,7 @@ bool WalletFrame::removeWallet(const QString &name) WalletView *walletView = mapWalletViews.take(name); walletStack->removeWidget(walletView); + delete walletView; return true; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 8f30a2a871..389acf0a95 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -274,7 +274,8 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran { // Check if we have a new address or an updated label std::string name; - if (!m_wallet->getAddress(dest, &name)) + if (!m_wallet->getAddress( + dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr)) { m_wallet->setAddressBook(dest, strLabel, "send"); } @@ -363,6 +364,12 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri } // Handlers for core signals +static void NotifyUnload(WalletModel* walletModel) +{ + qDebug() << "NotifyUnload"; + QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection); +} + static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) { qDebug() << "NotifyKeyStoreStatusChanged"; @@ -410,6 +417,7 @@ 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)); @@ -420,6 +428,7 @@ void WalletModel::subscribeToCoreSignals() void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet + m_handler_unload->disconnect(); m_handler_status_changed->disconnect(); m_handler_address_book_changed->disconnect(); m_handler_transaction_changed->disconnect(); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index e5ed5b4e82..35ededb121 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -204,8 +204,11 @@ public: QString getWalletName() const; bool isMultiwallet(); + + AddressTableModel* getAddressTableModel() const { return addressTableModel; } private: std::unique_ptr<interfaces::Wallet> m_wallet; + std::unique_ptr<interfaces::Handler> m_handler_unload; std::unique_ptr<interfaces::Handler> m_handler_status_changed; std::unique_ptr<interfaces::Handler> m_handler_address_book_changed; std::unique_ptr<interfaces::Handler> m_handler_transaction_changed; @@ -259,6 +262,9 @@ Q_SIGNALS: // Watch-only address added void notifyWatchonlyChanged(bool fHaveWatchonly); + // Signal that wallet is about to be removed + void unload(); + public Q_SLOTS: /* Wallet status might have changed */ void updateStatus(); diff --git a/src/qt/winshutdownmonitor.cpp b/src/qt/winshutdownmonitor.cpp index 1e7a76efc0..122d6f0b12 100644 --- a/src/qt/winshutdownmonitor.cpp +++ b/src/qt/winshutdownmonitor.cpp @@ -4,8 +4,8 @@ #include <qt/winshutdownmonitor.h> -#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 -#include <init.h> +#if defined(Q_OS_WIN) +#include <shutdown.h> #include <util.h> #include <windows.h> diff --git a/src/qt/winshutdownmonitor.h b/src/qt/winshutdownmonitor.h index 0bed55a2c6..c8a523a538 100644 --- a/src/qt/winshutdownmonitor.h +++ b/src/qt/winshutdownmonitor.h @@ -9,7 +9,6 @@ #include <QByteArray> #include <QString> -#if QT_VERSION >= 0x050000 #include <windef.h> // for HWND #include <QAbstractNativeEventFilter> @@ -24,6 +23,5 @@ public: static void registerShutdownBlockReason(const QString& strReason, const HWND& mainWinId); }; #endif -#endif #endif // BITCOIN_QT_WINSHUTDOWNMONITOR_H diff --git a/src/random.cpp b/src/random.cpp index b004dcac39..fee6c2d92a 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -11,14 +11,15 @@ #include <compat.h> // for Windows API #include <wincrypt.h> #endif -#include <util.h> // for LogPrint() -#include <utilstrencodings.h> // for GetTime() +#include <logging.h> // for LogPrint() +#include <utiltime.h> // for GetTime() #include <stdlib.h> #include <chrono> #include <thread> #ifndef WIN32 +#include <fcntl.h> #include <sys/time.h> #endif @@ -33,6 +34,7 @@ #include <sys/random.h> #endif #ifdef HAVE_SYSCTL_ARND +#include <utilstrencodings.h> // for ARRAYLEN #include <sys/sysctl.h> #endif @@ -178,7 +180,7 @@ static void RandAddSeedPerfmon() /** Fallback: get 32 bytes of system entropy from /dev/urandom. The most * compatible way to get cryptographic randomness on UNIX-ish platforms. */ -void GetDevURandom(unsigned char *ent32) +static void GetDevURandom(unsigned char *ent32) { int f = open("/dev/urandom", O_RDONLY); if (f == -1) { diff --git a/src/rest.cpp b/src/rest.cpp index 5871b554a6..a5f164497d 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -6,6 +6,7 @@ #include <chain.h> #include <chainparams.h> #include <core_io.h> +#include <index/txindex.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <validation.h> @@ -32,7 +33,7 @@ enum class RetFormat { }; static const struct { - enum RetFormat rf; + RetFormat rf; const char* name; } rf_names[] = { {RetFormat::UNDEF, ""}, @@ -67,7 +68,7 @@ static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string me return false; } -static enum RetFormat ParseDataFormat(std::string& param, const std::string& strReq) +static RetFormat ParseDataFormat(std::string& param, const std::string& strReq) { const std::string::size_type pos = strReq.rfind('.'); if (pos == std::string::npos) @@ -216,7 +217,7 @@ static bool rest_block(HTTPRequest* req, return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } - if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) + if (IsBlockPruned(pblockindex)) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) @@ -350,6 +351,10 @@ static bool rest_tx(HTTPRequest* req, const std::string& strURIPart) if (!ParseHashStr(hashStr, hash)) return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + if (g_txindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + CTransactionRef tx; uint256 hashBlock = uint256(); if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true)) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 06c68ea27c..012e3e3ac1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -6,6 +6,7 @@ #include <rpc/blockchain.h> #include <amount.h> +#include <base58.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> @@ -13,6 +14,8 @@ #include <consensus/validation.h> #include <validation.h> #include <core_io.h> +#include <index/txindex.h> +#include <key_io.h> #include <policy/feerate.h> #include <policy/policy.h> #include <primitives/transaction.h> @@ -27,10 +30,12 @@ #include <validationinterface.h> #include <warnings.h> +#include <assert.h> #include <stdint.h> #include <univalue.h> +#include <boost/algorithm/string.hpp> #include <boost/thread/thread.hpp> // boost::thread::interrupt #include <memory> @@ -47,19 +52,13 @@ static std::mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock; -extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); - -/* Calculate the difficulty for a given block index, - * or the block index of the given chain. +/* Calculate the difficulty for a given block index. */ -double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex) +double GetDifficulty(const CBlockIndex* blockindex) { if (blockindex == nullptr) { - if (chain.Tip() == nullptr) - return 1.0; - else - blockindex = chain.Tip(); + return 1.0; } int nShift = (blockindex->nBits >> 24) & 0xff; @@ -80,11 +79,6 @@ double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex) return dDiff; } -double GetDifficulty(const CBlockIndex* blockindex) -{ - return GetDifficulty(chainActive, blockindex); -} - UniValue blockheaderToJSON(const CBlockIndex* blockindex) { AssertLockHeld(cs_main); @@ -105,6 +99,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) result.pushKV("bits", strprintf("%08x", blockindex->nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + result.pushKV("nTx", (uint64_t)blockindex->nTx); if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); @@ -150,6 +145,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + result.pushKV("nTx", (uint64_t)blockindex->nTx); if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); @@ -159,7 +155,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx return result; } -UniValue getblockcount(const JSONRPCRequest& request) +static UniValue getblockcount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -176,7 +172,7 @@ UniValue getblockcount(const JSONRPCRequest& request) return chainActive.Height(); } -UniValue getbestblockhash(const JSONRPCRequest& request) +static UniValue getbestblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -203,7 +199,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex) cond_blockchange.notify_all(); } -UniValue waitfornewblock(const JSONRPCRequest& request) +static UniValue waitfornewblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -241,7 +237,7 @@ UniValue waitfornewblock(const JSONRPCRequest& request) return ret; } -UniValue waitforblock(const JSONRPCRequest& request) +static UniValue waitforblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -283,7 +279,7 @@ UniValue waitforblock(const JSONRPCRequest& request) return ret; } -UniValue waitforblockheight(const JSONRPCRequest& request) +static UniValue waitforblockheight(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -325,7 +321,7 @@ UniValue waitforblockheight(const JSONRPCRequest& request) return ret; } -UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) +static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( @@ -340,7 +336,7 @@ UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) return NullUniValue; } -UniValue getdifficulty(const JSONRPCRequest& request) +static UniValue getdifficulty(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -354,23 +350,29 @@ UniValue getdifficulty(const JSONRPCRequest& request) ); LOCK(cs_main); - return GetDifficulty(); + return GetDifficulty(chainActive.Tip()); } -std::string EntryDescriptionString() +static std::string EntryDescriptionString() { return " \"size\" : n, (numeric) virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.\n" - " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" - " \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n" + " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)\n" + " \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority (DEPRECATED)\n" " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n" " \"height\" : n, (numeric) block height when transaction entered pool\n" " \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n" " \"descendantsize\" : n, (numeric) virtual transaction size of in-mempool descendants (including this one)\n" - " \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n" + " \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one) (DEPRECATED)\n" " \"ancestorcount\" : n, (numeric) number of in-mempool ancestor transactions (including this one)\n" " \"ancestorsize\" : n, (numeric) virtual transaction size of in-mempool ancestors (including this one)\n" - " \"ancestorfees\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one)\n" + " \"ancestorfees\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one) (DEPRECATED)\n" " \"wtxid\" : hash, (string) hash of serialized transaction, including witness data\n" + " \"fees\" : {\n" + " \"base\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" + " \"modified\" : n, (numeric) transaction fee with fee deltas used for mining priority in " + CURRENCY_UNIT + "\n" + " \"ancestor\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT + "\n" + " \"descendant\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one) in " + CURRENCY_UNIT + "\n" + " }\n" " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" " \"transactionid\", (string) parent transaction id\n" " ... ]\n" @@ -379,10 +381,17 @@ std::string EntryDescriptionString() " ... ]\n"; } -void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) +static void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs) { AssertLockHeld(mempool.cs); + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(e.GetFee())); + fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); + fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); + fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); + info.pushKV("fees", fees); + info.pushKV("size", (int)e.GetTxSize()); info.pushKV("fee", ValueFromAmount(e.GetFee())); info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); @@ -449,7 +458,7 @@ UniValue mempoolToJSON(bool fVerbose) } } -UniValue getrawmempool(const JSONRPCRequest& request) +static UniValue getrawmempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -481,7 +490,7 @@ UniValue getrawmempool(const JSONRPCRequest& request) return mempoolToJSON(fVerbose); } -UniValue getmempoolancestors(const JSONRPCRequest& request) +static UniValue getmempoolancestors(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -545,7 +554,7 @@ UniValue getmempoolancestors(const JSONRPCRequest& request) } } -UniValue getmempooldescendants(const JSONRPCRequest& request) +static UniValue getmempooldescendants(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -609,7 +618,7 @@ UniValue getmempooldescendants(const JSONRPCRequest& request) } } -UniValue getmempoolentry(const JSONRPCRequest& request) +static UniValue getmempoolentry(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( @@ -642,7 +651,7 @@ UniValue getmempoolentry(const JSONRPCRequest& request) return info; } -UniValue getblockhash(const JSONRPCRequest& request) +static UniValue getblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -667,7 +676,7 @@ UniValue getblockhash(const JSONRPCRequest& request) return pblockindex->GetBlockHash().GetHex(); } -UniValue getblockheader(const JSONRPCRequest& request) +static UniValue getblockheader(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -691,6 +700,7 @@ UniValue getblockheader(const JSONRPCRequest& request) " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"0000...1f3\" (string) Expected number of hashes required to produce the current chain (in hex)\n" + " \"nTx\" : n, (numeric) The number of transactions in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\", (string) The hash of the next block\n" "}\n" @@ -726,7 +736,26 @@ UniValue getblockheader(const JSONRPCRequest& request) return blockheaderToJSON(pblockindex); } -UniValue getblock(const JSONRPCRequest& request) +static CBlock GetBlockChecked(const CBlockIndex* pblockindex) +{ + CBlock block; + if (IsBlockPruned(pblockindex)) { + throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); + } + + if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { + // Block not found on disk. This could be because we have the block + // header in our index but don't have the block (for example if a + // non-whitelisted node sends us an unrequested long chain of valid + // blocks, we add the headers to our index, but don't accept the + // block). + throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); + } + + return block; +} + +static UniValue getblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -760,6 +789,7 @@ UniValue getblock(const JSONRPCRequest& request) " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n" + " \"nTx\" : n, (numeric) The number of transactions in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" "}\n" @@ -794,17 +824,7 @@ UniValue getblock(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - CBlock block; - if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) - throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); - - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) - // Block not found on disk. This could be because we have the block - // header in our index but don't have the block (for example if a - // non-whitelisted node sends us an unrequested long chain of valid - // blocks, we add the headers to our index, but don't accept the - // block). - throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); + const CBlock block = GetBlockChecked(pblockindex); if (verbosity <= 0) { @@ -837,7 +857,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, ss << hash; ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u); stats.nTransactions++; - for (const auto output : outputs) { + for (const auto& output : outputs) { ss << VARINT(output.first + 1); ss << output.second.out.scriptPubKey; ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); @@ -888,7 +908,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) return true; } -UniValue pruneblockchain(const JSONRPCRequest& request) +static UniValue pruneblockchain(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -929,7 +949,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request) else if (height > chainHeight) throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { - LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks."); + LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } @@ -937,7 +957,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request) return uint64_t(height); } -UniValue gettxoutsetinfo(const JSONRPCRequest& request) +static UniValue gettxoutsetinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -947,9 +967,9 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"height\":n, (numeric) The current block height (index)\n" - " \"bestblock\": \"hex\", (string) the best block hash hex\n" - " \"transactions\": n, (numeric) The number of transactions\n" - " \"txouts\": n, (numeric) The number of output transactions\n" + " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n" + " \"transactions\": n, (numeric) The number of transactions with unspent outputs\n" + " \"txouts\": n, (numeric) The number of unspent transaction outputs\n" " \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n" " \"hash_serialized_2\": \"hash\", (string) The serialized hash\n" " \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n" @@ -992,7 +1012,7 @@ UniValue gettxout(const JSONRPCRequest& request) " Note that an unspent output that is spent in the mempool won't appear.\n" "\nResult:\n" "{\n" - " \"bestblock\" : \"hash\", (string) the block hash\n" + " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" " \"value\" : x.xxx, (numeric) The transaction value in " + CURRENCY_UNIT + "\n" " \"scriptPubKey\" : { (json object)\n" @@ -1058,7 +1078,7 @@ UniValue gettxout(const JSONRPCRequest& request) return ret; } -UniValue verifychain(const JSONRPCRequest& request) +static UniValue verifychain(const JSONRPCRequest& request) { int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); @@ -1148,7 +1168,7 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse return rv; } -void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) { // Deployments with timeout value of 0 are hidden. // A timeout value of 0 guarantees a softfork will never be activated. @@ -1218,7 +1238,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) 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()); + 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("initialblockdownload", IsInitialBlockDownload()); @@ -1274,7 +1294,7 @@ struct CompareBlocksByHeight } }; -UniValue getchaintips(const JSONRPCRequest& request) +static UniValue getchaintips(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -1391,7 +1411,7 @@ UniValue mempoolInfoToJSON() return ret; } -UniValue getmempoolinfo(const JSONRPCRequest& request) +static UniValue getmempoolinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -1414,7 +1434,7 @@ UniValue getmempoolinfo(const JSONRPCRequest& request) return mempoolInfoToJSON(); } -UniValue preciousblock(const JSONRPCRequest& request) +static UniValue preciousblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1452,7 +1472,7 @@ UniValue preciousblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue invalidateblock(const JSONRPCRequest& request) +static UniValue invalidateblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1491,7 +1511,7 @@ UniValue invalidateblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue reconsiderblock(const JSONRPCRequest& request) +static UniValue reconsiderblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1529,7 +1549,7 @@ UniValue reconsiderblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue getchaintxstats(const JSONRPCRequest& request) +static UniValue getchaintxstats(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( @@ -1603,7 +1623,285 @@ UniValue getchaintxstats(const JSONRPCRequest& request) return ret; } -UniValue savemempool(const JSONRPCRequest& request) +template<typename T> +static T CalculateTruncatedMedian(std::vector<T>& scores) +{ + size_t size = scores.size(); + if (size == 0) { + return 0; + } + + std::sort(scores.begin(), scores.end()); + if (size % 2 == 0) { + return (scores[size / 2 - 1] + scores[size / 2]) / 2; + } else { + return scores[size / 2]; + } +} + +template<typename T> +static inline bool SetHasKeys(const std::set<T>& set) {return false;} +template<typename T, typename Tk, typename... Args> +static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&... args) +{ + return (set.count(key) != 0) || SetHasKeys(set, args...); +} + +// outpoint (needed for the utxo index) + nHeight + fCoinBase +static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); + +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" + "\nResult:\n" + "{ (json object)\n" + " \"avgfee\": xxxxx, (numeric) Average fee in the block\n" + " \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n" + " \"avgtxsize\": xxxxx, (numeric) Average transaction size\n" + " \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n" + " \"height\": xxxxx, (numeric) The height of the block\n" + " \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n" + " \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n" + " \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n" + " \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n" + " \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n" + " \"medianfeerate\": xxxxx, (numeric) Truncated median feerate (in satoshis per virtual byte)\n" + " \"mediantime\": xxxxx, (numeric) The block median time past\n" + " \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n" + " \"minfee\": xxxxx, (numeric) Minimum fee in the block\n" + " \"minfeerate\": xxxxx, (numeric) Minimum feerate (in satoshis per virtual byte)\n" + " \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n" + " \"outs\": xxxxx, (numeric) The number of outputs\n" + " \"subsidy\": xxxxx, (numeric) The block subsidy\n" + " \"swtotal_size\": xxxxx, (numeric) Total size of all segwit transactions\n" + " \"swtotal_weight\": xxxxx, (numeric) Total weight of all segwit transactions divided by segwit scale factor (4)\n" + " \"swtxs\": xxxxx, (numeric) The number of segwit transactions\n" + " \"time\": xxxxx, (numeric) The block time\n" + " \"total_out\": xxxxx, (numeric) Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])\n" + " \"total_size\": xxxxx, (numeric) Total size of all non-coinbase transactions\n" + " \"total_weight\": xxxxx, (numeric) Total weight of all non-coinbase transactions divided by segwit scale factor (4)\n" + " \"totalfee\": xxxxx, (numeric) The fee total\n" + " \"txs\": xxxxx, (numeric) The number of transactions (excluding coinbase)\n" + " \"utxo_increase\": xxxxx, (numeric) The increase/decrease in the number of unspent outputs\n" + " \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in size for the utxo index (not discounting op_return and similar)\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + ); + } + + LOCK(cs_main); + + CBlockIndex* pindex; + if (request.params[0].isNum()) { + const int height = request.params[0].get_int(); + const int current_tip = chainActive.Height(); + if (height < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); + } + if (height > current_tip) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip)); + } + + pindex = chainActive[height]; + } else { + const std::string strHash = request.params[0].get_str(); + const uint256 hash(uint256S(strHash)); + pindex = LookupBlockIndex(hash); + if (!pindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + if (!chainActive.Contains(pindex)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString())); + } + } + + assert(pindex != nullptr); + + std::set<std::string> stats; + if (!request.params[1].isNull()) { + const UniValue stats_univalue = request.params[1].get_array(); + for (unsigned int i = 0; i < stats_univalue.size(); i++) { + const std::string stat = stats_univalue[i].get_str(); + stats.insert(stat); + } + } + + const CBlock block = GetBlockChecked(pindex); + + const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default) + const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; + const bool do_medianfee = do_all || stats.count("medianfee") != 0; + const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0; + const bool loop_inputs = do_all || do_medianfee || do_medianfeerate || + SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate"); + const bool loop_outputs = do_all || loop_inputs || stats.count("total_out"); + const bool do_calculate_size = do_mediantxsize || + SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size"); + const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "medianfeerate", "minfeerate", "maxfeerate"); + const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight"); + + CAmount maxfee = 0; + CAmount maxfeerate = 0; + CAmount minfee = MAX_MONEY; + CAmount minfeerate = MAX_MONEY; + CAmount total_out = 0; + CAmount totalfee = 0; + int64_t inputs = 0; + int64_t maxtxsize = 0; + int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE; + int64_t outputs = 0; + int64_t swtotal_size = 0; + int64_t swtotal_weight = 0; + int64_t swtxs = 0; + int64_t total_size = 0; + int64_t total_weight = 0; + int64_t utxo_size_inc = 0; + std::vector<CAmount> fee_array; + std::vector<CAmount> feerate_array; + std::vector<int64_t> txsize_array; + + for (const auto& tx : block.vtx) { + outputs += tx->vout.size(); + + CAmount tx_total_out = 0; + if (loop_outputs) { + for (const CTxOut& out : tx->vout) { + tx_total_out += out.nValue; + utxo_size_inc += GetSerializeSize(out, SER_NETWORK, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; + } + } + + if (tx->IsCoinBase()) { + continue; + } + + inputs += tx->vin.size(); // Don't count coinbase's fake input + total_out += tx_total_out; // Don't count coinbase reward + + int64_t tx_size = 0; + if (do_calculate_size) { + + tx_size = tx->GetTotalSize(); + if (do_mediantxsize) { + txsize_array.push_back(tx_size); + } + maxtxsize = std::max(maxtxsize, tx_size); + mintxsize = std::min(mintxsize, tx_size); + total_size += tx_size; + } + + int64_t weight = 0; + if (do_calculate_weight) { + weight = GetTransactionWeight(*tx); + total_weight += weight; + } + + if (do_calculate_sw && tx->HasWitness()) { + ++swtxs; + swtotal_size += tx_size; + swtotal_weight += weight; + } + + 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; + uint256 hashBlock; + if (!GetTransaction(in.prevout.hash, tx_in, Params().GetConsensus(), hashBlock, false)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, std::string("Unexpected internal error (tx index seems corrupt)")); + } + + CTxOut prevoutput = tx_in->vout[in.prevout.n]; + + tx_total_in += prevoutput.nValue; + utxo_size_inc -= GetSerializeSize(prevoutput, SER_NETWORK, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; + } + + CAmount txfee = tx_total_in - tx_total_out; + assert(MoneyRange(txfee)); + if (do_medianfee) { + fee_array.push_back(txfee); + } + maxfee = std::max(maxfee, txfee); + minfee = std::min(minfee, txfee); + totalfee += txfee; + + // New feerate uses satoshis per virtual byte instead of per serialized byte + CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0; + if (do_medianfeerate) { + feerate_array.push_back(feerate); + } + maxfeerate = std::max(maxfeerate, feerate); + minfeerate = std::min(minfeerate, feerate); + } + } + + UniValue ret_all(UniValue::VOBJ); + ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0); + ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte + ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); + ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); + ret_all.pushKV("height", (int64_t)pindex->nHeight); + ret_all.pushKV("ins", inputs); + ret_all.pushKV("maxfee", maxfee); + ret_all.pushKV("maxfeerate", maxfeerate); + ret_all.pushKV("maxtxsize", maxtxsize); + ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); + ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array)); + ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); + ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); + ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee); + ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate); + ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize); + ret_all.pushKV("outs", outputs); + ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); + ret_all.pushKV("swtotal_size", swtotal_size); + ret_all.pushKV("swtotal_weight", swtotal_weight); + ret_all.pushKV("swtxs", swtxs); + ret_all.pushKV("time", pindex->GetBlockTime()); + ret_all.pushKV("total_out", total_out); + ret_all.pushKV("total_size", total_size); + ret_all.pushKV("total_weight", total_weight); + ret_all.pushKV("totalfee", totalfee); + ret_all.pushKV("txs", (int64_t)block.vtx.size()); + ret_all.pushKV("utxo_increase", outputs - inputs); + ret_all.pushKV("utxo_size_inc", utxo_size_inc); + + if (do_all) { + return ret_all; + } + + UniValue ret(UniValue::VOBJ); + for (const std::string& stat : stats) { + const UniValue& value = ret_all[stat]; + if (value.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat)); + } + ret.pushKV(stat, value); + } + return ret; +} + +static UniValue savemempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( @@ -1626,11 +1924,296 @@ UniValue savemempool(const JSONRPCRequest& request) return NullUniValue; } +//! Search for a given set of pubkey scripts +bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) { + scan_progress = 0; + count = 0; + while (cursor->Valid()) { + COutPoint key; + Coin coin; + if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false; + if (++count % 8192 == 0) { + boost::this_thread::interruption_point(); + if (should_abort) { + // allow to abort the scan via the abort reference + return false; + } + } + if (count % 256 == 0) { + // update progress reference every 256 item + uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1); + scan_progress = (int)(high * 100.0 / 65536.0 + 0.5); + } + if (needles.count(coin.out.scriptPubKey)) { + out_results.emplace(key, coin); + } + cursor->Next(); + } + scan_progress = 100; + return true; +} + +/** RAII object to prevent concurrency issue when scanning the txout set */ +static std::mutex g_utxosetscan; +static std::atomic<int> g_scan_progress; +static std::atomic<bool> g_scan_in_progress; +static std::atomic<bool> g_should_abort_scan; +class CoinsViewScanReserver +{ +private: + bool m_could_reserve; +public: + explicit CoinsViewScanReserver() : m_could_reserve(false) {} + + bool reserve() { + assert (!m_could_reserve); + std::lock_guard<std::mutex> lock(g_utxosetscan); + if (g_scan_in_progress) { + return false; + } + g_scan_in_progress = true; + m_could_reserve = true; + return true; + } + + ~CoinsViewScanReserver() { + if (m_could_reserve) { + std::lock_guard<std::mutex> lock(g_utxosetscan); + g_scan_in_progress = false; + } + } +}; + +static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" }; + +enum class OutputScriptType { + UNKNOWN, + P2PK, + P2PKH, + P2SH_P2WPKH, + P2WPKH +}; + +static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype) +{ + if (outputtype == "P2PK") return OutputScriptType::P2PK; + else if (outputtype == "P2PKH") return OutputScriptType::P2PKH; + else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH; + else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH; + else return OutputScriptType::UNKNOWN; +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type) +{ + switch (type) { + case OutputScriptType::P2PKH: return key.GetID(); + case OutputScriptType::P2SH_P2WPKH: + case OutputScriptType::P2WPKH: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + if (type == OutputScriptType::P2SH_P2WPKH) { + CScript witprog = GetScriptForDestination(witdest); + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +UniValue scantxoutset(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "scantxoutset <action> ( <scanobjects> )\n" + "\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n" + "Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n" + "\nArguments:\n" + "1. \"action\" (string, required) 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, optional) Array of scan objects (only one object type per scan object allowed)\n" + " [\n" + " { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n" + " { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n" + " { \"pubkey\" : (object, optional) Public key\n" + " {\n" + " \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n" + " \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n" + " }\n" + " },\n" + " ]\n" + "\nResult:\n" + "{\n" + " \"unspents\": [\n" + " {\n" + " \"txid\" : \"transactionid\", (string) The transaction id\n" + " \"vout\": n, (numeric) the vout value\n" + " \"scriptPubKey\" : \"script\", (string) the script key\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" + " ,...], \n" + " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" + "]\n" + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); + + UniValue result(UniValue::VOBJ); + if (request.params[0].get_str() == "status") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // no scan in progress + return NullUniValue; + } + result.pushKV("progress", g_scan_progress); + return result; + } else if (request.params[0].get_str() == "abort") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // reserve was possible which means no scan was running + return false; + } + // set the abort flag + g_should_abort_scan = true; + return true; + } else if (request.params[0].get_str() == "start") { + CoinsViewScanReserver reserver; + if (!reserver.reserve()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); + } + std::set<CScript> needles; + CAmount total_in = 0; + + // loop through the scan objects + for (const UniValue& scanobject : request.params[1].get_array().getValues()) { + if (!scanobject.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object"); + } + UniValue address_uni = find_value(scanobject, "address"); + UniValue pubkey_uni = find_value(scanobject, "pubkey"); + UniValue script_uni = find_value(scanobject, "script"); + + // make sure only one object type is present + if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object"); + } else if (!address_uni.isNull() && !address_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value"); + } else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value"); + } else if (!script_uni.isNull() && !script_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value"); + } else if (address_uni.isStr()) { + // type: address + // decode destination and derive the scriptPubKey + // add the script to the scan containers + CTxDestination dest = DecodeDestination(address_uni.get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + CScript script = GetScriptForDestination(dest); + assert(!script.empty()); + needles.insert(script); + } else if (pubkey_uni.isObject()) { + // type: pubkey + // derive script(s) according to the script_type parameter + UniValue script_types_uni = find_value(pubkey_uni, "script_types"); + UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey"); + + // check the script types and use the default if not provided + if (!script_types_uni.isNull() && !script_types_uni.isArray()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array"); + } else if (script_types_uni.isNull()) { + // use the default script types + script_types_uni = UniValue(UniValue::VARR); + for (const char *t : g_default_scantxoutset_script_types) { + script_types_uni.push_back(t); + } + } + + // check the acctual pubkey + if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded"); + } + CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey")); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key"); + } + + // loop through the script types and derive the script + for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) { + OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str()); + if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type"); + CScript script; + if (script_type == OutputScriptType::P2PK) { + // support legacy P2PK scripts + script << ToByteVector(pubkey) << OP_CHECKSIG; + } else { + script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type)); + } + assert(!script.empty()); + needles.insert(script); + } + } else if (script_uni.isStr()) { + // type: script + // check and add the script to the scan containers (needles array) + CScript script(ParseHexV(script_uni, "script")); + // TODO: check script: max length, has OP, is unspenable etc. + needles.insert(script); + } + } + + // Scan the unspent transaction output set for inputs + UniValue unspents(UniValue::VARR); + std::vector<CTxOut> input_txos; + std::map<COutPoint, Coin> coins; + g_should_abort_scan = false; + g_scan_progress = 0; + int64_t count = 0; + std::unique_ptr<CCoinsViewCursor> pcursor; + { + LOCK(cs_main); + FlushStateToDisk(); + pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); + assert(pcursor); + } + bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); + result.pushKV("success", res); + result.pushKV("searched_items", count); + + for (const auto& it : coins) { + const COutPoint& outpoint = it.first; + const Coin& coin = it.second; + const CTxOut& txo = coin.out; + input_txos.push_back(txo); + total_in += txo.nValue; + + UniValue unspent(UniValue::VOBJ); + 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("amount", ValueFromAmount(txo.nValue)); + unspent.pushKV("height", (int32_t)coin.nHeight); + + unspents.push_back(unspent); + } + result.pushKV("unspents", unspents); + result.pushKV("total_amount", ValueFromAmount(total_in)); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + } + return result; +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "blockchain", "getblockchaininfo", &getblockchaininfo, {} }, { "blockchain", "getchaintxstats", &getchaintxstats, {"nblocks", "blockhash"} }, + { "blockchain", "getblockstats", &getblockstats, {"hash_or_height", "stats"} }, { "blockchain", "getbestblockhash", &getbestblockhash, {} }, { "blockchain", "getblockcount", &getblockcount, {} }, { "blockchain", "getblock", &getblock, {"blockhash","verbosity|verbose"} }, @@ -1650,6 +2233,7 @@ static const CRPCCommand commands[] = { "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", &preciousblock, {"blockhash"} }, + { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} }, /* Not shown in help */ { "hidden", "invalidateblock", &invalidateblock, {"blockhash"} }, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 960edfd56f..3aa8de2d2b 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -16,7 +16,7 @@ class UniValue; * @return A floating point number that is a multiple of the main net minimum * difficulty (4295032833 hashes). */ -double GetDifficulty(const CBlockIndex* blockindex = nullptr); +double GetDifficulty(const CBlockIndex* blockindex); /** Callback for when block tip changed. */ void RPCNotifyBlockChange(bool ibd, const CBlockIndex *); @@ -34,4 +34,3 @@ UniValue mempoolToJSON(bool fVerbose = false); UniValue blockheaderToJSON(const CBlockIndex* blockindex); #endif - diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 34c41b3b6b..593c59ca5e 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -38,6 +38,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 5 , "replaceable" }, { "sendtoaddress", 6 , "conf_target" }, { "settxfee", 0, "amount" }, + { "sethdseed", 0, "newkeypool" }, { "getreceivedbyaddress", 1, "minconf" }, { "getreceivedbyaccount", 1, "minconf" }, { "getreceivedbylabel", 1, "minconf" }, @@ -51,7 +52,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listreceivedbylabel", 0, "minconf" }, { "listreceivedbylabel", 1, "include_empty" }, { "listreceivedbylabel", 2, "include_watchonly" }, - { "getlabeladdress", 1, "force" }, { "getbalance", 1, "minconf" }, { "getbalance", 2, "include_watchonly" }, { "getblockhash", 0, "height" }, @@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 4, "subtractfeefrom" }, { "sendmany", 5 , "replaceable" }, { "sendmany", 6 , "conf_target" }, + { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, { "createmultisig", 0, "nrequired" }, @@ -109,6 +110,22 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, + { "walletcreatefundedpsbt", 0, "inputs" }, + { "walletcreatefundedpsbt", 1, "outputs" }, + { "walletcreatefundedpsbt", 2, "locktime" }, + { "walletcreatefundedpsbt", 3, "replaceable" }, + { "walletcreatefundedpsbt", 4, "options" }, + { "walletcreatefundedpsbt", 5, "bip32derivs" }, + { "walletprocesspsbt", 1, "sign" }, + { "walletprocesspsbt", 3, "bip32derivs" }, + { "createpsbt", 0, "inputs" }, + { "createpsbt", 1, "outputs" }, + { "createpsbt", 2, "locktime" }, + { "createpsbt", 3, "replaceable" }, + { "combinepsbt", 0, "txs"}, + { "finalizepsbt", 1, "extract"}, + { "converttopsbt", 1, "permitsigdata"}, + { "converttopsbt", 2, "iswitness"}, { "gettxout", 1, "n" }, { "gettxout", 2, "include_mempool" }, { "gettxoutproof", 0, "txids" }, @@ -122,6 +139,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "importmulti", 1, "options" }, { "verifychain", 0, "checklevel" }, { "verifychain", 1, "nblocks" }, + { "getblockstats", 0, "hash_or_height" }, + { "getblockstats", 1, "stats" }, { "pruneblockchain", 0, "height" }, { "keypoolrefill", 0, "newsize" }, { "getrawmempool", 0, "verbose" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index b4bb78e689..1f1044d80b 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -10,7 +10,6 @@ #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> -#include <init.h> #include <validation.h> #include <key_io.h> #include <miner.h> @@ -20,6 +19,7 @@ #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> +#include <shutdown.h> #include <txmempool.h> #include <util.h> #include <utilstrencodings.h> @@ -44,7 +44,7 @@ unsigned int ParseConfirmTarget(const UniValue& value) * or from the last difficulty change if 'lookup' is nonpositive. * If 'height' is nonnegative, compute the estimate at the time when a given block was found. */ -UniValue GetNetworkHashPS(int lookup, int height) { +static UniValue GetNetworkHashPS(int lookup, int height) { CBlockIndex *pb = chainActive.Tip(); if (height >= 0 && height < chainActive.Height()) @@ -81,7 +81,7 @@ UniValue GetNetworkHashPS(int lookup, int height) { return workDiff.getdouble() / timeDiff; } -UniValue getnetworkhashps(const JSONRPCRequest& request) +static UniValue getnetworkhashps(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( @@ -151,7 +151,7 @@ UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGen return blockHashes; } -UniValue generatetoaddress(const JSONRPCRequest& request) +static UniValue generatetoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( @@ -185,7 +185,7 @@ UniValue generatetoaddress(const JSONRPCRequest& request) return generateBlocks(coinbaseScript, nGenerate, nMaxTries, false); } -UniValue getmininginfo(const JSONRPCRequest& request) +static UniValue getmininginfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -214,7 +214,7 @@ UniValue getmininginfo(const JSONRPCRequest& request) obj.pushKV("blocks", (int)chainActive.Height()); obj.pushKV("currentblockweight", (uint64_t)nLastBlockWeight); obj.pushKV("currentblocktx", (uint64_t)nLastBlockTx); - obj.pushKV("difficulty", (double)GetDifficulty()); + obj.pushKV("difficulty", (double)GetDifficulty(chainActive.Tip())); obj.pushKV("networkhashps", getnetworkhashps(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", Params().NetworkIDString()); @@ -224,7 +224,7 @@ UniValue getmininginfo(const JSONRPCRequest& request) // NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts -UniValue prioritisetransaction(const JSONRPCRequest& request) +static UniValue prioritisetransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( @@ -278,7 +278,7 @@ static UniValue BIP22ValidationResult(const CValidationState& state) return "valid?"; } -std::string gbt_vb_name(const Consensus::DeploymentPos pos) { +static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos]; std::string s = vbinfo.name; if (!vbinfo.gbt_force) { @@ -287,7 +287,7 @@ std::string gbt_vb_name(const Consensus::DeploymentPos pos) { return s; } -UniValue getblocktemplate(const JSONRPCRequest& request) +static UniValue getblocktemplate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -525,6 +525,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request) // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } + assert(pindexPrev); CBlock* pblock = &pblocktemplate->block; // pointer for convenience const Consensus::Params& consensusParams = Params().GetConsensus(); @@ -694,7 +695,7 @@ protected: } }; -UniValue submitblock(const JSONRPCRequest& request) +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) { @@ -724,7 +725,6 @@ UniValue submitblock(const JSONRPCRequest& request) } uint256 hash = block.GetHash(); - bool fBlockPresent = false; { LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(hash); @@ -735,8 +735,6 @@ UniValue submitblock(const JSONRPCRequest& request) if (pindex->nStatus & BLOCK_FAILED_MASK) { return "duplicate-invalid"; } - // Otherwise, we might only have the header - process the block before returning - fBlockPresent = true; } } @@ -748,13 +746,15 @@ UniValue submitblock(const JSONRPCRequest& request) } } + bool new_block; submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); - bool fAccepted = ProcessNewBlock(Params(), blockptr, true, nullptr); + bool accepted = ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); UnregisterValidationInterface(&sc); - if (fBlockPresent) { - if (fAccepted && !sc.found) { - return "duplicate-inconclusive"; + if (!new_block) { + if (!accepted) { + // TODO Maybe pass down fNewBlock to AcceptBlockHeader, so it is properly set to true in this case? + return "invalid"; } return "duplicate"; } @@ -764,13 +764,13 @@ UniValue submitblock(const JSONRPCRequest& request) return BIP22ValidationResult(sc.state); } -UniValue estimatefee(const JSONRPCRequest& request) +static UniValue estimatefee(const JSONRPCRequest& request) { throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n" "Clients should use estimatesmartfee."); } -UniValue estimatesmartfee(const JSONRPCRequest& request) +static UniValue estimatesmartfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -831,7 +831,7 @@ UniValue estimatesmartfee(const JSONRPCRequest& request) return result; } -UniValue estimaterawfee(const JSONRPCRequest& request) +static UniValue estimaterawfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6754407dbd..09812bb980 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -7,12 +7,12 @@ #include <clientversion.h> #include <core_io.h> #include <crypto/ripemd160.h> -#include <init.h> #include <key_io.h> #include <validation.h> #include <httpserver.h> #include <net.h> #include <netbase.h> +#include <outputtype.h> #include <rpc/blockchain.h> #include <rpc/server.h> #include <rpc/util.h> @@ -33,7 +33,7 @@ #include <univalue.h> -UniValue validateaddress(const JSONRPCRequest& request) +static UniValue validateaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -78,7 +78,7 @@ UniValue validateaddress(const JSONRPCRequest& request) ret.pushKV("address", currentAddress); CScript scriptPubKey = GetScriptForDestination(dest); - ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));; + ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); @@ -90,11 +90,11 @@ UniValue validateaddress(const JSONRPCRequest& request) // Needed even with !ENABLE_WALLET, to pass (ignored) pointers around class CWallet; -UniValue createmultisig(const JSONRPCRequest& request) +static UniValue createmultisig(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { - std::string msg = "createmultisig nrequired [\"key\",...]\n" + 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" @@ -104,6 +104,7 @@ UniValue createmultisig(const JSONRPCRequest& request) " \"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" "\nResult:\n" "{\n" @@ -134,18 +135,27 @@ UniValue createmultisig(const JSONRPCRequest& request) } } + // Get the output type + OutputType output_type = OutputType::LEGACY; + if (!request.params[2].isNull()) { + if (!ParseOutputType(request.params[2].get_str(), output_type)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); + } + } + // Construct using pay-to-script-hash: - CScript inner = CreateMultisigRedeemscript(required, pubkeys); - CScriptID innerID(inner); + const CScript inner = CreateMultisigRedeemscript(required, pubkeys); + CBasicKeyStore keystore; + const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type); UniValue result(UniValue::VOBJ); - result.pushKV("address", EncodeDestination(innerID)); + result.pushKV("address", EncodeDestination(dest)); result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); return result; } -UniValue verifymessage(const JSONRPCRequest& request) +static UniValue verifymessage(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( @@ -201,7 +211,7 @@ UniValue verifymessage(const JSONRPCRequest& request) return (pubkey.GetID() == *keyID); } -UniValue signmessagewithprivkey(const JSONRPCRequest& request) +static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 2) throw std::runtime_error( @@ -240,7 +250,7 @@ UniValue signmessagewithprivkey(const JSONRPCRequest& request) return EncodeBase64(vchSig.data(), vchSig.size()); } -UniValue setmocktime(const JSONRPCRequest& request) +static UniValue setmocktime(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -299,7 +309,7 @@ static std::string RPCMallocInfo() } #endif -UniValue getmemoryinfo(const JSONRPCRequest& request) +static UniValue getmemoryinfo(const JSONRPCRequest& request) { /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" @@ -346,21 +356,22 @@ UniValue getmemoryinfo(const JSONRPCRequest& request) } } -uint32_t getCategoryMask(UniValue cats) { +static void EnableOrDisableLogCategories(UniValue cats, bool enable) { cats = cats.get_array(); - uint32_t mask = 0; for (unsigned int i = 0; i < cats.size(); ++i) { - uint32_t flag = 0; std::string cat = cats[i].get_str(); - if (!GetLogCategory(&flag, &cat)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); + + bool success; + if (enable) { + success = g_logger->EnableCategory(cat); + } else { + success = g_logger->DisableCategory(cat); } - if (flag == BCLog::NONE) { - return 0; + + if (!success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); } - mask |= flag; } - return mask; } UniValue logging(const JSONRPCRequest& request) @@ -399,25 +410,25 @@ UniValue logging(const JSONRPCRequest& request) ); } - uint32_t originalLogCategories = logCategories; + uint32_t original_log_categories = g_logger->GetCategoryMask(); if (request.params[0].isArray()) { - logCategories |= getCategoryMask(request.params[0]); + EnableOrDisableLogCategories(request.params[0], true); } - if (request.params[1].isArray()) { - logCategories &= ~getCategoryMask(request.params[1]); + EnableOrDisableLogCategories(request.params[1], false); } + uint32_t updated_log_categories = g_logger->GetCategoryMask(); + uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; // Update libevent logging if BCLog::LIBEVENT has changed. // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false, // in which case we should clear the BCLog::LIBEVENT flag. // Throw an error if the user has explicitly asked to change only the libevent // flag and it failed. - uint32_t changedLogCategories = originalLogCategories ^ logCategories; - if (changedLogCategories & BCLog::LIBEVENT) { - if (!UpdateHTTPServerLogging(logCategories & BCLog::LIBEVENT)) { - logCategories &= ~BCLog::LIBEVENT; - if (changedLogCategories == BCLog::LIBEVENT) { + if (changed_log_categories & BCLog::LIBEVENT) { + if (!UpdateHTTPServerLogging(g_logger->WillLogCategory(BCLog::LIBEVENT))) { + g_logger->DisableCategory(BCLog::LIBEVENT); + if (changed_log_categories == BCLog::LIBEVENT) { throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1."); } } @@ -432,7 +443,7 @@ UniValue logging(const JSONRPCRequest& request) return result; } -UniValue echo(const JSONRPCRequest& request) +static UniValue echo(const JSONRPCRequest& request) { if (request.fHelp) throw std::runtime_error( diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index fee2b765ba..8fa56e9335 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -23,7 +23,7 @@ #include <univalue.h> -UniValue getconnectioncount(const JSONRPCRequest& request) +static UniValue getconnectioncount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -42,7 +42,7 @@ UniValue getconnectioncount(const JSONRPCRequest& request) return (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL); } -UniValue ping(const JSONRPCRequest& request) +static UniValue ping(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -65,7 +65,7 @@ UniValue ping(const JSONRPCRequest& request) return NullUniValue; } -UniValue getpeerinfo(const JSONRPCRequest& request) +static UniValue getpeerinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -190,7 +190,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request) return ret; } -UniValue addnode(const JSONRPCRequest& request) +static UniValue addnode(const JSONRPCRequest& request) { std::string strCommand; if (!request.params[1].isNull()) @@ -237,7 +237,7 @@ UniValue addnode(const JSONRPCRequest& request) return NullUniValue; } -UniValue disconnectnode(const JSONRPCRequest& request) +static UniValue disconnectnode(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) throw std::runtime_error( @@ -280,7 +280,7 @@ UniValue disconnectnode(const JSONRPCRequest& request) return NullUniValue; } -UniValue getaddednodeinfo(const JSONRPCRequest& request) +static UniValue getaddednodeinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -347,7 +347,7 @@ UniValue getaddednodeinfo(const JSONRPCRequest& request) return ret; } -UniValue getnettotals(const JSONRPCRequest& request) +static UniValue getnettotals(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) throw std::runtime_error( @@ -413,7 +413,7 @@ static UniValue GetNetworksInfo() return networks; } -UniValue getnetworkinfo(const JSONRPCRequest& request) +static UniValue getnetworkinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -475,7 +475,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) UniValue localAddresses(UniValue::VARR); { LOCK(cs_mapLocalHost); - for (const std::pair<CNetAddr, LocalServiceInfo> &item : mapLocalHost) + for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost) { UniValue rec(UniValue::VOBJ); rec.pushKV("address", item.first.ToString()); @@ -489,7 +489,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) return obj; } -UniValue setban(const JSONRPCRequest& request) +static UniValue setban(const JSONRPCRequest& request) { std::string strCommand; if (!request.params[1].isNull()) @@ -553,7 +553,7 @@ UniValue setban(const JSONRPCRequest& request) return NullUniValue; } -UniValue listbanned(const JSONRPCRequest& request) +static UniValue listbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -586,7 +586,7 @@ UniValue listbanned(const JSONRPCRequest& request) return bannedAddresses; } -UniValue clearbanned(const JSONRPCRequest& request) +static UniValue clearbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -604,7 +604,7 @@ UniValue clearbanned(const JSONRPCRequest& request) return NullUniValue; } -UniValue setnetworkactive(const JSONRPCRequest& request) +static UniValue setnetworkactive(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 4a265735d2..6954aed252 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -46,7 +46,6 @@ enum RPCErrorCode //! General application defined errors RPC_MISC_ERROR = -1, //!< std::exception thrown in command handling - RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode RPC_TYPE_ERROR = -3, //!< Unexpected type was passed as parameter RPC_INVALID_ADDRESS_OR_KEY = -5, //!< Invalid address or key RPC_OUT_OF_MEMORY = -7, //!< Ran out of memory during operation @@ -88,6 +87,9 @@ enum RPCErrorCode //! Backwards compatible aliases RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME, + + //! Unused reserved codes, kept around for backwards compatibility. Do not reuse. + RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode }; UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 7bdf09812b..bb94e11fea 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -5,9 +5,10 @@ #include <chain.h> #include <coins.h> +#include <compat/byteswap.h> #include <consensus/validation.h> #include <core_io.h> -#include <init.h> +#include <index/txindex.h> #include <keystore.h> #include <validation.h> #include <validationinterface.h> @@ -18,7 +19,6 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/rawtransaction.h> -#include <rpc/safemode.h> #include <rpc/server.h> #include <script/script.h> #include <script/script_error.h> @@ -37,7 +37,7 @@ #include <univalue.h> -void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // @@ -47,6 +47,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags()); if (!hashBlock.IsNull()) { + LOCK(cs_main); + entry.pushKV("blockhash", hashBlock.GetHex()); CBlockIndex* pindex = LookupBlockIndex(hashBlock); if (pindex) { @@ -61,7 +63,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) } } -UniValue getrawtransaction(const JSONRPCRequest& request) +static UniValue getrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( @@ -141,8 +143,6 @@ UniValue getrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") ); - LOCK(cs_main); - bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); CBlockIndex* blockindex = nullptr; @@ -159,6 +159,8 @@ UniValue getrawtransaction(const JSONRPCRequest& request) } if (!request.params[2].isNull()) { + LOCK(cs_main); + uint256 blockhash = ParseHashV(request.params[2], "parameter 3"); blockindex = LookupBlockIndex(blockhash); if (!blockindex) { @@ -167,6 +169,11 @@ UniValue getrawtransaction(const JSONRPCRequest& request) in_active_chain = chainActive.Contains(blockindex); } + bool f_txindex_ready = false; + if (g_txindex && !blockindex) { + f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain(); + } + CTransactionRef tx; uint256 hash_block; if (!GetTransaction(hash, tx, Params().GetConsensus(), hash_block, true, blockindex)) { @@ -176,10 +183,12 @@ UniValue getrawtransaction(const JSONRPCRequest& request) throw JSONRPCError(RPC_MISC_ERROR, "Block not available"); } errmsg = "No such transaction found in the provided block"; + } else if (!g_txindex) { + errmsg = "No such mempool transaction. Use -txindex to enable blockchain transaction queries"; + } else if (!f_txindex_ready) { + errmsg = "No such mempool transaction. Blockchain transactions are still in the process of being indexed"; } else { - errmsg = fTxIndex - ? "No such mempool or blockchain transaction" - : "No such mempool transaction. Use -txindex to enable blockchain transaction queries"; + errmsg = "No such mempool or blockchain transaction"; } throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions."); } @@ -194,7 +203,7 @@ UniValue getrawtransaction(const JSONRPCRequest& request) return result; } -UniValue gettxoutproof(const JSONRPCRequest& request) +static UniValue gettxoutproof(const JSONRPCRequest& request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) throw std::runtime_error( @@ -229,19 +238,18 @@ UniValue gettxoutproof(const JSONRPCRequest& request) oneTxid = hash; } - LOCK(cs_main); - CBlockIndex* pblockindex = nullptr; - uint256 hashBlock; - if (!request.params[1].isNull()) - { + if (!request.params[1].isNull()) { + LOCK(cs_main); hashBlock = uint256S(request.params[1].get_str()); pblockindex = LookupBlockIndex(hashBlock); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } else { + LOCK(cs_main); + // Loop through txids and try to find which block they're in. Exit loop once a block is found. for (const auto& tx : setTxids) { const Coin& coin = AccessByTxid(*pcoinsTip, tx); @@ -252,6 +260,14 @@ UniValue gettxoutproof(const JSONRPCRequest& request) } } + + // Allow txindex to catch up if we need to query it and before we acquire cs_main. + if (g_txindex && !pblockindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + + LOCK(cs_main); + if (pblockindex == nullptr) { CTransactionRef tx; @@ -281,7 +297,7 @@ UniValue gettxoutproof(const JSONRPCRequest& request) return strHex; } -UniValue verifytxoutproof(const JSONRPCRequest& request) +static UniValue verifytxoutproof(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -291,7 +307,7 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) "\nArguments:\n" "1. \"proof\" (string, required) The hex-encoded proof generated by gettxoutproof\n" "\nResult:\n" - "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof is invalid\n" + "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" ); CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); @@ -308,89 +324,39 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !chainActive.Contains(pindex)) { + if (!pindex || !chainActive.Contains(pindex) || pindex->nTx == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } - for (const uint256& hash : vMatch) - res.push_back(hash.GetHex()); + // Check if proof is valid, only add results if so + if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { + for (const uint256& hash : vMatch) { + res.push_back(hash.GetHex()); + } + } + return res; } -UniValue createrawtransaction(const JSONRPCRequest& request) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) { - 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" - "\nResult:\n" - "\"transaction\" (string) hex string of the transaction\n" - - "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") - + 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 - ); - } - - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL - }, true - ); - if (request.params[0].isNull() || request.params[1].isNull()) + if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); - UniValue inputs = request.params[0].get_array(); - const bool outputs_is_obj = request.params[1].isObject(); - UniValue outputs = outputs_is_obj ? - request.params[1].get_obj() : - request.params[1].get_array(); + UniValue inputs = inputs_in.get_array(); + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); CMutableTransaction rawTx; - if (!request.params[2].isNull()) { - int64_t nLockTime = request.params[2].get_int64(); + if (!locktime.isNull()) { + int64_t nLockTime = locktime.get_int64(); if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } - bool rbfOptIn = request.params[3].isTrue(); + bool rbfOptIn = rbf.isTrue(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; @@ -470,14 +436,75 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } } - if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { + if (!rbf.isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } + return rawTx; +} + +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" + "\nResult:\n" + "\"transaction\" (string) hex string of the transaction\n" + + "\nExamples:\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + + 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 + ); + } + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + return EncodeHexTx(rawTx); } -UniValue decoderawtransaction(const JSONRPCRequest& request) +static UniValue decoderawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -553,7 +580,7 @@ UniValue decoderawtransaction(const JSONRPCRequest& request) return result; } -UniValue decodescript(const JSONRPCRequest& request) +static UniValue decodescript(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -597,6 +624,36 @@ UniValue decodescript(const JSONRPCRequest& request) // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, // don't return the address for a P2SH of the P2SH. r.pushKV("p2sh", EncodeDestination(CScriptID(script))); + // P2SH and witness programs cannot be wrapped in P2WSH, if this script + // is a witness program, don't return addresses for a segwit programs. + if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard") { + txnouttype which_type; + std::vector<std::vector<unsigned char>> solutions_data; + Solver(script, which_type, solutions_data); + // Uncompressed pubkeys cannot be used with segwit checksigs. + // If the script contains an uncompressed pubkey, skip encoding of a segwit program. + if ((which_type == TX_PUBKEY) || (which_type == TX_MULTISIG)) { + for (const auto& solution : solutions_data) { + if ((solution.size() != 1) && !CPubKey(solution).IsCompressed()) { + return r; + } + } + } + UniValue sr(UniValue::VOBJ); + CScript segwitScr; + if (which_type == TX_PUBKEY) { + segwitScr = GetScriptForDestination(WitnessV0KeyHash(Hash160(solutions_data[0].begin(), solutions_data[0].end()))); + } else if (which_type == TX_PUBKEYHASH) { + segwitScr = GetScriptForDestination(WitnessV0KeyHash(solutions_data[0])); + } else { + // Scripts that are not fit for P2WPKH are encoded as P2WSH. + // Newer segwit program versions should be considered when then become available. + segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); + } + ScriptPubKeyToUniv(segwitScr, sr, true); + sr.pushKV("p2sh-segwit", EncodeDestination(CScriptID(segwitScr))); + r.pushKV("segwit", sr); + } } return r; @@ -619,7 +676,7 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -UniValue combinerawtransaction(const JSONRPCRequest& request) +static UniValue combinerawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -688,19 +745,17 @@ UniValue combinerawtransaction(const JSONRPCRequest& request) if (coin.IsSpent()) { throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent"); } - const CScript& prevPubKey = coin.out.scriptPubKey; - const CAmount& amount = coin.out.nValue; - SignatureData sigdata; // ... and merge in other signatures: for (const CMutableTransaction& txv : txVariants) { if (txv.vin.size() > i) { - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); + sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out)); } } + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); - UpdateTransaction(mergedTx, i, sigdata); + UpdateInput(txin, sigdata); } return EncodeHexTx(mergedTx); @@ -763,7 +818,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } Coin newcoin; newcoin.out.scriptPubKey = scriptPubKey; - newcoin.out.nValue = 0; + newcoin.out.nValue = MAX_MONEY; if (prevOut.exists("amount")) { newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount")); } @@ -790,23 +845,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } } - int nHashType = SIGHASH_ALL; - if (!hashType.isNull()) { - static std::map<std::string, int> mapSigHashValues = { - {std::string("ALL"), int(SIGHASH_ALL)}, - {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, - {std::string("NONE"), int(SIGHASH_NONE)}, - {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, - {std::string("SINGLE"), int(SIGHASH_SINGLE)}, - {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, - }; - std::string strHashType = hashType.get_str(); - if (mapSigHashValues.count(strHashType)) { - nHashType = mapSigHashValues[strHashType]; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); - } - } + int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); @@ -827,14 +866,18 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival const CScript& prevPubKey = coin.out.scriptPubKey; const CAmount& amount = coin.out.nValue; - SignatureData sigdata; + SignatureData sigdata = DataFromTransaction(mtx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); } - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i)); - UpdateTransaction(mtx, i, sigdata); + UpdateInput(txin, sigdata); + + // amount must be specified for valid segwit signature + if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin.out.ToString())); + } ScriptError serror = SCRIPT_ERR_OK; if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { @@ -858,7 +901,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival return result; } -UniValue signrawtransactionwithkey(const JSONRPCRequest& request) +static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( @@ -940,7 +983,8 @@ UniValue signrawtransactionwithkey(const JSONRPCRequest& request) UniValue signrawtransaction(const JSONRPCRequest& request) { #ifdef ENABLE_WALLET - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); #endif if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) @@ -1036,7 +1080,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) } } -UniValue sendrawtransaction(const JSONRPCRequest& request) +static UniValue sendrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -1059,8 +1103,6 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") ); - ObserveSafeMode(); - std::promise<void> promise; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -1133,7 +1175,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) return hashTx.GetHex(); } -UniValue testmempoolaccept(const JSONRPCRequest& request) +static UniValue testmempoolaccept(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -1168,8 +1210,6 @@ UniValue testmempoolaccept(const JSONRPCRequest& request) ); } - ObserveSafeMode(); - RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL}); if (request.params[0].get_array().size() != 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now"); @@ -1214,6 +1254,553 @@ UniValue testmempoolaccept(const JSONRPCRequest& request) return result; } +static std::string WriteHDKeypath(std::vector<uint32_t>& keypath) +{ + std::string keypath_str = "m"; + for (uint32_t num : keypath) { + keypath_str += "/"; + bool hardened = false; + if (num & 0x80000000) { + hardened = true; + num &= ~0x80000000; + } + + keypath_str += std::to_string(num); + if (hardened) { + keypath_str += "'"; + } + } + return keypath_str; +} + +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" + + "\nResult:\n" + "{\n" + " \"tx\" : { (json object) The decoded network-serialized unsigned transaction.\n" + " ... The layout is the same as the output of decoderawtransaction.\n" + " },\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " \"inputs\" : [ (array of json objects)\n" + " {\n" + " \"non_witness_utxo\" : { (json object, optional) Decoded network transaction for non-witness UTXOs\n" + " ...\n" + " },\n" + " \"witness_utxo\" : { (json object, optional) Transaction output for witness UTXOs\n" + " \"amount\" : x.xxx, (numeric) The value in " + CURRENCY_UNIT + "\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"address\" : \"address\" (string) Bitcoin address if there is one\n" + " }\n" + " },\n" + " \"partial_signatures\" : { (json object, optional)\n" + " \"pubkey\" : \"signature\", (string) The public key and signature that corresponds to it.\n" + " ,...\n" + " }\n" + " \"sighash\" : \"type\", (string, optional) The sighash type to be used\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : { (json object, optional)\n" + " \"pubkey\" : { (json object, optional) The public key with the derivation path as the value.\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " ,...\n" + " }\n" + " \"final_scriptsig\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " }\n" + " \"final_scriptwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"outputs\" : [ (array of json objects)\n" + " {\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : [ (array of json objects, optional)\n" + " {\n" + " \"pubkey\" : \"pubkey\", (string) The public key this path corresponds to\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"fee\" : fee (numeric, optional) The transaction fee paid if all UTXOs slots in the PSBT have been filled.\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("decodepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR}); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + UniValue result(UniValue::VOBJ); + + // Add the decoded tx + UniValue tx_univ(UniValue::VOBJ); + TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); + result.pushKV("tx", tx_univ); + + // Unknown data + UniValue unknowns(UniValue::VOBJ); + for (auto entry : psbtx.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + result.pushKV("unknown", unknowns); + + // inputs + CAmount total_in = 0; + bool have_all_utxos = true; + UniValue inputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const PSBTInput& input = psbtx.inputs[i]; + UniValue in(UniValue::VOBJ); + // UTXOs + if (!input.witness_utxo.IsNull()) { + const CTxOut& txout = input.witness_utxo; + + UniValue out(UniValue::VOBJ); + + out.pushKV("amount", ValueFromAmount(txout.nValue)); + total_in += txout.nValue; + + UniValue o(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, o, true); + out.pushKV("scriptPubKey", o); + in.pushKV("witness_utxo", out); + } else if (input.non_witness_utxo) { + UniValue non_wit(UniValue::VOBJ); + TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false); + in.pushKV("non_witness_utxo", non_wit); + total_in += input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue; + } else { + have_all_utxos = false; + } + + // Partial sigs + if (!input.partial_sigs.empty()) { + UniValue partial_sigs(UniValue::VOBJ); + for (const auto& sig : input.partial_sigs) { + partial_sigs.pushKV(HexStr(sig.second.first), HexStr(sig.second.second)); + } + in.pushKV("partial_signatures", partial_sigs); + } + + // Sighash + if (input.sighash_type > 0) { + in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type)); + } + + // Redeem script and witness script + if (!input.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.redeem_script, r, false); + in.pushKV("redeem_script", r); + } + if (!input.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.witness_script, r, false); + in.pushKV("witness_script", r); + } + + // keypaths + if (!input.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : input.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + in.pushKV("bip32_derivs", keypaths); + } + + // Final scriptSig and scriptwitness + if (!input.final_script_sig.empty()) { + UniValue scriptsig(UniValue::VOBJ); + scriptsig.pushKV("asm", ScriptToAsmStr(input.final_script_sig, true)); + scriptsig.pushKV("hex", HexStr(input.final_script_sig)); + in.pushKV("final_scriptSig", scriptsig); + } + if (!input.final_script_witness.IsNull()) { + UniValue txinwitness(UniValue::VARR); + for (const auto& item : input.final_script_witness.stack) { + txinwitness.push_back(HexStr(item.begin(), item.end())); + } + in.pushKV("final_scriptwitness", txinwitness); + } + + // Unknown data + if (input.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : input.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + in.pushKV("unknown", unknowns); + } + + inputs.push_back(in); + } + result.pushKV("inputs", inputs); + + // outputs + CAmount output_value = 0; + UniValue outputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { + const PSBTOutput& output = psbtx.outputs[i]; + UniValue out(UniValue::VOBJ); + // Redeem script and witness script + if (!output.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.redeem_script, r, false); + out.pushKV("redeem_script", r); + } + if (!output.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.witness_script, r, false); + out.pushKV("witness_script", r); + } + + // keypaths + if (!output.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : output.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + out.pushKV("bip32_derivs", keypaths); + } + + // Unknown data + if (output.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : output.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + out.pushKV("unknown", unknowns); + } + + outputs.push_back(out); + + // Fee calculation + output_value += psbtx.tx->vout[i].nValue; + } + result.pushKV("outputs", outputs); + if (have_all_utxos) { + result.pushKV("fee", ValueFromAmount(total_in - output_value)); + } + + return result; +} + +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" + + "\nResult:\n" + " \"psbt\" (string) The base64-encoded partially signed transaction\n" + "\nExamples:\n" + + HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") + ); + + RPCTypeCheck(request.params, {UniValue::VARR}, true); + + // Unserialize the transactions + std::vector<PartiallySignedTransaction> psbtxs; + UniValue txs = request.params[0].get_array(); + for (unsigned int i = 0; i < txs.size(); ++i) { + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, txs[i].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + psbtxs.push_back(psbtx); + } + + PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one + + // Merge + for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) { + if (*it != merged_psbt) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs do not refer to the same transactions."); + } + merged_psbt.Merge(*it); + } + if (!merged_psbt.IsSane()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Merged PSBT is inconsistent"); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << merged_psbt; + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +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" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction if not extracted\n" + " \"hex\" : \"value\", (string) The hex-encoded network transaction if extracted\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("finalizepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + // Get all of the previous transactions + bool complete = true; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs.at(i); + + SignatureData sigdata; + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, sigdata, i, 1); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()); + if (complete && extract) { + CMutableTransaction mtx(*psbtx.tx); + for (unsigned int i = 0; i < mtx.vin.size(); ++i) { + mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig; + mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; + } + ssTx << mtx; + result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + } else { + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + } + result.push_back(Pair("complete", complete)); + + return result; +} + +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" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + + HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], 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()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +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" + " 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 wil be tried. Only has an effect if\n" + " permitsigdata is true.\n" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + "\nConvert the transaction to a PSBT\n" + + HelpExampleCli("converttopsbt", "\"rawtransaction\"") + ); + + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); + + // parse hex string from parameter + CMutableTransaction tx; + bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); + bool witness_specified = !request.params[2].isNull(); + bool iswitness = witness_specified ? request.params[2].get_bool() : false; + bool try_witness = permitsigdata ? (witness_specified ? iswitness : true) : false; + bool try_no_witness = permitsigdata ? (witness_specified ? !iswitness : true) : true; + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + // 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()))) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); + } + input.scriptSig.clear(); + input.scriptWitness.SetNull(); + } + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = tx; + for (unsigned int i = 0; i < tx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < tx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -1226,6 +1813,11 @@ static const CRPCCommand commands[] = { "rawtransactions", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ { "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} }, + { "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} }, + { "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} }, + { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, + { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} }, + { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} }, { "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} }, diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index ec9d1f2cf0..52dccc90e8 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -12,4 +12,7 @@ class UniValue; /** Sign a transaction with the given keystore and previous transactions */ UniValue SignTransaction(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); + #endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/rpc/safemode.cpp b/src/rpc/safemode.cpp deleted file mode 100644 index 9f3a9d30b8..0000000000 --- a/src/rpc/safemode.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include <rpc/safemode.h> - -#include <rpc/protocol.h> -#include <util.h> -#include <warnings.h> - -void ObserveSafeMode() -{ - std::string warning = GetWarnings("rpc"); - if (warning != "" && !gArgs.GetBoolArg("-disablesafemode", DEFAULT_DISABLE_SAFEMODE)) { - throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, std::string("Safe mode: ") + warning); - } -} - diff --git a/src/rpc/safemode.h b/src/rpc/safemode.h deleted file mode 100644 index 8466d6b2f9..0000000000 --- a/src/rpc/safemode.h +++ /dev/null @@ -1,12 +0,0 @@ -// 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. - -#ifndef BITCOIN_RPC_SAFEMODE_H -#define BITCOIN_RPC_SAFEMODE_H - -static const bool DEFAULT_DISABLE_SAFEMODE = true; - -void ObserveSafeMode(); - -#endif // BITCOIN_RPC_SAFEMODE_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index c7c3b1f0d3..b420e9d8b3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,14 +1,14 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. #include <rpc/server.h> #include <fs.h> -#include <init.h> #include <key_io.h> #include <random.h> +#include <shutdown.h> #include <sync.h> #include <ui_interface.h> #include <util.h> @@ -23,10 +23,10 @@ #include <memory> // for unique_ptr #include <unordered_map> -static bool fRPCRunning = false; -static bool fRPCInWarmup = true; -static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; +static bool fRPCRunning = false; +static bool fRPCInWarmup GUARDED_BY(cs_rpcWarmup) = true; +static std::string rpcWarmupStatus GUARDED_BY(cs_rpcWarmup) = "RPC server started"; /* Timer-creating functions */ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ @@ -239,7 +239,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest) return "Bitcoin server stopping"; } -UniValue uptime(const JSONRPCRequest& jsonRequest) +static UniValue uptime(const JSONRPCRequest& jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( diff --git a/src/scheduler.h b/src/scheduler.h index a41838a295..7169dceee5 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -95,8 +95,8 @@ private: CScheduler *m_pscheduler; CCriticalSection m_cs_callbacks_pending; - std::list<std::function<void (void)>> m_callbacks_pending; - bool m_are_callbacks_running = false; + std::list<std::function<void (void)>> m_callbacks_pending GUARDED_BY(m_cs_callbacks_pending); + bool m_are_callbacks_running GUARDED_BY(m_cs_callbacks_pending) = false; void MaybeScheduleProcessQueue(); void ProcessQueue(); diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index 8cc44b675f..e2370c5e50 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -81,7 +81,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err) { if (!verify_flags(flags)) { - return bitcoinconsensus_ERR_INVALID_FLAGS; + return set_error(err, bitcoinconsensus_ERR_INVALID_FLAGS); } try { TxInputStream stream(SER_NETWORK, PROTOCOL_VERSION, txTo, txToLen); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index e0d193fa38..4b982d647d 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -336,6 +336,10 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& opcode == OP_RSHIFT) return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); // Disabled opcodes. + // With SCRIPT_VERIFY_CONST_SCRIPTCODE, OP_CODESEPARATOR in non-segwit script is rejected even in an unexecuted branch + if (opcode == OP_CODESEPARATOR && sigversion == SigVersion::BASE && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_OP_CODESEPARATOR); + if (fExec && 0 <= opcode && opcode <= OP_PUSHDATA4) { if (fRequireMinimal && !CheckMinimalPush(vchPushValue, opcode)) { return set_error(serror, SCRIPT_ERR_MINIMALDATA); @@ -899,6 +903,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& case OP_CODESEPARATOR: { + // If SCRIPT_VERIFY_CONST_SCRIPTCODE flag is set, use of OP_CODESEPARATOR is rejected in pre-segwit + // script, even in an unexecuted branch (this is checked above the opcode case statement). + // Hash starts after the code separator pbegincodehash = pc; } @@ -919,7 +926,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& // Drop the signature in pre-segwit scripts but not segwit scripts if (sigversion == SigVersion::BASE) { - FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript(vchSig)); + if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } if (!CheckSignatureEncoding(vchSig, flags, serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) { @@ -983,7 +992,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& { valtype& vchSig = stacktop(-isig-k); if (sigversion == SigVersion::BASE) { - FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript(vchSig)); + if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } } @@ -1078,9 +1089,11 @@ namespace { * Wrapper that serializes like CTransaction, but with the modifications * required for the signature hash done in-place */ -class CTransactionSignatureSerializer { +template <class T> +class CTransactionSignatureSerializer +{ private: - const CTransaction& txTo; //!< reference to the spending transaction (the one being serialized) + const T& txTo; //!< reference to the spending transaction (the one being serialized) const CScript& scriptCode; //!< output script being consumed const unsigned int nIn; //!< input index of txTo being signed const bool fAnyoneCanPay; //!< whether the hashtype has the SIGHASH_ANYONECANPAY flag set @@ -1088,7 +1101,7 @@ private: const bool fHashNone; //!< whether the hashtype is SIGHASH_NONE public: - CTransactionSignatureSerializer(const CTransaction &txToIn, const CScript &scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : + CTransactionSignatureSerializer(const T& txToIn, const CScript& scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : txTo(txToIn), scriptCode(scriptCodeIn), nIn(nInIn), fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)), fHashSingle((nHashTypeIn & 0x1f) == SIGHASH_SINGLE), @@ -1169,7 +1182,9 @@ public: } }; -uint256 GetPrevoutHash(const CTransaction& txTo) { +template <class T> +uint256 GetPrevoutHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txin : txTo.vin) { ss << txin.prevout; @@ -1177,7 +1192,9 @@ uint256 GetPrevoutHash(const CTransaction& txTo) { return ss.GetHash(); } -uint256 GetSequenceHash(const CTransaction& txTo) { +template <class T> +uint256 GetSequenceHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txin : txTo.vin) { ss << txin.nSequence; @@ -1185,7 +1202,9 @@ uint256 GetSequenceHash(const CTransaction& txTo) { return ss.GetHash(); } -uint256 GetOutputsHash(const CTransaction& txTo) { +template <class T> +uint256 GetOutputsHash(const T& txTo) +{ CHashWriter ss(SER_GETHASH, 0); for (const auto& txout : txTo.vout) { ss << txout; @@ -1195,7 +1214,8 @@ uint256 GetOutputsHash(const CTransaction& txTo) { } // namespace -PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) +template <class T> +PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) { // Cache is calculated only for transactions with witness if (txTo.HasWitness()) { @@ -1206,7 +1226,12 @@ PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) } } -uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) +// explicit instantiation +template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); +template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); + +template <class T> +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) { assert(nIn < txTo.vin.size()); @@ -1267,7 +1292,7 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig } // Wrapper to serialize only the necessary parts of the transaction being signed - CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType); + CTransactionSignatureSerializer<T> txTmp(txTo, scriptCode, nIn, nHashType); // Serialize and hash CHashWriter ss(SER_GETHASH, 0); @@ -1275,12 +1300,14 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig return ss.GetHash(); } -bool TransactionSignatureChecker::VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const +template <class T> +bool GenericTransactionSignatureChecker<T>::VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const { return pubkey.Verify(sighash, vchSig); } -bool TransactionSignatureChecker::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +template <class T> +bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { CPubKey pubkey(vchPubKey); if (!pubkey.IsValid()) @@ -1301,7 +1328,8 @@ bool TransactionSignatureChecker::CheckSig(const std::vector<unsigned char>& vch return true; } -bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const +template <class T> +bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLockTime) const { // There are two kinds of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether @@ -1337,7 +1365,8 @@ bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) con return true; } -bool TransactionSignatureChecker::CheckSequence(const CScriptNum& nSequence) const +template <class T> +bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSequence) const { // Relative lock times are supported by comparing the passed // in operand to the sequence number of the input. @@ -1383,6 +1412,10 @@ bool TransactionSignatureChecker::CheckSequence(const CScriptNum& nSequence) con return true; } +// explicit instantiation +template class GenericTransactionSignatureChecker<CTransaction>; +template class GenericTransactionSignatureChecker<CMutableTransaction>; + static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror) { std::vector<std::vector<unsigned char> > stack; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 50c747900a..2d21aa81df 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -111,6 +111,10 @@ enum // Public keys in segregated witness scripts must be compressed // SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1U << 15), + + // Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts + // + SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16), }; bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror); @@ -120,7 +124,8 @@ struct PrecomputedTransactionData uint256 hashPrevouts, hashSequence, hashOutputs; bool ready = false; - explicit PrecomputedTransactionData(const CTransaction& tx); + template <class T> + explicit PrecomputedTransactionData(const T& tx); }; enum class SigVersion @@ -133,7 +138,8 @@ enum class SigVersion static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32; static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20; -uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); +template <class T> +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); class BaseSignatureChecker { @@ -156,10 +162,11 @@ public: virtual ~BaseSignatureChecker() {} }; -class TransactionSignatureChecker : public BaseSignatureChecker +template <class T> +class GenericTransactionSignatureChecker : public BaseSignatureChecker { private: - const CTransaction* txTo; + const T* txTo; unsigned int nIn; const CAmount amount; const PrecomputedTransactionData* txdata; @@ -168,21 +175,15 @@ protected: virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; public: - TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} - TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; }; -class MutableTransactionSignatureChecker : public TransactionSignatureChecker -{ -private: - const CTransaction txTo; - -public: - MutableTransactionSignatureChecker(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : TransactionSignatureChecker(&txTo, nInIn, amountIn), txTo(*txToIn) {} -}; +using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>; +using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CMutableTransaction>; bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr); bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr); diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index b826bcfe20..8c26866483 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -13,44 +13,55 @@ typedef std::vector<unsigned char> valtype; -static bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore) +namespace { + +/** + * This is an enum that tracks the execution context of a script, similar to + * SigVersion in script/interpreter. It is separate however because we want to + * distinguish between top-level scriptPubKey execution and P2SH redeemScript + * execution (a distinction that has no impact on consensus rules). + */ +enum class IsMineSigVersion { - for (const valtype& pubkey : pubkeys) { - CKeyID keyID = CPubKey(pubkey).GetID(); - if (!keystore.HaveKey(keyID)) return false; - } - return true; -} + TOP = 0, //! scriptPubKey execution + P2SH = 1, //! P2SH redeemScript + WITNESS_V0 = 2 //! P2WSH witness script execution +}; -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion sigversion) +/** + * This is an internal representation of isminetype + invalidity. + * Its order is significant, as we return the max of all explored + * possibilities. + */ +enum class IsMineResult { - bool isInvalid = false; - return IsMine(keystore, scriptPubKey, isInvalid, sigversion); -} + 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) +}; -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion sigversion) +bool PermitsUncompressed(IsMineSigVersion sigversion) { - bool isInvalid = false; - return IsMine(keystore, dest, isInvalid, sigversion); + return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; } -isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest, bool& isInvalid, SigVersion sigversion) +bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore) { - CScript script = GetScriptForDestination(dest); - return IsMine(keystore, script, isInvalid, sigversion); + for (const valtype& pubkey : pubkeys) { + CKeyID keyID = CPubKey(pubkey).GetID(); + if (!keystore.HaveKey(keyID)) return false; + } + return true; } -isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& isInvalid, SigVersion sigversion) +IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) { - isInvalid = false; + IsMineResult ret = IsMineResult::NO; std::vector<valtype> vSolutions; txnouttype whichType; - if (!Solver(scriptPubKey, whichType, vSolutions)) { - if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_UNSOLVABLE; - return ISMINE_NO; - } + Solver(scriptPubKey, whichType, vSolutions); CKeyID keyID; switch (whichType) @@ -61,52 +72,60 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& break; case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); - if (sigversion != SigVersion::BASE && vSolutions[0].size() != 33) { - isInvalid = true; - return ISMINE_NO; + if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { + return IsMineResult::INVALID; + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); } - if (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; break; case TX_WITNESS_V0_KEYHASH: { - if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WPKH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { // We do not support bare witness outputs unless the P2SH version of it would be // acceptable as well. This protects against matching before segwit activates. // This also applies to the P2WSH case. break; } - isminetype ret = ::IsMine(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, SigVersion::WITNESS_V0); - if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) - return ret; + ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); break; } case TX_PUBKEYHASH: keyID = CKeyID(uint160(vSolutions[0])); - if (sigversion != SigVersion::BASE) { + if (!PermitsUncompressed(sigversion)) { CPubKey pubkey; if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { - isInvalid = true; - return ISMINE_NO; + return IsMineResult::INVALID; } } - if (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } break; case TX_SCRIPTHASH: { + if (sigversion != IsMineSigVersion::TOP) { + // P2SH inside P2WSH or P2SH is invalid. + return IsMineResult::INVALID; + } CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - isminetype ret = IsMine(keystore, subscript, isInvalid); - if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) - return ret; + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); } break; } case TX_WITNESS_V0_SCRIPTHASH: { - if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + if (sigversion == IsMineSigVersion::WITNESS_V0) { + // P2WSH inside P2WSH is invalid. + return IsMineResult::INVALID; + } + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { break; } uint160 hash; @@ -114,39 +133,62 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& CScriptID scriptID = CScriptID(hash); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - isminetype ret = IsMine(keystore, subscript, isInvalid, SigVersion::WITNESS_V0); - if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) - return ret; + ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0)); } break; } case TX_MULTISIG: { + // Never treat bare multisig outputs as ours (they can still be made watchonly-though) + if (sigversion == IsMineSigVersion::TOP) { + break; + } + // Only consider transactions "mine" if we own ALL the // keys involved. Multi-signature transactions that are // partially owned (somebody else has a key that can spend // them) enable spend-out-from-under-you attacks, especially // in shared-wallet situations. std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); - if (sigversion != SigVersion::BASE) { + if (!PermitsUncompressed(sigversion)) { for (size_t i = 0; i < keys.size(); i++) { if (keys[i].size() != 33) { - isInvalid = true; - return ISMINE_NO; + return IsMineResult::INVALID; } } } - if (HaveKeys(keys, keystore)) - return ISMINE_SPENDABLE; + if (HaveKeys(keys, keystore)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } break; } } - if (keystore.HaveWatchOnly(scriptPubKey)) { - // TODO: This could be optimized some by doing some work after the above solver - SignatureData sigs; - return ProduceSignature(keystore, DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigs) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE; + if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { + ret = std::max(ret, IsMineResult::WATCH_ONLY); } - return ISMINE_NO; + return ret; +} + +} // namespace + +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) +{ + switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) { + case IsMineResult::INVALID: + case IsMineResult::NO: + return ISMINE_NO; + case IsMineResult::WATCH_ONLY: + return ISMINE_WATCH_ONLY; + case IsMineResult::SPENDABLE: + return ISMINE_SPENDABLE; + } + assert(false); +} + +isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest) +{ + CScript script = GetScriptForDestination(dest); + return IsMine(keystore, script); } diff --git a/src/script/ismine.h b/src/script/ismine.h index f93a66e35a..4246da49fe 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -17,25 +17,14 @@ class CScript; enum isminetype { ISMINE_NO = 0, - //! Indicates that we don't know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_UNSOLVABLE = 1, - //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys - ISMINE_WATCH_SOLVABLE = 2, - ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE, - ISMINE_SPENDABLE = 4, + ISMINE_WATCH_ONLY = 1, + ISMINE_SPENDABLE = 2, ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE }; /** used for bitflags of isminetype */ typedef uint8_t isminefilter; -/* isInvalid becomes true when the script is found invalid by consensus or policy. This will terminate the recursion - * and return ISMINE_NO immediately, as an invalid script should never be considered as "mine". This is needed as - * different SIGVERSION may have different network rules. Currently the only use of isInvalid is indicate uncompressed - * keys in SigVersion::WITNESS_V0 script, but could also be used in similar cases in the future - */ -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, bool& isInvalid, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion = SigVersion::BASE); +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); +isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); #endif // BITCOIN_SCRIPT_ISMINE_H diff --git a/src/script/script.cpp b/src/script/script.cpp index 7f25d915a8..c84c7b8ec1 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -141,11 +141,6 @@ const char* GetOpName(opcodetype opcode) case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; - // Note: - // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum - // as kind of implementation hack, they are *NOT* real opcodes. If found in real - // Script, just let the default: case deal with them. - default: return "OP_UNKNOWN"; } diff --git a/src/script/script.h b/src/script/script.h index d8b7c06013..a4f377dd94 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -181,13 +181,6 @@ enum opcodetype OP_NOP9 = 0xb8, OP_NOP10 = 0xb9, - - // template matching params - OP_SMALLINTEGER = 0xfa, - OP_PUBKEYS = 0xfb, - OP_PUBKEYHASH = 0xfd, - OP_PUBKEY = 0xfe, - OP_INVALIDOPCODE = 0xff, }; diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index dbceb1f740..ceda740580 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -89,6 +89,10 @@ const char* ScriptErrorString(const ScriptError serror) return "Witness provided for non-witness script"; case SCRIPT_ERR_WITNESS_PUBKEYTYPE: return "Using non-compressed keys in segwit"; + case SCRIPT_ERR_OP_CODESEPARATOR: + return "Using OP_CODESEPARATOR in non-witness script"; + case SCRIPT_ERR_SIG_FINDANDDELETE: + return "Signature is found in scriptCode"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index 3200e94707..6982a087f4 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -64,6 +64,10 @@ typedef enum ScriptError_t SCRIPT_ERR_WITNESS_UNEXPECTED, SCRIPT_ERR_WITNESS_PUBKEYTYPE, + /* Constant scriptCode */ + SCRIPT_ERR_OP_CODESEPARATOR, + SCRIPT_ERR_SIG_FINDANDDELETE, + SCRIPT_ERR_ERROR_COUNT } ScriptError; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index ac35f17f3e..f1ac1f411a 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -14,9 +14,9 @@ typedef std::vector<unsigned char> valtype; -TransactionSignatureCreator::TransactionSignatureCreator(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} -bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const +bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { CKey key; if (!provider.GetKey(address, key)) @@ -33,27 +33,58 @@ bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std return true; } -static bool Sign1(const SigningProvider& provider, const CKeyID& address, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, SigVersion sigversion) +static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script) { - std::vector<unsigned char> vchSig; - if (!creator.CreateSig(provider, vchSig, address, scriptCode, sigversion)) - return false; - ret.push_back(vchSig); - return true; + if (provider.GetCScript(scriptid, script)) { + return true; + } + // Look for scripts in SignatureData + if (CScriptID(sigdata.redeem_script) == scriptid) { + script = sigdata.redeem_script; + return true; + } else if (CScriptID(sigdata.witness_script) == scriptid) { + script = sigdata.witness_script; + return true; + } + return false; } -static bool SignN(const SigningProvider& provider, const std::vector<valtype>& multisigdata, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, SigVersion sigversion) +static bool GetPubKey(const SigningProvider& provider, SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) { - int nSigned = 0; - int nRequired = multisigdata.front()[0]; - for (unsigned int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++) - { - const valtype& pubkey = multisigdata[i]; - CKeyID keyID = CPubKey(pubkey).GetID(); - if (Sign1(provider, keyID, creator, scriptCode, ret, sigversion)) - ++nSigned; + if (provider.GetPubKey(address, pubkey)) { + sigdata.misc_pubkeys.emplace(pubkey.GetID(), pubkey); + return true; + } + // Look for pubkey in all partial sigs + const auto it = sigdata.signatures.find(address); + if (it != sigdata.signatures.end()) { + pubkey = it->second.first; + return true; + } + // Look for pubkey in pubkey list + const auto& pk_it = sigdata.misc_pubkeys.find(address); + if (pk_it != sigdata.misc_pubkeys.end()) { + pubkey = pk_it->second; + return true; } - return nSigned==nRequired; + return false; +} + +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) +{ + 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); + if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion)) { + auto i = sigdata.signatures.emplace(keyid, SigPair(pubkey, sig_out)); + assert(i.second); + return true; + } + return false; } /** @@ -63,17 +94,17 @@ static bool SignN(const SigningProvider& provider, const std::vector<valtype>& m * Returns false if scriptPubKey could not be completely satisfied. */ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, - std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion) + std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion, SignatureData& sigdata) { CScript scriptRet; uint160 h160; ret.clear(); + std::vector<unsigned char> sig; std::vector<valtype> vSolutions; if (!Solver(scriptPubKey, whichTypeRet, vSolutions)) return false; - CKeyID keyID; switch (whichTypeRet) { case TX_NONSTANDARD: @@ -81,37 +112,47 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TX_WITNESS_UNKNOWN: return false; case TX_PUBKEY: - keyID = CPubKey(vSolutions[0]).GetID(); - return Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion); - case TX_PUBKEYHASH: - keyID = CKeyID(uint160(vSolutions[0])); - if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion)) - return false; - else - { - CPubKey vch; - provider.GetPubKey(keyID, vch); - ret.push_back(ToByteVector(vch)); - } + if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), 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); + ret.push_back(ToByteVector(pubkey)); return true; + } case TX_SCRIPTHASH: - if (provider.GetCScript(uint160(vSolutions[0]), scriptRet)) { + if (GetCScript(provider, sigdata, uint160(vSolutions[0]), scriptRet)) { ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end())); return true; } return false; - case TX_MULTISIG: + case TX_MULTISIG: { + size_t required = vSolutions.front()[0]; ret.push_back(valtype()); // workaround CHECKMULTISIG bug - return (SignN(provider, vSolutions, creator, scriptPubKey, ret, sigversion)); - + 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)) { + ret.push_back(std::move(sig)); + } + } + bool ok = ret.size() == required + 1; + for (size_t i = 0; i + ret.size() < required + 1; ++i) { + ret.push_back(valtype()); + } + return ok; + } case TX_WITNESS_V0_KEYHASH: ret.push_back(vSolutions[0]); return true; case TX_WITNESS_V0_SCRIPTHASH: CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin()); - if (provider.GetCScript(h160, scriptRet)) { + if (GetCScript(provider, sigdata, h160, scriptRet)) { ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end())); return true; } @@ -139,9 +180,11 @@ static CScript PushAll(const std::vector<valtype>& values) bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata) { + if (sigdata.complete) return true; + std::vector<valtype> result; txnouttype whichType; - bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE); + bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); bool P2SH = false; CScript subscript; sigdata.scriptWitness.stack.clear(); @@ -152,7 +195,8 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato // the final scriptSig is the signatures from that // and then the serialized subscript: subscript = CScript(result[0].begin(), result[0].end()); - solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE) && whichType != TX_SCRIPTHASH; + sigdata.redeem_script = subscript; + solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE, sigdata) && whichType != TX_SCRIPTHASH; P2SH = true; } @@ -161,18 +205,23 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato CScript witnessscript; witnessscript << OP_DUP << OP_HASH160 << ToByteVector(result[0]) << OP_EQUALVERIFY << OP_CHECKSIG; txnouttype subType; - solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0); + solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata); sigdata.scriptWitness.stack = result; + sigdata.witness = true; result.clear(); } else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH) { CScript witnessscript(result[0].begin(), result[0].end()); + sigdata.witness_script = witnessscript; txnouttype subType; - solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH; + solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH; result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end())); sigdata.scriptWitness.stack = result; + sigdata.witness = true; result.clear(); + } else if (solved && whichType == TX_WITNESS_UNKNOWN) { + sigdata.witness = true; } if (P2SH) { @@ -181,106 +230,55 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptSig = PushAll(result); // Test solution - return solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker()); + sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker()); + return sigdata.complete; } -SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn) +bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash) { - SignatureData data; - assert(tx.vin.size() > nIn); - data.scriptSig = tx.vin[nIn].scriptSig; - data.scriptWitness = tx.vin[nIn].scriptWitness; - return data; -} - -void UpdateInput(CTxIn& input, const SignatureData& data) -{ - input.scriptSig = data.scriptSig; - input.scriptWitness = data.scriptWitness; -} - -void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data) -{ - assert(tx.vin.size() > nIn); - UpdateInput(tx.vin[nIn], data); -} + // 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 true; + } -bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) -{ - assert(nIn < txTo.vin.size()); + // Fill SignatureData with input info + input.FillSignatureData(sigdata); - CTransaction txToConst(txTo); - TransactionSignatureCreator creator(&txToConst, nIn, amount, nHashType); + // Get UTXO + CTxOut utxo; + if (input.non_witness_utxo) { + utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n]; + } else if (!input.witness_utxo.IsNull()) { + utxo = input.witness_utxo; + } else { + return false; + } - SignatureData sigdata; - bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); - UpdateTransaction(txTo, nIn, sigdata); - return ret; + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + bool sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); + input.FromSignatureData(sigdata); + return sig_complete; } -bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) +class SignatureExtractorChecker final : public BaseSignatureChecker { - assert(nIn < txTo.vin.size()); - CTxIn& txin = txTo.vin[nIn]; - assert(txin.prevout.n < txFrom.vout.size()); - const CTxOut& txout = txFrom.vout[txin.prevout.n]; +private: + SignatureData& sigdata; + BaseSignatureChecker& checker; - return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType); -} +public: + SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {} + bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; +}; -static std::vector<valtype> CombineMultisig(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const std::vector<valtype>& vSolutions, - const std::vector<valtype>& sigs1, const std::vector<valtype>& sigs2, SigVersion sigversion) +bool SignatureExtractorChecker::CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { - // Combine all the signatures we've got: - std::set<valtype> allsigs; - for (const valtype& v : sigs1) - { - if (!v.empty()) - allsigs.insert(v); - } - for (const valtype& v : sigs2) - { - if (!v.empty()) - allsigs.insert(v); - } - - // Build a map of pubkey -> signature by matching sigs to pubkeys: - assert(vSolutions.size() > 1); - unsigned int nSigsRequired = vSolutions.front()[0]; - unsigned int nPubKeys = vSolutions.size()-2; - std::map<valtype, valtype> sigs; - for (const valtype& sig : allsigs) - { - for (unsigned int i = 0; i < nPubKeys; i++) - { - const valtype& pubkey = vSolutions[i+1]; - if (sigs.count(pubkey)) - continue; // Already got a sig for this pubkey - - if (checker.CheckSig(sig, pubkey, scriptPubKey, sigversion)) - { - sigs[pubkey] = sig; - break; - } - } - } - // Now build a merged CScript: - unsigned int nSigsHave = 0; - std::vector<valtype> result; result.push_back(valtype()); // pop-one-too-many workaround - for (unsigned int i = 0; i < nPubKeys && nSigsHave < nSigsRequired; i++) - { - if (sigs.count(vSolutions[i+1])) - { - result.push_back(sigs[vSolutions[i+1]]); - ++nSigsHave; - } + if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) { + CPubKey pubkey(vchPubKey); + sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig)); + return true; } - // Fill any missing with OP_0: - for (unsigned int i = nSigsHave; i < nSigsRequired; i++) - result.push_back(valtype()); - - return result; + return false; } namespace @@ -305,89 +303,115 @@ struct Stacks }; } -static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const txnouttype txType, const std::vector<valtype>& vSolutions, - Stacks sigs1, Stacks sigs2, SigVersion sigversion) +// Extracts signatures and scripts from incomplete scriptSigs. Please do not extend this, use PSBT instead +SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout) { - switch (txType) - { - case TX_NONSTANDARD: - case TX_NULL_DATA: - case TX_WITNESS_UNKNOWN: - // Don't know anything about this, assume bigger one is correct: - if (sigs1.script.size() >= sigs2.script.size()) - return sigs1; - return sigs2; - case TX_PUBKEY: - case TX_PUBKEYHASH: - // Signatures are bigger than placeholders or empty scripts: - if (sigs1.script.empty() || sigs1.script[0].empty()) - return sigs2; - return sigs1; - case TX_WITNESS_V0_KEYHASH: - // Signatures are bigger than placeholders or empty scripts: - if (sigs1.witness.empty() || sigs1.witness[0].empty()) - return sigs2; - return sigs1; - case TX_SCRIPTHASH: - if (sigs1.script.empty() || sigs1.script.back().empty()) - return sigs2; - else if (sigs2.script.empty() || sigs2.script.back().empty()) - return sigs1; - else - { - // Recur to combine: - valtype spk = sigs1.script.back(); - CScript pubKey2(spk.begin(), spk.end()); - - txnouttype txType2; - std::vector<std::vector<unsigned char> > vSolutions2; - Solver(pubKey2, txType2, vSolutions2); - sigs1.script.pop_back(); - sigs2.script.pop_back(); - Stacks result = CombineSignatures(pubKey2, checker, txType2, vSolutions2, sigs1, sigs2, sigversion); - result.script.push_back(spk); - return result; - } - case TX_MULTISIG: - return Stacks(CombineMultisig(scriptPubKey, checker, vSolutions, sigs1.script, sigs2.script, sigversion)); - case TX_WITNESS_V0_SCRIPTHASH: - if (sigs1.witness.empty() || sigs1.witness.back().empty()) - return sigs2; - else if (sigs2.witness.empty() || sigs2.witness.back().empty()) - return sigs1; - else - { - // Recur to combine: - CScript pubKey2(sigs1.witness.back().begin(), sigs1.witness.back().end()); - txnouttype txType2; - std::vector<valtype> vSolutions2; - Solver(pubKey2, txType2, vSolutions2); - sigs1.witness.pop_back(); - sigs1.script = sigs1.witness; - sigs1.witness.clear(); - sigs2.witness.pop_back(); - sigs2.script = sigs2.witness; - sigs2.witness.clear(); - Stacks result = CombineSignatures(pubKey2, checker, txType2, vSolutions2, sigs1, sigs2, SigVersion::WITNESS_V0); - result.witness = result.script; - result.script.clear(); - result.witness.push_back(valtype(pubKey2.begin(), pubKey2.end())); - return result; + SignatureData data; + assert(tx.vin.size() > nIn); + data.scriptSig = tx.vin[nIn].scriptSig; + data.scriptWitness = tx.vin[nIn].scriptWitness; + Stacks stack(data); + + // Get signatures + MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue); + SignatureExtractorChecker extractor_checker(data, tx_checker); + if (VerifyScript(data.scriptSig, txout.scriptPubKey, &data.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) { + data.complete = true; + return data; + } + + // Get scripts + txnouttype script_type; + std::vector<std::vector<unsigned char>> solutions; + Solver(txout.scriptPubKey, script_type, solutions); + SigVersion sigversion = SigVersion::BASE; + CScript next_script = txout.scriptPubKey; + + if (script_type == TX_SCRIPTHASH && !stack.script.empty() && !stack.script.back().empty()) { + // Get the redeemScript + CScript redeem_script(stack.script.back().begin(), stack.script.back().end()); + data.redeem_script = redeem_script; + next_script = std::move(redeem_script); + + // Get redeemScript type + Solver(next_script, script_type, solutions); + stack.script.pop_back(); + } + if (script_type == TX_WITNESS_V0_SCRIPTHASH && !stack.witness.empty() && !stack.witness.back().empty()) { + // Get the witnessScript + CScript witness_script(stack.witness.back().begin(), stack.witness.back().end()); + data.witness_script = witness_script; + next_script = std::move(witness_script); + + // Get witnessScript type + Solver(next_script, script_type, solutions); + stack.witness.pop_back(); + stack.script = std::move(stack.witness); + stack.witness.clear(); + sigversion = SigVersion::WITNESS_V0; + } + if (script_type == TX_MULTISIG && !stack.script.empty()) { + // Build a map of pubkey -> signature by matching sigs to pubkeys: + assert(solutions.size() > 1); + unsigned int num_pubkeys = solutions.size()-2; + unsigned int last_success_key = 0; + for (const valtype& sig : stack.script) { + for (unsigned int i = last_success_key; i < num_pubkeys; ++i) { + const valtype& pubkey = solutions[i+1]; + // We either have a signature for this pubkey, or we have found a signature and it is valid + if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion)) { + last_success_key = i + 1; + break; + } + } } - default: - return Stacks(); } + + return data; +} + +void UpdateInput(CTxIn& input, const SignatureData& data) +{ + input.scriptSig = data.scriptSig; + input.scriptWitness = data.scriptWitness; +} + +void SignatureData::MergeSignatureData(SignatureData sigdata) +{ + if (complete) return; + if (sigdata.complete) { + *this = std::move(sigdata); + return; + } + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + if (witness_script.empty() && !sigdata.witness_script.empty()) { + witness_script = sigdata.witness_script; + } + signatures.insert(std::make_move_iterator(sigdata.signatures.begin()), std::make_move_iterator(sigdata.signatures.end())); } -SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, - const SignatureData& scriptSig1, const SignatureData& scriptSig2) +bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType) { - txnouttype txType; - std::vector<std::vector<unsigned char> > vSolutions; - Solver(scriptPubKey, txType, vSolutions); + assert(nIn < txTo.vin.size()); - return CombineSignatures(scriptPubKey, checker, txType, vSolutions, Stacks(scriptSig1), Stacks(scriptSig2), SigVersion::BASE).Output(); + MutableTransactionSignatureCreator creator(&txTo, nIn, amount, nHashType); + + SignatureData sigdata; + bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); + UpdateInput(txTo.vin.at(nIn), sigdata); + return ret; +} + +bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) +{ + assert(nIn < txTo.vin.size()); + CTxIn& txin = txTo.vin[nIn]; + assert(txin.prevout.n < txFrom.vout.size()); + const CTxOut& txout = txFrom.vout[txin.prevout.n]; + + return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType); } namespace { @@ -423,6 +447,7 @@ public: } const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(); +const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); bool IsSolvable(const SigningProvider& provider, const CScript& script) { @@ -441,3 +466,161 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) } return false; } + + +bool PartiallySignedTransaction::IsNull() const +{ + return !tx && inputs.empty() && outputs.empty() && unknown.empty(); +} + +void PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) +{ + for (unsigned int i = 0; i < inputs.size(); ++i) { + inputs[i].Merge(psbt.inputs[i]); + } + for (unsigned int i = 0; i < outputs.size(); ++i) { + outputs[i].Merge(psbt.outputs[i]); + } +} + +bool PartiallySignedTransaction::IsSane() const +{ + for (PSBTInput input : inputs) { + if (!input.IsSane()) return false; + } + return true; +} + +bool PSBTInput::IsNull() const +{ + return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty(); +} + +void PSBTInput::FillSignatureData(SignatureData& sigdata) const +{ + if (!final_script_sig.empty()) { + sigdata.scriptSig = final_script_sig; + sigdata.complete = true; + } + if (!final_script_witness.IsNull()) { + sigdata.scriptWitness = final_script_witness; + sigdata.complete = true; + } + if (sigdata.complete) { + return; + } + + sigdata.signatures.insert(partial_sigs.begin(), partial_sigs.end()); + if (!redeem_script.empty()) { + sigdata.redeem_script = redeem_script; + } + if (!witness_script.empty()) { + sigdata.witness_script = witness_script; + } + for (const auto& key_pair : hd_keypaths) { + sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first); + } +} + +void PSBTInput::FromSignatureData(const SignatureData& sigdata) +{ + if (sigdata.complete) { + partial_sigs.clear(); + hd_keypaths.clear(); + redeem_script.clear(); + witness_script.clear(); + + if (!sigdata.scriptSig.empty()) { + final_script_sig = sigdata.scriptSig; + } + if (!sigdata.scriptWitness.IsNull()) { + final_script_witness = sigdata.scriptWitness; + } + return; + } + + partial_sigs.insert(sigdata.signatures.begin(), sigdata.signatures.end()); + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + if (witness_script.empty() && !sigdata.witness_script.empty()) { + witness_script = sigdata.witness_script; + } +} + +void PSBTInput::Merge(const PSBTInput& input) +{ + if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; + if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { + witness_utxo = input.witness_utxo; + non_witness_utxo = nullptr; // Clear out any non-witness utxo when we set a witness one. + } + + partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end()); + hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end()); + unknown.insert(input.unknown.begin(), input.unknown.end()); + + if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script; + if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script; + if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig; + if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness; +} + +bool PSBTInput::IsSane() const +{ + // Cannot have both witness and non-witness utxos + if (!witness_utxo.IsNull() && non_witness_utxo) return false; + + // If we have a witness_script or a scriptWitness, we must also have a witness utxo + if (!witness_script.empty() && witness_utxo.IsNull()) return false; + if (!final_script_witness.IsNull() && witness_utxo.IsNull()) return false; + + return true; +} + +void PSBTOutput::FillSignatureData(SignatureData& sigdata) const +{ + if (!redeem_script.empty()) { + sigdata.redeem_script = redeem_script; + } + if (!witness_script.empty()) { + sigdata.witness_script = witness_script; + } + for (const auto& key_pair : hd_keypaths) { + sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair.first); + } +} + +void PSBTOutput::FromSignatureData(const SignatureData& sigdata) +{ + if (redeem_script.empty() && !sigdata.redeem_script.empty()) { + redeem_script = sigdata.redeem_script; + } + if (witness_script.empty() && !sigdata.witness_script.empty()) { + witness_script = sigdata.witness_script; + } +} + +bool PSBTOutput::IsNull() const +{ + return redeem_script.empty() && witness_script.empty() && hd_keypaths.empty() && unknown.empty(); +} + +void PSBTOutput::Merge(const PSBTOutput& output) +{ + hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); + unknown.insert(output.unknown.begin(), output.unknown.end()); + + if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; + if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; +} + +bool PublicOnlySigningProvider::GetCScript(const CScriptID &scriptid, CScript& script) const +{ + return m_provider->GetCScript(scriptid, script); +} + +bool PublicOnlySigningProvider::GetPubKey(const CKeyID &address, CPubKey& pubkey) const +{ + return m_provider->GetPubKey(address, pubkey); +} diff --git a/src/script/sign.h b/src/script/sign.h index cf3651c1de..e3a6196b28 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -6,7 +6,11 @@ #ifndef BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H +#include <boost/optional.hpp> +#include <hash.h> +#include <pubkey.h> #include <script/interpreter.h> +#include <streams.h> class CKey; class CKeyID; @@ -21,9 +25,22 @@ class SigningProvider { public: virtual ~SigningProvider() {} - virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const =0; - virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const =0; - virtual bool GetKey(const CKeyID &address, CKey& key) const =0; + 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; } +}; + +extern const SigningProvider& DUMMY_SIGNING_PROVIDER; + +class PublicOnlySigningProvider : public SigningProvider +{ +private: + const SigningProvider* m_provider; + +public: + PublicOnlySigningProvider(const SigningProvider* provider) : m_provider(provider) {} + bool GetCScript(const CScriptID &scriptid, CScript& script) const; + bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const; }; /** Interface for signature creators. */ @@ -37,35 +54,588 @@ public: }; /** A signature creator for transactions. */ -class TransactionSignatureCreator : public BaseSignatureCreator { - const CTransaction* txTo; +class MutableTransactionSignatureCreator : public BaseSignatureCreator { + const CMutableTransaction* txTo; unsigned int nIn; int nHashType; CAmount amount; - const TransactionSignatureChecker checker; + const MutableTransactionSignatureChecker checker; public: - TransactionSignatureCreator(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn=SIGHASH_ALL); + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; }; -class MutableTransactionSignatureCreator : public TransactionSignatureCreator { - CTransaction tx; - -public: - MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : TransactionSignatureCreator(&tx, nInIn, amountIn, nHashTypeIn), tx(*txToIn) {} -}; - /** A signature creator that just produces 72-byte empty signatures. */ extern const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR; +typedef std::pair<CPubKey, std::vector<unsigned char>> SigPair; + +// This struct contains information from a transaction input and also contains signatures for that input. +// The information contained here can be used to create a signature and is also filled by ProduceSignature +// in order to construct final scriptSigs and scriptWitnesses. struct SignatureData { - CScript scriptSig; - CScriptWitness scriptWitness; + bool complete = false; ///< Stores whether the scriptSig and scriptWitness are complete + bool witness = false; ///< Stores whether the input this SigData corresponds to is a witness input + CScript scriptSig; ///< The scriptSig of an input. Contains complete signatures or the traditional partial signatures format + CScript redeem_script; ///< The redeemScript (if any) for the input + CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. + CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. + std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. + std::map<CKeyID, CPubKey> misc_pubkeys; SignatureData() {} explicit SignatureData(const CScript& script) : scriptSig(script) {} + void MergeSignatureData(SignatureData sigdata); +}; + +// Magic bytes +static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; + +// Global types +static constexpr uint8_t PSBT_GLOBAL_UNSIGNED_TX = 0x00; + +// Input types +static constexpr uint8_t PSBT_IN_NON_WITNESS_UTXO = 0x00; +static constexpr uint8_t PSBT_IN_WITNESS_UTXO = 0x01; +static constexpr uint8_t PSBT_IN_PARTIAL_SIG = 0x02; +static constexpr uint8_t PSBT_IN_SIGHASH = 0x03; +static constexpr uint8_t PSBT_IN_REDEEMSCRIPT = 0x04; +static constexpr uint8_t PSBT_IN_WITNESSSCRIPT = 0x05; +static constexpr uint8_t PSBT_IN_BIP32_DERIVATION = 0x06; +static constexpr uint8_t PSBT_IN_SCRIPTSIG = 0x07; +static constexpr uint8_t PSBT_IN_SCRIPTWITNESS = 0x08; + +// Output types +static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00; +static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01; +static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; + +// The separator is 0x00. Reading this in means that the unserializer can interpret it +// as a 0 length key which indicates that this is the separator. The separator has no value. +static constexpr uint8_t PSBT_SEPARATOR = 0x00; + +// Takes a stream and multiple arguments and serializes them into a vector and then into the stream +// The resulting output into the stream has the total serialized length of all of the objects followed by all objects concatenated with each other. +template<typename Stream, typename... X> +void SerializeToVector(Stream& s, const X&... args) +{ + std::vector<unsigned char> ret; + CVectorWriter ss(SER_NETWORK, PROTOCOL_VERSION, ret, 0); + SerializeMany(ss, args...); + s << ret; +} + +// Takes a stream and multiple arguments and unserializes them first as a vector then each object individually in the order provided in the arguments +template<typename Stream, typename... X> +void UnserializeFromVector(Stream& s, X&... args) +{ + std::vector<unsigned char> data; + s >> data; + CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION); + UnserializeMany(ss, args...); + if (!ss.eof()) { + throw std::ios_base::failure("Size of value was not the stated size"); + } +} + +// Deserialize HD keypaths into a map +template<typename Stream> +void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths) +{ + // Make sure that the key is the size of pubkey + 1 + if (key.size() != CPubKey::PUBLIC_KEY_SIZE + 1 && key.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type BIP32 keypath"); + } + // Read in the pubkey from key + CPubKey pubkey(key.begin() + 1, key.end()); + if (!pubkey.IsFullyValid()) { + throw std::ios_base::failure("Invalid pubkey"); + } + if (hd_keypaths.count(pubkey) > 0) { + throw std::ios_base::failure("Duplicate Key, pubkey derivation path already provided"); + } + + // Read in key path + uint64_t value_len = ReadCompactSize(s); + std::vector<uint32_t> keypath; + for (unsigned int i = 0; i < value_len; i += sizeof(uint32_t)) { + uint32_t index; + s >> index; + keypath.push_back(index); + } + + // Add to map + hd_keypaths.emplace(pubkey, keypath); +} + +// Serialize HD keypaths to a stream from a map +template<typename Stream> +void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths, uint8_t type) +{ + for (auto keypath_pair : hd_keypaths) { + SerializeToVector(s, type, MakeSpan(keypath_pair.first)); + WriteCompactSize(s, keypath_pair.second.size() * sizeof(uint32_t)); + for (auto& path : keypath_pair.second) { + s << path; + } + } +} + +/** A structure for PSBTs which contain per-input information */ +struct PSBTInput +{ + CTransactionRef non_witness_utxo; + CTxOut witness_utxo; + CScript redeem_script; + CScript witness_script; + CScript final_script_sig; + CScriptWitness final_script_witness; + std::map<CPubKey, std::vector<uint32_t>> hd_keypaths; + std::map<CKeyID, SigPair> partial_sigs; + std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + int sighash_type = 0; + + bool IsNull() const; + void FillSignatureData(SignatureData& sigdata) const; + void FromSignatureData(const SignatureData& sigdata); + void Merge(const PSBTInput& input); + bool IsSane() const; + PSBTInput() {} + + template <typename Stream> + inline void Serialize(Stream& s) const { + // Write the utxo + // If there is a non-witness utxo, then don't add the witness one. + if (non_witness_utxo) { + SerializeToVector(s, PSBT_IN_NON_WITNESS_UTXO); + SerializeToVector(s, non_witness_utxo); + } else if (!witness_utxo.IsNull()) { + SerializeToVector(s, PSBT_IN_WITNESS_UTXO); + SerializeToVector(s, witness_utxo); + } + + if (final_script_sig.empty() && final_script_witness.IsNull()) { + // Write any partial signatures + for (auto sig_pair : partial_sigs) { + SerializeToVector(s, PSBT_IN_PARTIAL_SIG, MakeSpan(sig_pair.second.first)); + s << sig_pair.second.second; + } + + // Write the sighash type + if (sighash_type > 0) { + SerializeToVector(s, PSBT_IN_SIGHASH); + SerializeToVector(s, sighash_type); + } + + // Write the redeem script + if (!redeem_script.empty()) { + SerializeToVector(s, PSBT_IN_REDEEMSCRIPT); + s << redeem_script; + } + + // Write the witness script + if (!witness_script.empty()) { + SerializeToVector(s, PSBT_IN_WITNESSSCRIPT); + s << witness_script; + } + + // Write any hd keypaths + SerializeHDKeypaths(s, hd_keypaths, PSBT_IN_BIP32_DERIVATION); + } + + // Write script sig + if (!final_script_sig.empty()) { + SerializeToVector(s, PSBT_IN_SCRIPTSIG); + s << final_script_sig; + } + // write script witness + if (!final_script_witness.IsNull()) { + SerializeToVector(s, PSBT_IN_SCRIPTWITNESS); + SerializeToVector(s, final_script_witness.stack); + } + + // Write unknown things + for (auto& entry : unknown) { + s << entry.first; + s << entry.second; + } + + s << PSBT_SEPARATOR; + } + + + template <typename Stream> + inline void Unserialize(Stream& s) { + // Read loop + while(!s.empty()) { + // Read + std::vector<unsigned char> key; + s >> key; + + // 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; + + // First byte of key is the type + unsigned char type = key[0]; + + // Do stuff based on type + switch(type) { + case PSBT_IN_NON_WITNESS_UTXO: + if (non_witness_utxo) { + throw std::ios_base::failure("Duplicate Key, input non-witness utxo already provided"); + } + UnserializeFromVector(s, non_witness_utxo); + break; + case PSBT_IN_WITNESS_UTXO: + if (!witness_utxo.IsNull()) { + throw std::ios_base::failure("Duplicate Key, input witness utxo already provided"); + } + UnserializeFromVector(s, witness_utxo); + break; + case PSBT_IN_PARTIAL_SIG: + { + // Make sure that the key is the size of pubkey + 1 + if (key.size() != CPubKey::PUBLIC_KEY_SIZE + 1 && key.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type partial signature pubkey"); + } + // Read in the pubkey from key + CPubKey pubkey(key.begin() + 1, key.end()); + if (!pubkey.IsFullyValid()) { + throw std::ios_base::failure("Invalid pubkey"); + } + if (partial_sigs.count(pubkey.GetID()) > 0) { + throw std::ios_base::failure("Duplicate Key, input partial signature for pubkey already provided"); + } + + // Read in the signature from value + std::vector<unsigned char> sig; + s >> sig; + + // Add to list + partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig))); + break; + } + case PSBT_IN_SIGHASH: + if (sighash_type > 0) { + throw std::ios_base::failure("Duplicate Key, input sighash type already provided"); + } + UnserializeFromVector(s, sighash_type); + break; + case PSBT_IN_REDEEMSCRIPT: + { + if (!redeem_script.empty()) { + throw std::ios_base::failure("Duplicate Key, input redeemScript already provided"); + } + s >> redeem_script; + break; + } + case PSBT_IN_WITNESSSCRIPT: + { + if (!witness_script.empty()) { + throw std::ios_base::failure("Duplicate Key, input witnessScript already provided"); + } + s >> witness_script; + break; + } + case PSBT_IN_BIP32_DERIVATION: + { + DeserializeHDKeypaths(s, key, hd_keypaths); + break; + } + case PSBT_IN_SCRIPTSIG: + { + if (!final_script_sig.empty()) { + throw std::ios_base::failure("Duplicate Key, input final scriptSig already provided"); + } + s >> final_script_sig; + break; + } + case PSBT_IN_SCRIPTWITNESS: + { + if (!final_script_witness.IsNull()) { + throw std::ios_base::failure("Duplicate Key, input final scriptWitness already provided"); + } + UnserializeFromVector(s, final_script_witness.stack); + break; + } + // Unknown stuff + default: + if (unknown.count(key) > 0) { + throw std::ios_base::failure("Duplicate Key, key for unknown value already provided"); + } + // Read in the value + std::vector<unsigned char> val_bytes; + s >> val_bytes; + unknown.emplace(std::move(key), std::move(val_bytes)); + break; + } + } + } + + template <typename Stream> + PSBTInput(deserialize_type, Stream& s) { + Unserialize(s); + } +}; + +/** A structure for PSBTs which contains per output information */ +struct PSBTOutput +{ + CScript redeem_script; + CScript witness_script; + std::map<CPubKey, std::vector<uint32_t>> hd_keypaths; + std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + + bool IsNull() const; + void FillSignatureData(SignatureData& sigdata) const; + void FromSignatureData(const SignatureData& sigdata); + void Merge(const PSBTOutput& output); + bool IsSane() const; + PSBTOutput() {} + + template <typename Stream> + inline void Serialize(Stream& s) const { + // Write the redeem script + if (!redeem_script.empty()) { + SerializeToVector(s, PSBT_OUT_REDEEMSCRIPT); + s << redeem_script; + } + + // Write the witness script + if (!witness_script.empty()) { + SerializeToVector(s, PSBT_OUT_WITNESSSCRIPT); + s << witness_script; + } + + // Write any hd keypaths + SerializeHDKeypaths(s, hd_keypaths, PSBT_OUT_BIP32_DERIVATION); + + // Write unknown things + for (auto& entry : unknown) { + s << entry.first; + s << entry.second; + } + + s << PSBT_SEPARATOR; + } + + + template <typename Stream> + inline void Unserialize(Stream& s) { + // Read loop + while(!s.empty()) { + // Read + std::vector<unsigned char> key; + s >> key; + + // 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; + + // First byte of key is the type + unsigned char type = key[0]; + + // Do stuff based on type + switch(type) { + case PSBT_OUT_REDEEMSCRIPT: + { + if (!redeem_script.empty()) { + throw std::ios_base::failure("Duplicate Key, output redeemScript already provided"); + } + s >> redeem_script; + break; + } + case PSBT_OUT_WITNESSSCRIPT: + { + if (!witness_script.empty()) { + throw std::ios_base::failure("Duplicate Key, output witnessScript already provided"); + } + s >> witness_script; + break; + } + case PSBT_OUT_BIP32_DERIVATION: + { + DeserializeHDKeypaths(s, key, hd_keypaths); + break; + } + // Unknown stuff + default: { + if (unknown.count(key) > 0) { + throw std::ios_base::failure("Duplicate Key, key for unknown value already provided"); + } + // Read in the value + std::vector<unsigned char> val_bytes; + s >> val_bytes; + unknown.emplace(std::move(key), std::move(val_bytes)); + break; + } + } + } + } + + template <typename Stream> + PSBTOutput(deserialize_type, Stream& s) { + Unserialize(s); + } +}; + +/** A version of CTransaction with the PSBT format*/ +struct PartiallySignedTransaction +{ + boost::optional<CMutableTransaction> tx; + std::vector<PSBTInput> inputs; + std::vector<PSBTOutput> outputs; + std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + + bool IsNull() const; + void Merge(const PartiallySignedTransaction& psbt); + 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) {} + + // Only checks if they refer to the same transaction + friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) + { + return a.tx->GetHash() == b.tx->GetHash(); + } + friend bool operator!=(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) + { + return !(a == b); + } + + template <typename Stream> + inline void Serialize(Stream& s) const { + + // magic bytes + s << PSBT_MAGIC_BYTES; + + // unsigned tx flag + SerializeToVector(s, PSBT_GLOBAL_UNSIGNED_TX); + + // Write serialized tx to a stream + SerializeToVector(s, *tx); + + // Write the unknown things + for (auto& entry : unknown) { + s << entry.first; + s << entry.second; + } + + // Separator + s << PSBT_SEPARATOR; + + // Write inputs + for (const PSBTInput& input : inputs) { + s << input; + } + // Write outputs + for (const PSBTOutput& output : outputs) { + s << output; + } + } + + + template <typename Stream> + inline void Unserialize(Stream& s) { + // Read the magic bytes + uint8_t magic[5]; + s >> magic; + if (!std::equal(magic, magic + 5, PSBT_MAGIC_BYTES)) { + throw std::ios_base::failure("Invalid PSBT magic bytes"); + } + + // Read global data + while(!s.empty()) { + // Read + std::vector<unsigned char> key; + s >> key; + + // 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; + + // First byte of key is the type + unsigned char type = key[0]; + + // Do stuff based on type + switch(type) { + case PSBT_GLOBAL_UNSIGNED_TX: + { + if (tx) { + throw std::ios_base::failure("Duplicate Key, unsigned tx already provided"); + } + CMutableTransaction mtx; + UnserializeFromVector(s, mtx); + tx = std::move(mtx); + // Make sure that all scriptSigs and scriptWitnesses are empty + for (const CTxIn& txin : tx->vin) { + if (!txin.scriptSig.empty() || !txin.scriptWitness.IsNull()) { + throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses."); + } + } + break; + } + // Unknown stuff + default: { + if (unknown.count(key) > 0) { + throw std::ios_base::failure("Duplicate Key, key for unknown value already provided"); + } + // Read in the value + std::vector<unsigned char> val_bytes; + s >> val_bytes; + unknown.emplace(std::move(key), std::move(val_bytes)); + } + } + } + + // Make sure that we got an unsigned tx + if (!tx) { + throw std::ios_base::failure("No unsigned transcation was provided"); + } + + // Read input data + unsigned int i = 0; + while (!s.empty() && i < tx->vin.size()) { + PSBTInput input; + s >> input; + inputs.push_back(input); + + // Make sure the non-witness utxo matches the outpoint + if (input.non_witness_utxo && input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) { + throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash"); + } + ++i; + } + // Make sure that the number of inputs matches the number of inputs in the transaction + if (inputs.size() != tx->vin.size()) { + throw std::ios_base::failure("Inputs provided does not match the number of inputs in transaction."); + } + + // Read output data + i = 0; + while (!s.empty() && i < tx->vout.size()) { + PSBTOutput output; + s >> output; + outputs.push_back(output); + ++i; + } + // Make sure that the number of outputs matches the number of outputs in the transaction + if (outputs.size() != tx->vout.size()) { + throw std::ios_base::failure("Outputs provided does not match the number of outputs in transaction."); + } + // Sanity check + if (!IsSane()) { + throw std::ios_base::failure("PSBT is not sane."); + } + } + + template <typename Stream> + PartiallySignedTransaction(deserialize_type, Stream& s) { + Unserialize(s); + } }; /** Produce a script signature using a generic signature creator. */ @@ -75,12 +645,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); -/** Combine two script signatures using a generic signature checker, intelligently, possibly with OP_0 placeholders. */ -SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker, const SignatureData& scriptSig1, const SignatureData& scriptSig2); +/** Signs a PSBTInput */ +bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash = 1); -/** Extract signature data from a transaction, and insert it. */ -SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn); -void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data); +/** Extract signature data from a transaction input, and insert it. */ +SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); void UpdateInput(CTxIn& input, const SignatureData& data); /* Check whether we know how to sign for an output like this, assuming we diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 76778112aa..f0b2c62a91 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -5,6 +5,7 @@ #include <script/standard.h> +#include <crypto/sha256.h> #include <pubkey.h> #include <script/script.h> #include <util.h> @@ -18,6 +19,11 @@ unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; CScriptID::CScriptID(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {} +WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in) +{ + CSHA256().Write(in.data(), in.size()).Finalize(begin()); +} + const char* GetTxnOutputType(txnouttype t) { switch (t) @@ -35,22 +41,54 @@ const char* GetTxnOutputType(txnouttype t) return nullptr; } -bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet) +static bool MatchPayToPubkey(const CScript& script, valtype& pubkey) { - // Templates - static std::multimap<txnouttype, CScript> mTemplates; - if (mTemplates.empty()) - { - // Standard tx, sender provides pubkey, receiver adds signature - mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG)); + if (script.size() == CPubKey::PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + if (script.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + return false; +} - // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey - mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG)); +static bool MatchPayToPubkeyHash(const CScript& script, valtype& pubkeyhash) +{ + if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { + pubkeyhash = valtype(script.begin () + 3, script.begin() + 23); + return true; + } + return false; +} - // Sender provides N pubkeys, receivers provides M signatures - mTemplates.insert(std::make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG)); +/** Test for "small positive integer" script opcodes - OP_1 through OP_16. */ +static constexpr bool IsSmallInteger(opcodetype opcode) +{ + return opcode >= OP_1 && opcode <= OP_16; +} + +static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys) +{ + opcodetype opcode; + valtype data; + CScript::const_iterator it = script.begin(); + if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false; + + if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false; + required = CScript::DecodeOP_N(opcode); + while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) { + pubkeys.emplace_back(std::move(data)); } + if (!IsSmallInteger(opcode)) return false; + unsigned int keys = CScript::DecodeOP_N(opcode); + if (pubkeys.size() != keys || keys < required) return false; + return (it + 1 == script.end()); +} +bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet) +{ vSolutionsRet.clear(); // Shortcut for pay-to-script-hash, which are more constrained than the other types: @@ -82,6 +120,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v vSolutionsRet.push_back(std::move(witnessprogram)); return true; } + typeRet = TX_NONSTANDARD; return false; } @@ -95,84 +134,27 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v return true; } - // Scan templates - const CScript& script1 = scriptPubKey; - for (const std::pair<txnouttype, CScript>& tplate : mTemplates) - { - const CScript& script2 = tplate.second; - vSolutionsRet.clear(); + std::vector<unsigned char> data; + if (MatchPayToPubkey(scriptPubKey, data)) { + typeRet = TX_PUBKEY; + vSolutionsRet.push_back(std::move(data)); + return true; + } - opcodetype opcode1, opcode2; - std::vector<unsigned char> vch1, vch2; + if (MatchPayToPubkeyHash(scriptPubKey, data)) { + typeRet = TX_PUBKEYHASH; + vSolutionsRet.push_back(std::move(data)); + return true; + } - // Compare - CScript::const_iterator pc1 = script1.begin(); - CScript::const_iterator pc2 = script2.begin(); - while (true) - { - if (pc1 == script1.end() && pc2 == script2.end()) - { - // Found a match - typeRet = tplate.first; - if (typeRet == TX_MULTISIG) - { - // Additional checks for TX_MULTISIG: - unsigned char m = vSolutionsRet.front()[0]; - unsigned char n = vSolutionsRet.back()[0]; - if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n) - return false; - } - return true; - } - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - - // Template matching opcodes: - if (opcode2 == OP_PUBKEYS) - { - while (CPubKey::ValidSize(vch1)) - { - vSolutionsRet.push_back(vch1); - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - } - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - // Normal situation is to fall through - // to other if/else statements - } - - if (opcode2 == OP_PUBKEY) - { - if (!CPubKey::ValidSize(vch1)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_PUBKEYHASH) - { - if (vch1.size() != sizeof(uint160)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_SMALLINTEGER) - { // Single-byte small integer pushed onto vSolutions - if (opcode1 == OP_0 || - (opcode1 >= OP_1 && opcode1 <= OP_16)) - { - char n = (char)CScript::DecodeOP_N(opcode1); - vSolutionsRet.push_back(valtype(1, n)); - } - else - break; - } - else if (opcode1 != opcode2 || vch1 != vch2) - { - // Others must match exactly - break; - } - } + unsigned int required; + std::vector<std::vector<unsigned char>> keys; + if (MatchMultisig(scriptPubKey, required, keys)) { + typeRet = TX_MULTISIG; + vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16 + vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end()); + vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..16 + return true; } vSolutionsRet.clear(); @@ -353,9 +335,7 @@ CScript GetScriptForWitness(const CScript& redeemscript) return GetScriptForDestination(WitnessV0KeyHash(vSolutions[0])); } } - uint256 hash; - CSHA256().Write(&redeemscript[0], redeemscript.size()).Finalize(hash.begin()); - return GetScriptForDestination(WitnessV0ScriptHash(hash)); + return GetScriptForDestination(WitnessV0ScriptHash(redeemscript)); } bool IsValidDestination(const CTxDestination& dest) { diff --git a/src/script/standard.h b/src/script/standard.h index 3b2838a5bb..1380030871 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -23,7 +23,7 @@ class CScriptID : public uint160 { public: CScriptID() : uint160() {} - CScriptID(const CScript& in); + explicit CScriptID(const CScript& in); CScriptID(const uint160& in) : uint160(in) {} }; @@ -77,6 +77,7 @@ struct WitnessV0ScriptHash : public uint256 { WitnessV0ScriptHash() : uint256() {} explicit WitnessV0ScriptHash(const uint256& hash) : uint256(hash) {} + explicit WitnessV0ScriptHash(const CScript& script); using uint256::uint256; }; diff --git a/src/serialize.h b/src/serialize.h index e54c7483d2..df3b47ba87 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -189,7 +189,9 @@ template<typename X> const X& ReadWriteAsHelper(const X& x) { return x; } SerializationOp(s, CSerActionUnserialize()); \ } +#ifndef CHAR_EQUALS_INT8 template<typename Stream> inline void Serialize(Stream& s, char a ) { ser_writedata8(s, a); } // TODO Get rid of bare char +#endif template<typename Stream> inline void Serialize(Stream& s, int8_t a ) { ser_writedata8(s, a); } template<typename Stream> inline void Serialize(Stream& s, uint8_t a ) { ser_writedata8(s, a); } template<typename Stream> inline void Serialize(Stream& s, int16_t a ) { ser_writedata16(s, a); } @@ -205,7 +207,9 @@ template<typename Stream, int N> inline void Serialize(Stream& s, const unsigned template<typename Stream> inline void Serialize(Stream& s, const Span<const unsigned char>& span) { s.write(CharCast(span.data()), span.size()); } template<typename Stream> inline void Serialize(Stream& s, const Span<unsigned char>& span) { s.write(CharCast(span.data()), span.size()); } +#ifndef CHAR_EQUALS_INT8 template<typename Stream> inline void Unserialize(Stream& s, char& a ) { a = ser_readdata8(s); } // TODO Get rid of bare char +#endif template<typename Stream> inline void Unserialize(Stream& s, int8_t& a ) { a = ser_readdata8(s); } template<typename Stream> inline void Unserialize(Stream& s, uint8_t& a ) { a = ser_readdata8(s); } template<typename Stream> inline void Unserialize(Stream& s, int16_t& a ) { a = ser_readdata16(s); } diff --git a/src/shutdown.cpp b/src/shutdown.cpp new file mode 100644 index 0000000000..dec497d8ec --- /dev/null +++ b/src/shutdown.cpp @@ -0,0 +1,23 @@ +// 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. + +#include <shutdown.h> + +#include <atomic> + +static std::atomic<bool> fRequestShutdown(false); + +void StartShutdown() +{ + fRequestShutdown = true; +} +void AbortShutdown() +{ + fRequestShutdown = false; +} +bool ShutdownRequested() +{ + return fRequestShutdown; +} diff --git a/src/shutdown.h b/src/shutdown.h new file mode 100644 index 0000000000..3ed851c789 --- /dev/null +++ b/src/shutdown.h @@ -0,0 +1,13 @@ +// 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_SHUTDOWN_H +#define BITCOIN_SHUTDOWN_H + +void StartShutdown(); +void AbortShutdown(); +bool ShutdownRequested(); + +#endif diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index f10fd07c63..a273424b51 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -141,7 +141,7 @@ Arena::Stats Arena::stats() const } #ifdef ARENA_DEBUG -void printchunk(char* base, size_t sz, bool used) { +static void printchunk(char* base, size_t sz, bool used) { std::cout << "0x" << std::hex << std::setw(16) << std::setfill('0') << base << " 0x" << std::hex << std::setw(16) << std::setfill('0') << sz << diff --git a/src/sync.cpp b/src/sync.cpp index 6f21d498ee..28a4e37e68 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -4,13 +4,15 @@ #include <sync.h> -#include <memory> -#include <set> -#include <util.h> +#include <logging.h> #include <utilstrencodings.h> #include <stdio.h> +#include <map> +#include <memory> +#include <set> + #ifdef DEBUG_LOCKCONTENTION #if !defined(HAVE_THREAD_LOCAL) static_assert(false, "thread_local is not supported"); @@ -73,7 +75,7 @@ struct LockData { std::mutex dd_mutex; } static lockdata; -static thread_local std::unique_ptr<LockStack> lockstack; +static thread_local LockStack g_lockstack; static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, const LockStack& s1, const LockStack& s2) { @@ -103,21 +105,18 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, static void push_lock(void* c, const CLockLocation& locklocation) { - if (!lockstack) - lockstack.reset(new LockStack); - std::lock_guard<std::mutex> lock(lockdata.dd_mutex); - lockstack->push_back(std::make_pair(c, locklocation)); + g_lockstack.push_back(std::make_pair(c, locklocation)); - for (const std::pair<void*, CLockLocation> & i : (*lockstack)) { + for (const std::pair<void*, CLockLocation>& i : g_lockstack) { if (i.first == c) break; std::pair<void*, void*> p1 = std::make_pair(i.first, c); if (lockdata.lockorders.count(p1)) continue; - lockdata.lockorders[p1] = (*lockstack); + lockdata.lockorders[p1] = g_lockstack; std::pair<void*, void*> p2 = std::make_pair(c, i.first); lockdata.invlockorders.insert(p2); @@ -128,7 +127,7 @@ static void push_lock(void* c, const CLockLocation& locklocation) static void pop_lock() { - (*lockstack).pop_back(); + g_lockstack.pop_back(); } void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry) @@ -144,14 +143,14 @@ void LeaveCritical() std::string LocksHeld() { std::string result; - for (const std::pair<void*, CLockLocation> & i : *lockstack) + for (const std::pair<void*, CLockLocation>& i : g_lockstack) result += i.second.ToString() + std::string("\n"); return result; } void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) { - for (const std::pair<void*, CLockLocation> & i : *lockstack) + for (const std::pair<void*, CLockLocation>& i : g_lockstack) if (i.first == cs) return; fprintf(stderr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); @@ -160,7 +159,7 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) { - for (const std::pair<void*, CLockLocation>& i : *lockstack) { + for (const std::pair<void*, CLockLocation>& i : g_lockstack) { if (i.first == cs) { fprintf(stderr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); abort(); diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 21a1153ad0..8644aea371 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -17,7 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(arith_uint256_tests, BasicTestingSetup) /// Convert vector to arith_uint256, via uint256 blob -inline arith_uint256 arith_uint256V(const std::vector<unsigned char>& vch) +static inline arith_uint256 arith_uint256V(const std::vector<unsigned char>& vch) { return UintToArith256(uint256(vch)); } @@ -53,7 +53,7 @@ const unsigned char MaxArray[] = const arith_uint256 MaxL = arith_uint256V(std::vector<unsigned char>(MaxArray,MaxArray+32)); const arith_uint256 HalfL = (OneL << 255); -std::string ArrayToString(const unsigned char A[], unsigned int width) +static std::string ArrayToString(const unsigned char A[], unsigned int width) { std::stringstream Stream; Stream << std::hex; @@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE( basics ) // constructors, equality, inequality tmpL = ~MaxL; BOOST_CHECK(tmpL == ~MaxL); } -void shiftArrayRight(unsigned char* to, const unsigned char* from, unsigned int arrayLength, unsigned int bitsToShift) +static void shiftArrayRight(unsigned char* to, const unsigned char* from, unsigned int arrayLength, unsigned int bitsToShift) { for (unsigned int T=0; T < arrayLength; ++T) { @@ -136,7 +136,7 @@ void shiftArrayRight(unsigned char* to, const unsigned char* from, unsigned int } } -void shiftArrayLeft(unsigned char* to, const unsigned char* from, unsigned int arrayLength, unsigned int bitsToShift) +static void shiftArrayLeft(unsigned char* to, const unsigned char* from, unsigned int arrayLength, unsigned int bitsToShift) { for (unsigned int T=0; T < arrayLength; ++T) { @@ -198,13 +198,6 @@ BOOST_AUTO_TEST_CASE( shifts ) { // "<<" ">>" "<<=" ">>=" BOOST_AUTO_TEST_CASE( unaryOperators ) // ! ~ - { - BOOST_CHECK(!ZeroL); - BOOST_CHECK(!(!OneL)); - for (unsigned int i = 0; i < 256; ++i) - BOOST_CHECK(!(!(OneL<<i))); - BOOST_CHECK(!(!R1L)); - BOOST_CHECK(!(!MaxL)); - BOOST_CHECK(~ZeroL == MaxL); unsigned char TmpArray[32]; @@ -369,7 +362,7 @@ BOOST_AUTO_TEST_CASE( divide ) } -bool almostEqual(double d1, double d2) +static bool almostEqual(double d1, double d2) { return fabs(d1-d2) <= 4*fabs(d1)*std::numeric_limits<double>::epsilon(); } diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index 495290c8d9..6ecc9ac705 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -9,7 +9,7 @@ BOOST_FIXTURE_TEST_SUITE(bech32_tests, BasicTestingSetup) -bool CaseInsensitiveEqual(const std::string &s1, const std::string &s2) +static bool CaseInsensitiveEqual(const std::string &s1, const std::string &s2) { if (s1.size() != s2.size()) return false; for (size_t i = 0; i < s1.size(); ++i) { @@ -57,6 +57,8 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid) "A1G7SGD8", "10a06t8", "1qzzfhee", + "a12UEL5L", + "A12uEL5L", }; for (const std::string& str : CASES) { auto ret = bech32::Decode(str); diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 3c9ff1877d..2c625c089c 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -87,11 +87,11 @@ TestVector test3 = "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", 0); -void RunTest(const TestVector &test) { +static void RunTest(const TestVector &test) { std::vector<unsigned char> seed = ParseHex(test.strHexMaster); CExtKey key; CExtPubKey pubkey; - key.SetMaster(seed.data(), seed.size()); + key.SetSeed(seed.data(), seed.size()); pubkey = key.Neuter(); for (const TestDerivation &derive : test.vDerive) { unsigned char data[74]; diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index 32b408838c..7d8ae46fb8 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -1,19 +1,19 @@ #include <boost/test/unit_test.hpp> -#include "stdlib.h" +#include <stdlib.h> -#include "rpc/blockchain.cpp" -#include "test/test_bitcoin.h" +#include <rpc/blockchain.h> +#include <test/test_bitcoin.h> /* Equality between doubles is imprecise. Comparison should be done * with a small threshold of tolerance, rather than exact equality. */ -bool DoubleEquals(double a, double b, double epsilon) +static bool DoubleEquals(double a, double b, double epsilon) { return std::abs(a - b) < epsilon; } -CBlockIndex* CreateBlockIndexWithNbits(uint32_t nbits) +static CBlockIndex* CreateBlockIndexWithNbits(uint32_t nbits) { CBlockIndex* block_index = new CBlockIndex(); block_index->nHeight = 46367; @@ -22,15 +22,7 @@ CBlockIndex* CreateBlockIndexWithNbits(uint32_t nbits) return block_index; } -CChain CreateChainWithNbits(uint32_t nbits) -{ - CBlockIndex* block_index = CreateBlockIndexWithNbits(nbits); - CChain chain; - chain.SetTip(block_index); - return chain; -} - -void RejectDifficultyMismatch(double difficulty, double expected_difficulty) { +static void RejectDifficultyMismatch(double difficulty, double expected_difficulty) { BOOST_CHECK_MESSAGE( DoubleEquals(difficulty, expected_difficulty, 0.00001), "Difficulty was " + std::to_string(difficulty) @@ -40,15 +32,10 @@ void RejectDifficultyMismatch(double difficulty, double expected_difficulty) { /* Given a BlockIndex with the provided nbits, * verify that the expected difficulty results. */ -void TestDifficulty(uint32_t nbits, double expected_difficulty) +static void TestDifficulty(uint32_t nbits, double expected_difficulty) { CBlockIndex* block_index = CreateBlockIndexWithNbits(nbits); - /* Since we are passing in block index explicitly, - * there is no need to set up anything within the chain itself. - */ - CChain chain; - - double difficulty = GetDifficulty(chain, block_index); + double difficulty = GetDifficulty(block_index); delete block_index; RejectDifficultyMismatch(difficulty, expected_difficulty); @@ -84,43 +71,8 @@ BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target) // Verify that difficulty is 1.0 for an empty chain. BOOST_AUTO_TEST_CASE(get_difficulty_for_null_tip) { - CChain chain; - double difficulty = GetDifficulty(chain, nullptr); + double difficulty = GetDifficulty(nullptr); RejectDifficultyMismatch(difficulty, 1.0); } -/* Verify that if difficulty is based upon the block index - * in the chain, if no block index is explicitly specified. - */ -BOOST_AUTO_TEST_CASE(get_difficulty_for_null_block_index) -{ - CChain chain = CreateChainWithNbits(0x1df88f6f); - - double difficulty = GetDifficulty(chain, nullptr); - delete chain.Tip(); - - double expected_difficulty = 0.004023; - - RejectDifficultyMismatch(difficulty, expected_difficulty); -} - -/* Verify that difficulty is based upon the explicitly specified - * block index rather than being taken from the provided chain, - * when both are present. - */ -BOOST_AUTO_TEST_CASE(get_difficulty_for_block_index_overrides_tip) -{ - CChain chain = CreateChainWithNbits(0x1df88f6f); - /* This block index's nbits should be used - * instead of the chain's when calculating difficulty. - */ - CBlockIndex* override_block_index = CreateBlockIndexWithNbits(0x12345678); - - double difficulty = GetDifficulty(chain, override_block_index); - delete chain.Tip(); - delete override_block_index; - - RejectDifficultyMismatch(difficulty, 5913134931067755359633408.0); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 8cffacbffe..3dd5356164 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -5,6 +5,7 @@ #include <blockencodings.h> #include <consensus/merkle.h> #include <chainparams.h> +#include <pow.h> #include <random.h> #include <test/test_bitcoin.h> diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index de47216449..c8de7f4a7c 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -146,7 +146,7 @@ typedef CCheckQueue<FrozenCleanupCheck> FrozenCleanup_Queue; /** This test case checks that the CCheckQueue works properly * with each specified size_t Checks pushed. */ -void Correct_Queue_range(std::vector<size_t> range) +static void Correct_Queue_range(std::vector<size_t> range) { auto small_queue = std::unique_ptr<Correct_Queue>(new Correct_Queue {QUEUE_BATCH_SIZE}); boost::thread_group tg; diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index a146c69fd2..b792ff8b45 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -312,7 +312,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) if (InsecureRandRange(10) == 0 && coinbase_coins.size()) { auto utxod = FindRandomFrom(coinbase_coins); // Reuse the exact same coinbase - tx = std::get<0>(utxod->second); + tx = CMutableTransaction{std::get<0>(utxod->second)}; // shouldn't be available for reconnection if it's been duplicated disconnected_coins.erase(utxod->first); @@ -331,7 +331,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // 1/20 times reconnect a previously disconnected tx if (randiter % 20 == 2 && disconnected_coins.size()) { auto utxod = FindRandomFrom(disconnected_coins); - tx = std::get<0>(utxod->second); + tx = CMutableTransaction{std::get<0>(utxod->second)}; prevout = tx.vin[0].prevout; if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) { disconnected_coins.erase(utxod->first); @@ -540,7 +540,7 @@ const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)}; const static auto CLEAN_FLAGS = {char(0), FRESH}; const static auto ABSENT_FLAGS = {NO_ENTRY}; -void SetCoinsValue(CAmount value, Coin& coin) +static void SetCoinsValue(CAmount value, Coin& coin) { assert(value != ABSENT); coin.Clear(); @@ -552,7 +552,7 @@ void SetCoinsValue(CAmount value, Coin& coin) } } -size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags) +static size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags) { if (value == ABSENT) { assert(flags == NO_ENTRY); @@ -605,7 +605,7 @@ public: CCoinsViewCacheTest cache{&base}; }; -void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) +static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.AccessCoin(OUTPOINT); @@ -656,7 +656,7 @@ BOOST_AUTO_TEST_CASE(ccoins_access) CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH); } -void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) +static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.SpendCoin(OUTPOINT); @@ -707,7 +707,7 @@ BOOST_AUTO_TEST_CASE(ccoins_spend) CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY ); } -void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) +static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); @@ -734,7 +734,7 @@ void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_va // while still verifying that the CoinsViewCache::AddCoin implementation // ignores base values. template <typename... Args> -void CheckAddCoin(Args&&... args) +static void CheckAddCoin(Args&&... args) { for (CAmount base_value : {ABSENT, PRUNED, VALUE1}) CheckAddCoinBase(base_value, std::forward<Args>(args)...); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index de0d72614b..d701f3bc4e 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -23,7 +23,7 @@ BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) template<typename Hasher, typename In, typename Out> -void TestVector(const Hasher &h, const In &in, const Out &out) { +static void TestVector(const Hasher &h, const In &in, const Out &out) { Out hash; BOOST_CHECK(out.size() == h.OUTPUT_SIZE); hash.resize(out.size()); @@ -51,22 +51,22 @@ void TestVector(const Hasher &h, const In &in, const Out &out) { } } -void TestSHA1(const std::string &in, const std::string &hexout) { TestVector(CSHA1(), in, ParseHex(hexout));} -void TestSHA256(const std::string &in, const std::string &hexout) { TestVector(CSHA256(), in, ParseHex(hexout));} -void TestSHA512(const std::string &in, const std::string &hexout) { TestVector(CSHA512(), in, ParseHex(hexout));} -void TestRIPEMD160(const std::string &in, const std::string &hexout) { TestVector(CRIPEMD160(), in, ParseHex(hexout));} +static void TestSHA1(const std::string &in, const std::string &hexout) { TestVector(CSHA1(), in, ParseHex(hexout));} +static void TestSHA256(const std::string &in, const std::string &hexout) { TestVector(CSHA256(), in, ParseHex(hexout));} +static void TestSHA512(const std::string &in, const std::string &hexout) { TestVector(CSHA512(), in, ParseHex(hexout));} +static void TestRIPEMD160(const std::string &in, const std::string &hexout) { TestVector(CRIPEMD160(), in, ParseHex(hexout));} -void TestHMACSHA256(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { +static void TestHMACSHA256(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); TestVector(CHMAC_SHA256(key.data(), key.size()), ParseHex(hexin), ParseHex(hexout)); } -void TestHMACSHA512(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { +static void TestHMACSHA512(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); TestVector(CHMAC_SHA512(key.data(), key.size()), ParseHex(hexin), ParseHex(hexout)); } -void TestAES128(const std::string &hexkey, const std::string &hexin, const std::string &hexout) +static void TestAES128(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); std::vector<unsigned char> in = ParseHex(hexin); @@ -86,7 +86,7 @@ void TestAES128(const std::string &hexkey, const std::string &hexin, const std:: BOOST_CHECK_EQUAL(HexStr(buf2), HexStr(in)); } -void TestAES256(const std::string &hexkey, const std::string &hexin, const std::string &hexout) +static void TestAES256(const std::string &hexkey, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); std::vector<unsigned char> in = ParseHex(hexin); @@ -105,7 +105,7 @@ void TestAES256(const std::string &hexkey, const std::string &hexin, const std:: BOOST_CHECK(buf == in); } -void TestAES128CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) +static void TestAES128CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); std::vector<unsigned char> iv = ParseHex(hexiv); @@ -146,7 +146,7 @@ void TestAES128CBC(const std::string &hexkey, const std::string &hexiv, bool pad } } -void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) +static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) { std::vector<unsigned char> key = ParseHex(hexkey); std::vector<unsigned char> iv = ParseHex(hexiv); @@ -187,7 +187,7 @@ void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, bool pad } } -void TestChaCha20(const std::string &hexkey, uint64_t nonce, uint64_t seek, const std::string& hexout) +static void TestChaCha20(const std::string &hexkey, uint64_t nonce, uint64_t seek, const std::string& hexout) { std::vector<unsigned char> key = ParseHex(hexkey); ChaCha20 rng(key.data(), key.size()); @@ -200,7 +200,7 @@ void TestChaCha20(const std::string &hexkey, uint64_t nonce, uint64_t seek, cons BOOST_CHECK(out == outres); } -std::string LongTestString(void) { +static std::string LongTestString(void) { std::string ret; for (int i=0; i<200000; i++) { ret += (unsigned char)(i); @@ -546,4 +546,20 @@ BOOST_AUTO_TEST_CASE(countbits_tests) } } +BOOST_AUTO_TEST_CASE(sha256d64) +{ + for (int i = 0; i <= 32; ++i) { + unsigned char in[64 * 32]; + unsigned char out1[32 * 32], out2[32 * 32]; + for (int j = 0; j < 64 * i; ++j) { + in[j] = InsecureRandBits(8); + } + for (int j = 0; j < i; ++j) { + CHash256().Write(in + 64 * j, 64).Finalize(out1 + 32 * j); + } + SHA256D64(out2, in, i); + BOOST_CHECK(memcmp(out1, out2, 32 * i) == 0); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index ccd5caacd5..2edc7c16d4 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -28,7 +28,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); /** insecure_GetRandHash fills in a uint256 from local_rand_ctx */ -void insecure_GetRandHash(uint256& t) +static void insecure_GetRandHash(uint256& t) { uint32_t* ptr = (uint32_t*)t.begin(); for (uint8_t j = 0; j < 8; ++j) @@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) * inserted into a megabytes sized cache */ template <typename Cache> -double test_cache(size_t megabytes, double load) +static double test_cache(size_t megabytes, double load) { local_rand_ctx = FastRandomContext(true); std::vector<uint256> hashes; @@ -109,12 +109,12 @@ double test_cache(size_t megabytes, double load) * how you measure around load 1.0 as after load 1.0 your normalized hit rate * becomes effectively perfect, ignoring freshness. */ -double normalize_hit_rate(double hits, double load) +static double normalize_hit_rate(double hits, double load) { return hits * std::max(load, 1.0); } -/** Check the hit rate on loads ranging from 0.1 to 2.0 */ +/** Check the hit rate on loads ranging from 0.1 to 1.6 */ BOOST_AUTO_TEST_CASE(cuckoocache_hit_rate_ok) { /** Arbitrarily selected Hit Rate threshold that happens to work for this test @@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(cuckoocache_hit_rate_ok) /** This helper checks that erased elements are preferentially inserted onto and * that the hit rate of "fresher" keys is reasonable*/ template <typename Cache> -void test_cache_erase(size_t megabytes) +static void test_cache_erase(size_t megabytes) { double load = 1; local_rand_ctx = FastRandomContext(true); @@ -195,7 +195,7 @@ BOOST_AUTO_TEST_CASE(cuckoocache_erase_ok) } template <typename Cache> -void test_cache_erase_parallel(size_t megabytes) +static void test_cache_erase_parallel(size_t megabytes) { double load = 1; local_rand_ctx = FastRandomContext(true); @@ -283,7 +283,7 @@ BOOST_AUTO_TEST_CASE(cuckoocache_erase_parallel_ok) template <typename Cache> -void test_cache_generations() +static void test_cache_generations() { // This test checks that for a simulation of network activity, the fresh hit // rate is never below 99%, and the number of times that it is worse than diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index abb46fe533..918df6d8d9 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -340,5 +340,53 @@ [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], "010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601010221023cb6055f4b57a1580c5a753e19610cafaedf7e0ff377731c77837fd666eae1712102c1b1db303ac232ffa8e5e7cc2cf5f96c6e40d3e6914061204c0541cb2043a0969552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], +["SCRIPT_VERIFY_CONST_SCRIPTCODE tests"], +["All transactions are copied from OP_CODESEPARATOR tests in tx_valid.json"], + +[[["bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224", 0, "CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIG"]], + "01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], +[[["83e194f90b6ef21fa2e3a365b63794fb5daa844bdc9b25de30899fcfe7b01047", 0, "CODESEPARATOR CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIG"]], + "01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CODESEPARATOR CHECKSIG"]], + "01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["CODESEPARATOR in an unexecuted IF block is still invalid"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["CODESEPARATOR in an executed IF block is invalid"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], + "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + + +["Using CHECKSIG with singatures in scriptSigs will trigger FindAndDelete, which is invalid"], +[[["ccf7f4053a02e653c36ac75c891b7496d0dc5ce5214f6c913d9cf8f1329ebee0", 0, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["OP_CODESEPARATOR in scriptSig is invalid"], +[[["10c9f0effe83e97f80f067de2b11c6a00c3088a4bce42c5ae761519af9306f3c", 1, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["Again, FindAndDelete() in scriptSig"], +[[["6056ebd549003b10cbbd915cea0d82209fe40b8617104be917a26fa92cbe3d6f", 0, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +[[["5a6b0021a6042a686b6b94abc36b387bef9109847774e8b1e51eb8cc55c53921", 1, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], + "01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH,CONST_SCRIPTCODE"], + +["FindAndDelete() in redeemScript"], +[[["b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9", 0, "DUP HASH160 0x14 0xf6f365c40f0739b61de827a44751e5e99032ed8f EQUALVERIFY CHECKSIG"], + ["ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742", 0, "HASH160 0x14 0xd8dacdadb7462ae15cd906f1878706d0da8660e6 EQUAL"]], + "0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000", "P2SH,CONST_SCRIPTCODE"], + +["FindAndDelete() in bare CHECKMULTISIG"], +[[["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 0, "DUP HASH160 0x14 0xf6f365c40f0739b61de827a44751e5e99032ed8f EQUALVERIFY CHECKSIG"], + ["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 1, "2 0x48 0x3045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 3 CHECKMULTISIG"]], + "0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "P2SH,CONST_SCRIPTCODE"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 0bcecc58fe..4a1c77166d 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -477,17 +477,17 @@ ["BIP143 example: P2WSH with OP_CODESEPARATOR and out-of-range SIGHASH_SINGLE."], [[["6eb316926b1c5d567cd6f5e6a84fec606fc53d7b474526d1fff3948020c93dfe", 0, "0x21 0x036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8 CHECKSIG", 156250000], ["f825690aee1b3dc247da796cacb12687a5e802429fd291cfd63e010f02cf1508", 0, "0x00 0x20 0x5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0", 4900000000]], -"01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000", "P2SH,WITNESS"], +"01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: P2WSH with unexecuted OP_CODESEPARATOR and SINGLE|ANYONECANPAY"], [[["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215], ["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215]], -"01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], +"01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: Same as the previous example with input-output pairs swapped"], [[["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215], ["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215]], -"0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], +"0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["BIP143 example: P2SH-P2WSH 6-of-6 multisig signed with 6 different SIGHASH types"], [[["6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436", 1, "HASH160 0x14 0x9993a429037b5d912407a71c252019287b8d27a5 EQUAL", 987654321]], @@ -504,7 +504,7 @@ "010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000", "P2SH,WITNESS"], ["BIP143: correct sighash (without FindAndDelete) = 71c9cd9b2869b9c70b01b1f0360c148f42dee72297db312638df136f43311f23"], [[["f18783ace138abac5d3a7a5cf08e88fe6912f267ef936452e0c27d090621c169", 7500, "0x00 0x20 0x9e1be07558ea5cc8e02ed1d80c0911048afad949affa36d5c3951e3159dbea19", 200000]], -"0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000", "P2SH,WITNESS"], +"0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["This is multisig version of the FindAndDelete tests"], ["Script is 2 CHECKMULTISIGVERIFY <sig1> <sig2> DROP"], ["52af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175"], @@ -514,7 +514,7 @@ "01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000", "P2SH,WITNESS"], ["BIP143: correct sighash (without FindAndDelete) = c1628a1e7c67f14ca0c27c06e4fdeec2e6d1a73c7a91d7c046ff83e835aebb72"], [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], -"010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], +"010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS,CONST_SCRIPTCODE"], ["Test long outputs, which are streamed using length-prefixed bitcoin strings. This might be surprising."], [[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index edc41ec42c..fac7418cba 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -12,7 +12,7 @@ #include <boost/test/unit_test.hpp> // Test if a string consists entirely of null characters -bool is_null_key(const std::vector<unsigned char>& key) { +static bool is_null_key(const std::vector<unsigned char>& key) { bool isnull = true; for (unsigned int i = 0; i < key.size(); i++) @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper) { // Perform tests both obfuscated and non-obfuscated. for (bool obfuscate : {false, true}) { - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir(std::string("dbwrapper").append(obfuscate ? "_true" : "_false")); CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); char key = 'k'; uint256 in = InsecureRand256(); @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_batch) { // Perform tests both obfuscated and non-obfuscated. for (bool obfuscate : {false, true}) { - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir(std::string("dbwrapper_batch").append(obfuscate ? "_true" : "_false")); CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); char key = 'i'; @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_iterator) { // Perform tests both obfuscated and non-obfuscated. for (bool obfuscate : {false, true}) { - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir(std::string("dbwrapper_iterator").append(obfuscate ? "_true" : "_false")); CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); // The two keys are intentionally chosen for ordering @@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_iterator) BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) { // We're going to share this fs::path between two wrappers - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir("existing_data_no_obfuscate"); create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. @@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) BOOST_AUTO_TEST_CASE(existing_data_reindex) { // We're going to share this fs::path between two wrappers - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir("existing_data_reindex"); create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) BOOST_AUTO_TEST_CASE(iterator_ordering) { - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir("iterator_ordering"); CDBWrapper dbw(ph, (1 << 20), true, false, false); for (int x=0x00; x<256; ++x) { uint8_t key = x; @@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE(iterator_string_ordering) { char buf[10]; - fs::path ph = fs::temp_directory_path() / fs::unique_path(); + fs::path ph = SetDataDir("iterator_string_ordering"); CDBWrapper dbw(ph, (1 << 20), true, false, false); for (int x=0x00; x<10; ++x) { for (int y = 0; y < 10; y++) { diff --git a/src/test/DoS_tests.cpp b/src/test/denialofservice_tests.cpp index abc31e6181..49037adb9a 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -24,6 +24,8 @@ extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer); extern void EraseOrphansFor(NodeId peer); extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans); +extern void Misbehaving(NodeId nodeid, int howmuch, const std::string& message=""); + struct COrphanTx { CTransactionRef tx; NodeId fromPeer; @@ -31,7 +33,7 @@ struct COrphanTx { }; extern std::map<uint256, COrphanTx> mapOrphanTransactions; -CService ip(uint32_t i) +static CService ip(uint32_t i) { struct in_addr s; s.s_addr = i; @@ -42,7 +44,7 @@ static NodeId id = 0; void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds); -BOOST_FIXTURE_TEST_SUITE(DoS_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // Test eviction of an outbound peer whose chain never advances // Mock a node connection, and use mocktime to simulate a peer @@ -54,7 +56,6 @@ BOOST_FIXTURE_TEST_SUITE(DoS_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { - std::atomic<bool> interruptDummy(false); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -66,25 +67,40 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) dummyNode1.fSuccessfullyConnected = true; // This test requires that we have a chain with non-zero work. - LOCK(cs_main); - BOOST_CHECK(chainActive.Tip() != nullptr); - BOOST_CHECK(chainActive.Tip()->nChainWork > 0); + { + LOCK(cs_main); + BOOST_CHECK(chainActive.Tip() != nullptr); + BOOST_CHECK(chainActive.Tip()->nChainWork > 0); + } // Test starts here - LOCK(dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1, interruptDummy); // should result in getheaders - LOCK(dummyNode1.cs_vSend); - BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); - dummyNode1.vSendMsg.clear(); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); // should result in getheaders + } + { + LOCK2(cs_main, dummyNode1.cs_vSend); + BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); + dummyNode1.vSendMsg.clear(); + } int64_t nStartTime = GetTime(); // Wait 21 minutes SetMockTime(nStartTime+21*60); - peerLogic->SendMessages(&dummyNode1, interruptDummy); // should result in getheaders - BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); // should result in getheaders + } + { + LOCK2(cs_main, dummyNode1.cs_vSend); + BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); + } // Wait 3 more minutes SetMockTime(nStartTime+24*60); - peerLogic->SendMessages(&dummyNode1, interruptDummy); // should result in disconnect + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); // should result in disconnect + } BOOST_CHECK(dummyNode1.fDisconnect == true); SetMockTime(0); @@ -92,7 +108,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) peerLogic->FinalizeNode(dummyNode1.GetId(), dummy); } -void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic) +static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic) { CAddress addr(ip(GetRandInt(0xffffffff)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK|NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", /*fInboundIn=*/ false)); @@ -177,7 +193,6 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) BOOST_AUTO_TEST_CASE(DoS_banning) { - std::atomic<bool> interruptDummy(false); connman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -190,8 +205,10 @@ BOOST_AUTO_TEST_CASE(DoS_banning) LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 100); // Should get banned } - LOCK(dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1, interruptDummy); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); + } BOOST_CHECK(connman->IsBanned(addr1)); BOOST_CHECK(!connman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned @@ -205,15 +222,20 @@ BOOST_AUTO_TEST_CASE(DoS_banning) LOCK(cs_main); Misbehaving(dummyNode2.GetId(), 50); } - LOCK(dummyNode2.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode2, interruptDummy); + { + LOCK2(cs_main, dummyNode2.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode2); + } BOOST_CHECK(!connman->IsBanned(addr2)); // 2 not banned yet... BOOST_CHECK(connman->IsBanned(addr1)); // ... but 1 still should be { LOCK(cs_main); Misbehaving(dummyNode2.GetId(), 50); } - peerLogic->SendMessages(&dummyNode2, interruptDummy); + { + LOCK2(cs_main, dummyNode2.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode2); + } BOOST_CHECK(connman->IsBanned(addr2)); bool dummy; @@ -223,7 +245,6 @@ BOOST_AUTO_TEST_CASE(DoS_banning) BOOST_AUTO_TEST_CASE(DoS_banscore) { - std::atomic<bool> interruptDummy(false); connman->ClearBanned(); gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number @@ -237,20 +258,28 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 100); } - LOCK(dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1, interruptDummy); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); + } BOOST_CHECK(!connman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 10); } - peerLogic->SendMessages(&dummyNode1, interruptDummy); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); + } BOOST_CHECK(!connman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 1); } - peerLogic->SendMessages(&dummyNode1, interruptDummy); + { + LOCK2(cs_main, dummyNode1.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode1); + } BOOST_CHECK(connman->IsBanned(addr1)); gArgs.ForceSetArg("-banscore", std::to_string(DEFAULT_BANSCORE_THRESHOLD)); @@ -260,7 +289,6 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) BOOST_AUTO_TEST_CASE(DoS_bantime) { - std::atomic<bool> interruptDummy(false); connman->ClearBanned(); int64_t nStartTime = GetTime(); @@ -277,8 +305,10 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) LOCK(cs_main); Misbehaving(dummyNode.GetId(), 100); } - LOCK(dummyNode.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode, interruptDummy); + { + LOCK2(cs_main, dummyNode.cs_sendProcessing); + peerLogic->SendMessages(&dummyNode); + } BOOST_CHECK(connman->IsBanned(addr)); SetMockTime(nStartTime+60*60); @@ -291,7 +321,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) peerLogic->FinalizeNode(dummyNode.GetId(), dummy); } -CTransactionRef RandomOrphan() +static CTransactionRef RandomOrphan() { std::map<uint256, COrphanTx>::iterator it; LOCK(cs_main); diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index cd603b7f58..c065e25676 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -27,11 +27,21 @@ static void ResetArgs(const std::string& strArg) for (std::string& s : vecArg) vecChar.push_back(s.c_str()); - gArgs.ParseParameters(vecChar.size(), vecChar.data()); + std::string error; + gArgs.ParseParameters(vecChar.size(), vecChar.data(), error); +} + +static void SetupArgs(const std::vector<std::string>& args) +{ + gArgs.ClearArgs(); + for (const std::string& arg : args) { + gArgs.AddArg(arg, "", false, OptionsCategory::OPTIONS); + } } BOOST_AUTO_TEST_CASE(boolarg) { + SetupArgs({"-foo"}); ResetArgs("-foo"); BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); @@ -84,6 +94,7 @@ BOOST_AUTO_TEST_CASE(boolarg) BOOST_AUTO_TEST_CASE(stringarg) { + SetupArgs({"-foo", "-bar"}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); @@ -108,6 +119,7 @@ BOOST_AUTO_TEST_CASE(stringarg) BOOST_AUTO_TEST_CASE(intarg) { + SetupArgs({"-foo", "-bar"}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0); @@ -127,6 +139,7 @@ BOOST_AUTO_TEST_CASE(intarg) BOOST_AUTO_TEST_CASE(doubledash) { + SetupArgs({"-foo", "-bar"}); ResetArgs("--foo"); BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true); @@ -137,6 +150,7 @@ BOOST_AUTO_TEST_CASE(doubledash) BOOST_AUTO_TEST_CASE(boolargno) { + SetupArgs({"-foo", "-bar"}); ResetArgs("-nofoo"); BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); diff --git a/src/test/main_tests.cpp b/src/test/main_tests.cpp index 570c205731..8676a099da 100644 --- a/src/test/main_tests.cpp +++ b/src/test/main_tests.cpp @@ -58,8 +58,8 @@ BOOST_AUTO_TEST_CASE(subsidy_limit_test) BOOST_CHECK_EQUAL(nSum, CAmount{2099999997690000}); } -bool ReturnFalse() { return false; } -bool ReturnTrue() { return true; } +static bool ReturnFalse() { return false; } +static bool ReturnTrue() { return true; } BOOST_AUTO_TEST_CASE(test_combiner_all) { diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 37615d08b3..0264d29455 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) } template<typename name> -void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) +static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) { BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size()); typename CTxMemPool::indexed_transaction_set::index<name>::type::iterator it = pool.mapTx.get<name>().begin(); @@ -571,4 +571,179 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) SetMockTime(0); } +inline CTransactionRef make_tx(std::vector<CAmount>&& output_values, std::vector<CTransactionRef>&& inputs=std::vector<CTransactionRef>(), std::vector<uint32_t>&& input_indices=std::vector<uint32_t>()) +{ + CMutableTransaction tx = CMutableTransaction(); + tx.vin.resize(inputs.size()); + tx.vout.resize(output_values.size()); + for (size_t i = 0; i < inputs.size(); ++i) { + tx.vin[i].prevout.hash = inputs[i]->GetHash(); + tx.vin[i].prevout.n = input_indices.size() > i ? input_indices[i] : 0; + } + for (size_t i = 0; i < output_values.size(); ++i) { + tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL; + tx.vout[i].nValue = output_values[i]; + } + return MakeTransactionRef(tx); +} + + +BOOST_AUTO_TEST_CASE(MempoolAncestryTests) +{ + size_t ancestors, descendants; + + CTxMemPool pool; + TestMemPoolEntryHelper entry; + + /* Base transaction */ + // + // [tx1] + // + CTransactionRef tx1 = make_tx(/* output_values */ {10 * COIN}); + pool.addUnchecked(tx1->GetHash(), entry.Fee(10000LL).FromTx(tx1)); + + // Ancestors / descendants should be 1 / 1 (itself / itself) + pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 1ULL); + + /* Child transaction */ + // + // [tx1].0 <- [tx2] + // + CTransactionRef tx2 = make_tx(/* output_values */ {495 * CENT, 5 * COIN}, /* inputs */ {tx1}); + pool.addUnchecked(tx2->GetHash(), entry.Fee(10000LL).FromTx(tx2)); + + // Ancestors / descendants should be: + // transaction ancestors descendants + // ============ =========== =========== + // tx1 1 (tx1) 2 (tx1,2) + // tx2 2 (tx1,2) 2 (tx1,2) + pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 2ULL); + pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 2ULL); + + /* Grand-child 1 */ + // + // [tx1].0 <- [tx2].0 <- [tx3] + // + CTransactionRef tx3 = make_tx(/* output_values */ {290 * CENT, 200 * CENT}, /* inputs */ {tx2}); + pool.addUnchecked(tx3->GetHash(), entry.Fee(10000LL).FromTx(tx3)); + + // Ancestors / descendants should be: + // transaction ancestors descendants + // ============ =========== =========== + // tx1 1 (tx1) 3 (tx1,2,3) + // tx2 2 (tx1,2) 3 (tx1,2,3) + // tx3 3 (tx1,2,3) 3 (tx1,2,3) + pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 3ULL); + pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 3ULL); + pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 3ULL); + + /* Grand-child 2 */ + // + // [tx1].0 <- [tx2].0 <- [tx3] + // | + // \---1 <- [tx4] + // + CTransactionRef tx4 = make_tx(/* output_values */ {290 * CENT, 250 * CENT}, /* inputs */ {tx2}, /* input_indices */ {1}); + pool.addUnchecked(tx4->GetHash(), entry.Fee(10000LL).FromTx(tx4)); + + // Ancestors / descendants should be: + // transaction ancestors descendants + // ============ =========== =========== + // tx1 1 (tx1) 4 (tx1,2,3,4) + // tx2 2 (tx1,2) 4 (tx1,2,3,4) + // tx3 3 (tx1,2,3) 4 (tx1,2,3,4) + // tx4 3 (tx1,2,4) 4 (tx1,2,3,4) + pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + + /* Make an alternate branch that is longer and connect it to tx3 */ + // + // [ty1].0 <- [ty2].0 <- [ty3].0 <- [ty4].0 <- [ty5].0 + // | + // [tx1].0 <- [tx2].0 <- [tx3].0 <- [ty6] --->--/ + // | + // \---1 <- [tx4] + // + CTransactionRef ty1, ty2, ty3, ty4, ty5; + CTransactionRef* ty[5] = {&ty1, &ty2, &ty3, &ty4, &ty5}; + CAmount v = 5 * COIN; + for (uint64_t i = 0; i < 5; i++) { + CTransactionRef& tyi = *ty[i]; + tyi = make_tx(/* output_values */ {v}, /* inputs */ i > 0 ? std::vector<CTransactionRef>{*ty[i - 1]} : std::vector<CTransactionRef>{}); + v -= 50 * CENT; + pool.addUnchecked(tyi->GetHash(), entry.Fee(10000LL).FromTx(tyi)); + pool.GetTransactionAncestry(tyi->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, i+1); + BOOST_CHECK_EQUAL(descendants, i+1); + } + CTransactionRef ty6 = make_tx(/* output_values */ {5 * COIN}, /* inputs */ {tx3, ty5}); + pool.addUnchecked(ty6->GetHash(), entry.Fee(10000LL).FromTx(ty6)); + + // Ancestors / descendants should be: + // transaction ancestors descendants + // ============ =================== =========== + // tx1 1 (tx1) 5 (tx1,2,3,4, ty6) + // tx2 2 (tx1,2) 5 (tx1,2,3,4, ty6) + // tx3 3 (tx1,2,3) 5 (tx1,2,3,4, ty6) + // tx4 3 (tx1,2,4) 5 (tx1,2,3,4, ty6) + // ty1 1 (ty1) 6 (ty1,2,3,4,5,6) + // ty2 2 (ty1,2) 6 (ty1,2,3,4,5,6) + // ty3 3 (ty1,2,3) 6 (ty1,2,3,4,5,6) + // ty4 4 (y1234) 6 (ty1,2,3,4,5,6) + // ty5 5 (y12345) 6 (ty1,2,3,4,5,6) + // ty6 9 (tx123, ty123456) 6 (ty1,2,3,4,5,6) + pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 5ULL); + pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 5ULL); + pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 5ULL); + pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 5ULL); + pool.GetTransactionAncestry(ty1->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); + pool.GetTransactionAncestry(ty2->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); + pool.GetTransactionAncestry(ty3->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); + pool.GetTransactionAncestry(ty4->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 4ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); + pool.GetTransactionAncestry(ty5->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 5ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); + pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 9ULL); + BOOST_CHECK_EQUAL(descendants, 6ULL); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index 72a2672352..259e45dacf 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -9,6 +9,123 @@ BOOST_FIXTURE_TEST_SUITE(merkle_tests, TestingSetup) +static uint256 ComputeMerkleRootFromBranch(const uint256& leaf, const std::vector<uint256>& vMerkleBranch, uint32_t nIndex) { + 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)); + } else { + hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it)); + } + nIndex >>= 1; + } + return hash; +} + +/* This implements a constant-space merkle root/path calculator, limited to 2^32 leaves. */ +static void MerkleComputation(const std::vector<uint256>& leaves, uint256* proot, bool* pmutated, uint32_t branchpos, std::vector<uint256>* pbranch) { + if (pbranch) pbranch->clear(); + if (leaves.size() == 0) { + if (pmutated) *pmutated = false; + if (proot) *proot = uint256(); + return; + } + bool mutated = false; + // count is the number of leaves processed so far. + uint32_t count = 0; + // inner is an array of eagerly computed subtree hashes, indexed by tree + // level (0 being the leaves). + // For example, when count is 25 (11001 in binary), inner[4] is the hash of + // the first 16 leaves, inner[3] of the next 8 leaves, and inner[0] equal to + // the last leaf. The other inner entries are undefined. + uint256 inner[32]; + // Which position in inner is a hash that depends on the matching leaf. + int matchlevel = -1; + // First process all leaves into 'inner' values. + while (count < leaves.size()) { + uint256 h = leaves[count]; + bool matchh = count == branchpos; + count++; + int level; + // For each of the lower bits in count that are 0, do 1 step. Each + // corresponds to an inner value that existed before processing the + // current leaf, and each needs a hash to combine it. + for (level = 0; !(count & (((uint32_t)1) << level)); level++) { + if (pbranch) { + if (matchh) { + pbranch->push_back(inner[level]); + } else if (matchlevel == level) { + pbranch->push_back(h); + matchh = true; + } + } + mutated |= (inner[level] == h); + CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); + } + // Store the resulting hash at inner position level. + inner[level] = h; + if (matchh) { + matchlevel = level; + } + } + // Do a final 'sweep' over the rightmost branch of the tree to process + // odd levels, and reduce everything to a single top value. + // Level is the level (counted from the bottom) up to which we've sweeped. + int level = 0; + // As long as bit number level in count is zero, skip it. It means there + // is nothing left at this level. + while (!(count & (((uint32_t)1) << level))) { + level++; + } + uint256 h = inner[level]; + bool matchh = matchlevel == level; + while (count != (((uint32_t)1) << level)) { + // If we reach this point, h is an inner value that is not the top. + // We combine it with itself (Bitcoin's special rule for odd levels in + // the tree) to produce a higher level one. + if (pbranch && matchh) { + pbranch->push_back(h); + } + CHash256().Write(h.begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); + // Increment count to the value it would have if two entries at this + // level had existed. + count += (((uint32_t)1) << level); + level++; + // And propagate the result upwards accordingly. + while (!(count & (((uint32_t)1) << level))) { + if (pbranch) { + if (matchh) { + pbranch->push_back(inner[level]); + } else if (matchlevel == level) { + pbranch->push_back(h); + matchh = true; + } + } + CHash256().Write(inner[level].begin(), 32).Write(h.begin(), 32).Finalize(h.begin()); + level++; + } + } + // Return result. + if (pmutated) *pmutated = mutated; + if (proot) *proot = h; +} + +static std::vector<uint256> ComputeMerkleBranch(const std::vector<uint256>& leaves, uint32_t position) { + std::vector<uint256> ret; + MerkleComputation(leaves, nullptr, nullptr, position, &ret); + return ret; +} + +static std::vector<uint256> BlockMerkleBranch(const CBlock& block, uint32_t position) +{ + std::vector<uint256> leaves; + leaves.resize(block.vtx.size()); + for (size_t s = 0; s < block.vtx.size(); s++) { + leaves[s] = block.vtx[s]->GetHash(); + } + return ComputeMerkleBranch(leaves, position); +} + // Older version of the merkle root computation code, for comparison. static uint256 BlockBuildMerkleTree(const CBlock& block, bool* fMutated, std::vector<uint256>& vMerkleTree) { diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index c98566f9ca..9a325f5f4c 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -82,7 +82,7 @@ struct { {2, 0xbbbeb305}, {2, 0xfe1c810a}, }; -CBlockIndex CreateBlockIndex(int nHeight) +static CBlockIndex CreateBlockIndex(int nHeight) { CBlockIndex index; index.nHeight = nHeight; @@ -90,7 +90,7 @@ CBlockIndex CreateBlockIndex(int nHeight) return index; } -bool TestSequenceLocks(const CTransaction &tx, int flags) +static bool TestSequenceLocks(const CTransaction &tx, int flags) { LOCK(mempool.cs); return CheckSequenceLocks(tx, flags); @@ -99,7 +99,7 @@ bool TestSequenceLocks(const CTransaction &tx, int 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. -void TestPackageSelection(const CChainParams& chainparams, CScript scriptPubKey, std::vector<CTransactionRef>& txFirst) +static void TestPackageSelection(const CChainParams& chainparams, CScript scriptPubKey, std::vector<CTransactionRef>& txFirst) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 066f6328a6..77db9f5c57 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(multisig_tests, BasicTestingSetup) -CScript +static CScript sign_multisig(const CScript& scriptPubKey, const std::vector<CKey>& keys, const CTransaction& transaction, int whichIn) { uint256 hash = SignatureHash(scriptPubKey, transaction, whichIn, SIGHASH_ALL, 0, SigVersion::BASE); diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 436ae696d1..2a77e0e1ec 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -63,7 +63,7 @@ public: } }; -CDataStream AddrmanToStream(CAddrManSerializationMock& _addrman) +static CDataStream AddrmanToStream(CAddrManSerializationMock& _addrman) { CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION); ssPeersIn << Params().MessageStart(); @@ -89,6 +89,7 @@ BOOST_AUTO_TEST_CASE(cnode_listen_port) BOOST_AUTO_TEST_CASE(caddrdb_read) { + SetDataDir("caddrdb_read"); CAddrManUncorrupted addrmanUncorrupted; addrmanUncorrupted.MakeDeterministic(); @@ -134,6 +135,7 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) { + SetDataDir("caddrdb_read_corrupted"); CAddrManCorrupted addrmanCorrupted; addrmanCorrupted.MakeDeterministic(); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 027214e512..bc90e5ae09 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -39,7 +39,7 @@ BOOST_AUTO_TEST_CASE(netbase_networks) BOOST_CHECK(ResolveIP("::1").GetNetwork() == NET_UNROUTABLE); BOOST_CHECK(ResolveIP("8.8.8.8").GetNetwork() == NET_IPV4); BOOST_CHECK(ResolveIP("2001::8888").GetNetwork() == NET_IPV6); - BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_TOR); + BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_ONION); BOOST_CHECK(CreateInternal("foo.com").GetNetwork() == NET_INTERNAL); } @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE(netbase_getgroup) BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052 BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964 BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380 - BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_TOR, 239})); // Tor + BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor BOOST_CHECK(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net BOOST_CHECK(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6 diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_p2sh_tests.cpp index 63d211dd97..e224df6704 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -46,7 +46,7 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, Scri } -BOOST_FIXTURE_TEST_SUITE(script_P2SH_tests, BasicTestingSetup) +BOOST_FIXTURE_TEST_SUITE(script_p2sh_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sign) { @@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) // vout[5/6] are non-standard because they exceed MAX_P2SH_SIGOPS CScript sixteenSigops; sixteenSigops << OP_16 << OP_CHECKMULTISIG; keystore.AddCScript(sixteenSigops); - txFrom.vout[5].scriptPubKey = GetScriptForDestination(CScriptID(fifteenSigops)); + txFrom.vout[5].scriptPubKey = GetScriptForDestination(CScriptID(sixteenSigops)); txFrom.vout[5].nValue = 5000; CScript twentySigops; twentySigops << OP_CHECKMULTISIG; keystore.AddCScript(twentySigops); diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 767c5fdbd2..7d4734986a 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -398,106 +398,149 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript scriptPubKey; isminetype result; - bool isInvalid; // P2PK compressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << ToByteVector(pubkeys[0]) << OP_CHECKSIG; + scriptPubKey = GetScriptForRawPubKey(pubkeys[0]); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PK uncompressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << ToByteVector(uncompressedPubkey) << OP_CHECKSIG; + scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PKH compressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + scriptPubKey = GetScriptForDestination(pubkeys[0].GetID()); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2PKH uncompressed { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_DUP << OP_HASH160 << ToByteVector(uncompressedPubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; + scriptPubKey = GetScriptForDestination(uncompressedPubkey.GetID()); // Keystore does not have key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2SH { CBasicKeyStore keystore; - CScript redeemScript; - redeemScript << OP_DUP << OP_HASH160 << ToByteVector(pubkeys[0].GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; - - scriptPubKey.clear(); - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + CScript redeemScript = GetScriptForDestination(pubkeys[0].GetID()); + scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); // Keystore does not have redeemScript or key - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript but no key keystore.AddCScript(redeemScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript and key keystore.AddKey(keys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); + } + + // (P2PKH inside) P2SH inside P2SH (invalid) + { + CBasicKeyStore keystore; + + CScript redeemscript_inner = GetScriptForDestination(pubkeys[0].GetID()); + CScript redeemscript = GetScriptForDestination(CScriptID(redeemscript_inner)); + scriptPubKey = GetScriptForDestination(CScriptID(redeemscript)); + + keystore.AddCScript(redeemscript); + keystore.AddCScript(redeemscript_inner); + keystore.AddCScript(scriptPubKey); + keystore.AddKey(keys[0]); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + } + + // (P2PKH inside) P2SH inside P2WSH (invalid) + { + CBasicKeyStore keystore; + + CScript redeemscript = GetScriptForDestination(pubkeys[0].GetID()); + CScript witnessscript = GetScriptForDestination(CScriptID(redeemscript)); + scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); + + keystore.AddCScript(witnessscript); + keystore.AddCScript(redeemscript); + keystore.AddCScript(scriptPubKey); + keystore.AddKey(keys[0]); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + } + + // P2WPKH inside P2WSH (invalid) + { + CBasicKeyStore keystore; + + CScript witnessscript = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0].GetID())); + scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); + + keystore.AddCScript(witnessscript); + keystore.AddCScript(scriptPubKey); + keystore.AddKey(keys[0]); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + } + + // (P2PKH inside) P2WSH inside P2WSH (invalid) + { + CBasicKeyStore keystore; + + CScript witnessscript_inner = GetScriptForDestination(pubkeys[0].GetID()); + CScript witnessscript = GetScriptForDestination(WitnessV0ScriptHash(witnessscript_inner)); + scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); + + keystore.AddCScript(witnessscript_inner); + keystore.AddCScript(witnessscript); + keystore.AddCScript(scriptPubKey); + keystore.AddKey(keys[0]); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); } // P2WPKH compressed @@ -505,14 +548,12 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CBasicKeyStore keystore; keystore.AddKey(keys[0]); - scriptPubKey.clear(); - scriptPubKey << OP_0 << ToByteVector(pubkeys[0].GetID()); + scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0].GetID())); // Keystore implicitly has key and P2SH redeemScript keystore.AddCScript(scriptPubKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2WPKH uncompressed @@ -520,49 +561,45 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CBasicKeyStore keystore; keystore.AddKey(uncompressedKey); - scriptPubKey.clear(); - scriptPubKey << OP_0 << ToByteVector(uncompressedPubkey.GetID()); + scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(uncompressedPubkey.GetID())); // Keystore has key, but no P2SH redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has key and P2SH redeemScript keystore.AddCScript(scriptPubKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(isInvalid); } // scriptPubKey multisig { CBasicKeyStore keystore; - scriptPubKey.clear(); - scriptPubKey << OP_2 << - ToByteVector(uncompressedPubkey) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; + scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); // Keystore does not have any keys - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has 1/2 keys keystore.AddKey(uncompressedKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has 2/2 keys keystore.AddKey(keys[1]); - result = IsMine(keystore, scriptPubKey, isInvalid); - BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + + // Keystore has 2/2 keys and the script + keystore.AddCScript(scriptPubKey); + + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); } // P2SH multisig @@ -571,25 +608,17 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) keystore.AddKey(uncompressedKey); keystore.AddKey(keys[1]); - CScript redeemScript; - redeemScript << OP_2 << - ToByteVector(uncompressedPubkey) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; - - scriptPubKey.clear(); - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); + scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); // Keystore has no redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has redeemScript keystore.AddCScript(redeemScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2WSH multisig with compressed keys @@ -598,35 +627,22 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) keystore.AddKey(keys[0]); keystore.AddKey(keys[1]); - CScript witnessScript; - witnessScript << OP_2 << - ToByteVector(pubkeys[0]) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; - - uint256 scriptHash; - CSHA256().Write(&witnessScript[0], witnessScript.size()) - .Finalize(scriptHash.begin()); - - scriptPubKey.clear(); - scriptPubKey << OP_0 << ToByteVector(scriptHash); + CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]}); + scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); // Keystore has keys, but no witnessScript or P2SH redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has keys and witnessScript, but no P2SH redeemScript keystore.AddCScript(witnessScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has keys, witnessScript, P2SH redeemScript keystore.AddCScript(scriptPubKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // P2WSH multisig with uncompressed key @@ -635,75 +651,47 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) keystore.AddKey(uncompressedKey); keystore.AddKey(keys[1]); - CScript witnessScript; - witnessScript << OP_2 << - ToByteVector(uncompressedPubkey) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; - - uint256 scriptHash; - CSHA256().Write(&witnessScript[0], witnessScript.size()) - .Finalize(scriptHash.begin()); - - scriptPubKey.clear(); - scriptPubKey << OP_0 << ToByteVector(scriptHash); + CScript witnessScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); + scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); // Keystore has keys, but no witnessScript or P2SH redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has keys and witnessScript, but no P2SH redeemScript keystore.AddCScript(witnessScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has keys, witnessScript, P2SH redeemScript keystore.AddCScript(scriptPubKey); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(isInvalid); } // P2WSH multisig wrapped in P2SH { CBasicKeyStore keystore; - CScript witnessScript; - witnessScript << OP_2 << - ToByteVector(pubkeys[0]) << - ToByteVector(pubkeys[1]) << - OP_2 << OP_CHECKMULTISIG; - - uint256 scriptHash; - CSHA256().Write(&witnessScript[0], witnessScript.size()) - .Finalize(scriptHash.begin()); - - CScript redeemScript; - redeemScript << OP_0 << ToByteVector(scriptHash); - - scriptPubKey.clear(); - scriptPubKey << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; + CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]}); + CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); + scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); // Keystore has no witnessScript, P2SH redeemScript, or keys - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has witnessScript and P2SH redeemScript, but no keys keystore.AddCScript(redeemScript); keystore.AddCScript(witnessScript); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); // Keystore has keys, witnessScript, P2SH redeemScript keystore.AddKey(keys[0]); keystore.AddKey(keys[1]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); - BOOST_CHECK(!isInvalid); } // OP_RETURN @@ -714,9 +702,32 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) scriptPubKey.clear(); scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]); - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + } + + // witness unspendable + { + CBasicKeyStore keystore; + keystore.AddKey(keys[0]); + + scriptPubKey.clear(); + scriptPubKey << OP_0 << ToByteVector(ParseHex("aabb")); + + result = IsMine(keystore, scriptPubKey); + BOOST_CHECK_EQUAL(result, ISMINE_NO); + } + + // witness unknown + { + CBasicKeyStore keystore; + keystore.AddKey(keys[0]); + + scriptPubKey.clear(); + scriptPubKey << OP_16 << ToByteVector(ParseHex("aabb")); + + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); } // Nonstandard @@ -727,9 +738,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) scriptPubKey.clear(); scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL; - result = IsMine(keystore, scriptPubKey, isInvalid); + result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); } } diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 068f1e66f4..543b21e8a6 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -97,9 +97,11 @@ static ScriptErrorDesc script_errors[]={ {SCRIPT_ERR_WITNESS_MALLEATED_P2SH, "WITNESS_MALLEATED_P2SH"}, {SCRIPT_ERR_WITNESS_UNEXPECTED, "WITNESS_UNEXPECTED"}, {SCRIPT_ERR_WITNESS_PUBKEYTYPE, "WITNESS_PUBKEYTYPE"}, + {SCRIPT_ERR_OP_CODESEPARATOR, "OP_CODESEPARATOR"}, + {SCRIPT_ERR_SIG_FINDANDDELETE, "SIG_FINDANDDELETE"}, }; -const char *FormatScriptError(ScriptError_t err) +static const char *FormatScriptError(ScriptError_t err) { for (unsigned int i=0; i<ARRAYLEN(script_errors); ++i) if (script_errors[i].err == err) @@ -108,7 +110,7 @@ const char *FormatScriptError(ScriptError_t err) return ""; } -ScriptError_t ParseScriptError(const std::string &name) +static ScriptError_t ParseScriptError(const std::string &name) { for (unsigned int i=0; i<ARRAYLEN(script_errors); ++i) if (script_errors[i].name == name) @@ -135,7 +137,7 @@ CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey, int n return txCredit; } -CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CScriptWitness& scriptWitness, const CMutableTransaction& txCredit) +CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CScriptWitness& scriptWitness, const CTransaction& txCredit) { CMutableTransaction txSpend; txSpend.nVersion = 1; @@ -161,7 +163,7 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, const CScript flags |= SCRIPT_VERIFY_WITNESS; } ScriptError err; - CMutableTransaction txCredit = BuildCreditingTransaction(scriptPubKey, nValue); + const CTransaction txCredit{BuildCreditingTransaction(scriptPubKey, nValue)}; CMutableTransaction tx = BuildSpendingTransaction(scriptSig, scriptWitness, txCredit); CMutableTransaction tx2 = tx; BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, &scriptWitness, flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue), &err) == expect, message); @@ -1028,7 +1030,7 @@ BOOST_AUTO_TEST_CASE(script_PushData) BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } -CScript +static CScript sign_multisig(const CScript& scriptPubKey, const std::vector<CKey>& keys, const CTransaction& transaction) { uint256 hash = SignatureHash(scriptPubKey, transaction, 0, SIGHASH_ALL, 0, SigVersion::BASE); @@ -1052,7 +1054,7 @@ sign_multisig(const CScript& scriptPubKey, const std::vector<CKey>& keys, const } return result; } -CScript +static CScript sign_multisig(const CScript& scriptPubKey, const CKey& key, const CTransaction& transaction) { std::vector<CKey> keys; @@ -1071,7 +1073,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) CScript scriptPubKey12; scriptPubKey12 << OP_1 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << OP_2 << OP_CHECKMULTISIG; - CMutableTransaction txFrom12 = BuildCreditingTransaction(scriptPubKey12); + const CTransaction txFrom12{BuildCreditingTransaction(scriptPubKey12)}; CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), CScriptWitness(), txFrom12); CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12); @@ -1102,7 +1104,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) CScript scriptPubKey23; scriptPubKey23 << OP_2 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << ToByteVector(key3.GetPubKey()) << OP_3 << OP_CHECKMULTISIG; - CMutableTransaction txFrom23 = BuildCreditingTransaction(scriptPubKey23); + const CTransaction txFrom23{BuildCreditingTransaction(scriptPubKey23)}; CMutableTransaction txTo23 = BuildSpendingTransaction(CScript(), CScriptWitness(), txFrom23); std::vector<CKey> keys; @@ -1159,10 +1161,19 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } +/* Wrapper around ProduceSignature to combine two scriptsigs */ +SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& tx, const SignatureData& scriptSig1, const SignatureData& scriptSig2) +{ + SignatureData data; + data.MergeSignatureData(scriptSig1); + data.MergeSignatureData(scriptSig2); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue), txout.scriptPubKey, data); + return data; +} + BOOST_AUTO_TEST_CASE(script_combineSigs) { - // Test the CombineSignatures function - CAmount amount = 0; + // Test the ProduceSignature's ability to combine signatures function CBasicKeyStore keystore; std::vector<CKey> keys; std::vector<CPubKey> pubkeys; @@ -1178,52 +1189,51 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) CMutableTransaction txFrom = BuildCreditingTransaction(GetScriptForDestination(keys[0].GetPubKey().GetID())); CMutableTransaction txTo = BuildSpendingTransaction(CScript(), CScriptWitness(), txFrom); CScript& scriptPubKey = txFrom.vout[0].scriptPubKey; - CScript& scriptSig = txTo.vin[0].scriptSig; + SignatureData scriptSig; SignatureData empty; - SignatureData combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, empty); + SignatureData combined = CombineSignatures(txFrom.vout[0], txTo, empty, empty); BOOST_CHECK(combined.scriptSig.empty()); // Single signature case: SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); // changes scriptSig - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); - CScript scriptSigCopy = scriptSig; + 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); + SignatureData scriptSigCopy = scriptSig; // Signing again will give a different, valid signature: SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); + 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); scriptPubKey = GetScriptForDestination(CScriptID(pkSingle)); SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); + 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); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); - // dummy scriptSigCopy with placeholder, should always choose non-placeholder: - scriptSigCopy = CScript() << OP_0 << std::vector<unsigned char>(pkSingle.begin(), pkSingle.end()); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), SignatureData(scriptSigCopy)); - BOOST_CHECK(combined.scriptSig == scriptSig); + 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); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); - BOOST_CHECK(combined.scriptSig == scriptSig); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); - BOOST_CHECK(combined.scriptSig == scriptSig); + 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); // A couple of partially-signed versions: std::vector<unsigned char> sig1; @@ -1250,22 +1260,28 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) CScript complete12 = CScript() << OP_0 << sig1 << sig2; CScript complete13 = CScript() << OP_0 << sig1 << sig3; CScript complete23 = CScript() << OP_0 << sig2 << sig3; - - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial1b)); + SignatureData partial1_sigs; + partial1_sigs.signatures.emplace(keys[0].GetPubKey().GetID(), SigPair(keys[0].GetPubKey(), sig1)); + SignatureData partial2_sigs; + partial2_sigs.signatures.emplace(keys[1].GetPubKey().GetID(), SigPair(keys[1].GetPubKey(), sig2)); + SignatureData partial3_sigs; + partial3_sigs.signatures.emplace(keys[2].GetPubKey().GetID(), SigPair(keys[2].GetPubKey(), sig3)); + + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == partial1a); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial2a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial1a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial2_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1b), SignatureData(partial2b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial1_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete12); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial1b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial1_sigs); BOOST_CHECK(combined.scriptSig == complete13); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial3a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial2_sigs, partial3_sigs); BOOST_CHECK(combined.scriptSig == complete23); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial2b)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial2_sigs); BOOST_CHECK(combined.scriptSig == complete23); - combined = CombineSignatures(scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial3a)); + combined = CombineSignatures(txFrom.vout[0], txTo, partial3_sigs, partial3_sigs); BOOST_CHECK(combined.scriptSig == partial3c); } @@ -1479,4 +1495,146 @@ BOOST_AUTO_TEST_CASE(script_can_append_self) BOOST_CHECK(s == d); } + +#if defined(HAVE_CONSENSUS_LIB) + +/* Test simple (successful) usage of bitcoinconsensus_verify_script */ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_returns_true) +{ + unsigned int libconsensus_flags = 0; + int nIn = 0; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_1; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << spendTx; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 1); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_OK); +} + +/* Test bitcoinconsensus_verify_script returns invalid tx index err*/ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_index_err) +{ + unsigned int libconsensus_flags = 0; + int nIn = 3; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_EQUAL; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << spendTx; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 0); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_INDEX); +} + +/* Test bitcoinconsensus_verify_script returns tx size mismatch err*/ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_size) +{ + unsigned int libconsensus_flags = 0; + int nIn = 0; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_EQUAL; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << spendTx; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size() * 2, nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 0); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_SIZE_MISMATCH); +} + +/* Test bitcoinconsensus_verify_script returns invalid tx serialization error */ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_serialization) +{ + unsigned int libconsensus_flags = 0; + int nIn = 0; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_EQUAL; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << 0xffffffff; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 0); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_DESERIALIZE); +} + +/* Test bitcoinconsensus_verify_script returns amount required error */ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_amount_required_err) +{ + unsigned int libconsensus_flags = bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS; + int nIn = 0; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_EQUAL; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << spendTx; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 0); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED); +} + +/* Test bitcoinconsensus_verify_script returns invalid flags err */ +BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags) +{ + unsigned int libconsensus_flags = 1 << 3; + int nIn = 0; + + CScript scriptPubKey; + CScript scriptSig; + CScriptWitness wit; + + scriptPubKey << OP_EQUAL; + CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); + CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << spendTx; + + bitcoinconsensus_error err; + int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err); + BOOST_CHECK_EQUAL(result, 0); + BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS); +} + +#endif BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index b4d8a2419e..86647e72eb 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) * Verifies script execution of the zeroth scriptPubKey of tx output and * zeroth scriptSig and witness of tx input. */ -ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction& input, int flags) +static ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction& input, int flags) { ScriptError error; CTransaction inputi(input); @@ -82,7 +82,7 @@ ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction * and witness such that spendingTx spends output zero of creationTx. * Also inserts creationTx's output into the coins view. */ -void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableTransaction& creationTx, const CScript& scriptPubKey, const CScript& scriptSig, const CScriptWitness& witness) +static void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableTransaction& creationTx, const CScript& scriptPubKey, const CScript& scriptSig, const CScriptWitness& witness) { creationTx.nVersion = 1; creationTx.vin.resize(1); diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 5d057108b1..e1e77f7c92 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -6,11 +6,8 @@ #include <support/allocators/zeroafterfree.h> #include <test/test_bitcoin.h> -#include <boost/assign/std/vector.hpp> // for 'operator+=()' #include <boost/test/unit_test.hpp> -using namespace boost::assign; // bring 'operator+=()' into scope - BOOST_FIXTURE_TEST_SUITE(streams_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(streams_vector_writer) @@ -80,14 +77,17 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) // Degenerate case - key += '\x00','\x00'; + key.push_back('\x00'); + key.push_back('\x00'); ds.Xor(key); BOOST_CHECK_EQUAL( std::string(expected_xor.begin(), expected_xor.end()), std::string(ds.begin(), ds.end())); - in += '\x0f','\xf0'; - expected_xor += '\xf0','\x0f'; + in.push_back('\x0f'); + in.push_back('\xf0'); + expected_xor.push_back('\xf0'); + expected_xor.push_back('\x0f'); // Single character key @@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) ds.insert(ds.begin(), in.begin(), in.end()); key.clear(); - key += '\xff'; + key.push_back('\xff'); ds.Xor(key); BOOST_CHECK_EQUAL( std::string(expected_xor.begin(), expected_xor.end()), @@ -105,14 +105,17 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) in.clear(); expected_xor.clear(); - in += '\xf0','\x0f'; - expected_xor += '\x0f','\x00'; + in.push_back('\xf0'); + in.push_back('\x0f'); + expected_xor.push_back('\x0f'); + expected_xor.push_back('\x00'); ds.clear(); ds.insert(ds.begin(), in.begin(), in.end()); key.clear(); - key += '\xff','\x0f'; + key.push_back('\xff'); + key.push_back('\x0f'); ds.Xor(key); BOOST_CHECK_EQUAL( diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index e9873f4526..b35b21335e 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -11,6 +11,7 @@ #include <validation.h> #include <miner.h> #include <net_processing.h> +#include <pow.h> #include <ui_interface.h> #include <streams.h> #include <rpc/server.h> @@ -38,37 +39,50 @@ FastRandomContext insecure_rand_ctx(insecure_rand_seed); extern bool fPrintToConsole; extern void noui_connect(); +std::ostream& operator<<(std::ostream& os, const uint256& num) +{ + os << num.ToString(); + return os; +} + 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(); - InitSignatureCache(); - InitScriptExecutionCache(); - fPrintToDebugLog = false; // don't want to write to debug.log file - fCheckBlockIndex = true; - SelectParams(chainName); - noui_connect(); + SHA256AutoDetect(); + RandomInit(); + ECC_Start(); + SetupEnvironment(); + SetupNetworking(); + InitSignatureCache(); + InitScriptExecutionCache(); + fCheckBlockIndex = true; + SelectParams(chainName); + noui_connect(); } BasicTestingSetup::~BasicTestingSetup() { - ECC_Stop(); + fs::remove_all(m_path_root); + ECC_Stop(); +} + +fs::path BasicTestingSetup::SetDataDir(const std::string& name) +{ + fs::path ret = m_path_root / name; + fs::create_directories(ret); + gArgs.ForceSetArg("-datadir", ret.string()); + return ret; } TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(chainName) { + SetDataDir("tempdir"); const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. RegisterAllCoreRPCCommands(tableRPC); ClearDatadirCache(); - pathTemp = fs::temp_directory_path() / strprintf("test_bitcoin_%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(1 << 30))); - fs::create_directories(pathTemp); - gArgs.ForceSetArg("-datadir", pathTemp.string()); // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. @@ -93,7 +107,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha threadGroup.create_thread(&ThreadScriptCheck); g_connman = std::unique_ptr<CConnman>(new CConnman(0x1337, 0x1337)); // Deterministic randomness for tests. connman = g_connman.get(); - peerLogic.reset(new PeerLogicValidation(connman, scheduler)); + peerLogic.reset(new PeerLogicValidation(connman, scheduler, /*enable_bip61=*/true)); } TestingSetup::~TestingSetup() @@ -108,7 +122,6 @@ TestingSetup::~TestingSetup() pcoinsTip.reset(); pcoinsdbview.reset(); pblocktree.reset(); - fs::remove_all(pathTemp); } TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index 1f91eb622c..88b2d37e87 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -45,6 +45,11 @@ struct BasicTestingSetup { explicit BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~BasicTestingSetup(); + + fs::path SetDataDir(const std::string& name); + +private: + const fs::path m_path_root; }; /** Testing setup that configures a complete environment. @@ -59,7 +64,6 @@ struct CConnmanTest { class PeerLogicValidation; struct TestingSetup: public BasicTestingSetup { - fs::path pathTemp; boost::thread_group threadGroup; CConnman* connman; CScheduler scheduler; @@ -120,4 +124,7 @@ struct TestMemPoolEntryHelper CBlock getBlock13b8a(); +// define an implicit conversion here so that uint256 may be used directly in BOOST_CHECK_* +std::ostream& operator<<(std::ostream& os, const uint256& num); + #endif diff --git a/src/test/test_bitcoin_fuzzy.cpp b/src/test/test_bitcoin_fuzzy.cpp index 69e9804c2f..b2daa2adb5 100644 --- a/src/test/test_bitcoin_fuzzy.cpp +++ b/src/test/test_bitcoin_fuzzy.cpp @@ -52,7 +52,7 @@ enum TEST_ID { TEST_ID_END }; -bool read_stdin(std::vector<uint8_t> &data) { +static bool read_stdin(std::vector<uint8_t> &data) { uint8_t buffer[1024]; ssize_t length=0; while((length = read(STDIN_FILENO, buffer, 1024)) > 0) { @@ -63,7 +63,7 @@ bool read_stdin(std::vector<uint8_t> &data) { return length==0; } -int test_one_input(std::vector<uint8_t> buffer) { +static int test_one_input(std::vector<uint8_t> buffer) { if (buffer.size() < sizeof(uint32_t)) return 0; uint32_t test_id = 0xffffffff; diff --git a/src/test/torcontrol_tests.cpp b/src/test/torcontrol_tests.cpp index 9ece9e70c2..c7ceb2f1e9 100644 --- a/src/test/torcontrol_tests.cpp +++ b/src/test/torcontrol_tests.cpp @@ -3,14 +3,22 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <test/test_bitcoin.h> -#include <torcontrol.cpp> +#include <torcontrol.h> #include <boost/test/unit_test.hpp> +#include <map> +#include <string> +#include <utility> + + +std::pair<std::string, std::string> SplitTorReplyLine(const std::string& s); +std::map<std::string, std::string> ParseTorReplyMapping(const std::string& s); + BOOST_FIXTURE_TEST_SUITE(torcontrol_tests, BasicTestingSetup) -void CheckSplitTorReplyLine(std::string input, std::string command, std::string args) +static void CheckSplitTorReplyLine(std::string input, std::string command, std::string args) { BOOST_TEST_MESSAGE(std::string("CheckSplitTorReplyLine(") + input + ")"); auto ret = SplitTorReplyLine(input); @@ -51,7 +59,7 @@ BOOST_AUTO_TEST_CASE(util_SplitTorReplyLine) CheckSplitTorReplyLine("COMMAND EVEN+more ARGS", "COMMAND", " EVEN+more ARGS"); } -void CheckParseTorReplyMapping(std::string input, std::map<std::string,std::string> expected) +static void CheckParseTorReplyMapping(std::string input, std::map<std::string,std::string> expected) { BOOST_TEST_MESSAGE(std::string("CheckParseTorReplyMapping(") + input + ")"); auto ret = ParseTorReplyMapping(input); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index b222392ee5..45dc0e3571 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -53,6 +53,7 @@ static std::map<std::string, unsigned int> mapFlagNames = { {std::string("WITNESS"), (unsigned int)SCRIPT_VERIFY_WITNESS}, {std::string("DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM}, {std::string("WITNESS_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_WITNESS_PUBKEYTYPE}, + {std::string("CONST_SCRIPTCODE"), (unsigned int)SCRIPT_VERIFY_CONST_SCRIPTCODE}, }; unsigned int ParseScriptFlags(std::string strFlags) @@ -343,7 +344,7 @@ BOOST_AUTO_TEST_CASE(test_Get) BOOST_CHECK_EQUAL(coins.GetValueIn(t1), (50+21+22)*CENT); } -void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) +static void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) { CMutableTransaction outputm; outputm.nVersion = 1; @@ -381,7 +382,7 @@ void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, C assert(input.vin[0].scriptWitness.stack == inputm.vin[0].scriptWitness.stack); } -void CheckWithFlag(const CTransactionRef& output, const CMutableTransaction& input, int flags, bool success) +static void CheckWithFlag(const CTransactionRef& output, const CMutableTransaction& input, int flags, bool success) { ScriptError error; CTransaction inputi(input); @@ -404,7 +405,7 @@ static CScript PushAll(const std::vector<valtype>& values) return result; } -void ReplaceRedeemScript(CScript& script, const CScript& redeemScript) +static void ReplaceRedeemScript(CScript& script, const CScript& redeemScript) { std::vector<valtype> stack; EvalScript(stack, script, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker(), SigVersion::BASE); @@ -493,6 +494,15 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { threadGroup.join_all(); } +SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutableTransaction& input2, const CTransactionRef tx) +{ + SignatureData sigdata; + sigdata = DataFromTransaction(input1, 0, tx->vout[0]); + sigdata.MergeSignatureData(DataFromTransaction(input2, 0, tx->vout[0])); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&input1, 0, tx->vout[0].nValue), tx->vout[0].scriptPubKey, sigdata); + return sigdata; +} + BOOST_AUTO_TEST_CASE(test_witness) { CBasicKeyStore keystore, keystore2; @@ -628,7 +638,7 @@ BOOST_AUTO_TEST_CASE(test_witness) CreateCreditAndSpend(keystore2, scriptMulti, output2, input2, false); CheckWithFlag(output2, input2, 0, false); BOOST_CHECK(*output1 == *output2); - UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0))); + UpdateInput(input1.vin[0], CombineSignatures(input1, input2, output1)); CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true); // P2SH 2-of-2 multisig @@ -639,7 +649,7 @@ BOOST_AUTO_TEST_CASE(test_witness) CheckWithFlag(output2, input2, 0, true); CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH, false); BOOST_CHECK(*output1 == *output2); - UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0))); + UpdateInput(input1.vin[0], CombineSignatures(input1, input2, output1)); CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH, true); CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true); @@ -651,7 +661,7 @@ BOOST_AUTO_TEST_CASE(test_witness) CheckWithFlag(output2, input2, 0, true); CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false); BOOST_CHECK(*output1 == *output2); - UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0))); + UpdateInput(input1.vin[0], CombineSignatures(input1, input2, output1)); CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true); CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true); @@ -663,7 +673,7 @@ BOOST_AUTO_TEST_CASE(test_witness) CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH, true); CheckWithFlag(output2, input2, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false); BOOST_CHECK(*output1 == *output2); - UpdateTransaction(input1, 0, CombineSignatures(output1->vout[0].scriptPubKey, MutableTransactionSignatureChecker(&input1, 0, output1->vout[0].nValue), DataFromTransaction(input1, 0), DataFromTransaction(input2, 0))); + UpdateInput(input1.vin[0], CombineSignatures(input1, input2, output1)); CheckWithFlag(output1, input1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true); CheckWithFlag(output1, input1, STANDARD_SCRIPT_VERIFY_FLAGS, true); } diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp new file mode 100644 index 0000000000..be7ee2428b --- /dev/null +++ b/src/test/txindex_tests.cpp @@ -0,0 +1,66 @@ +// 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. + +#include <index/txindex.h> +#include <script/standard.h> +#include <test/test_bitcoin.h> +#include <util.h> +#include <utiltime.h> +#include <validation.h> + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(txindex_tests) + +BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) +{ + TxIndex txindex(1 << 20, true); + + CTransactionRef tx_disk; + uint256 block_hash; + + // Transaction should not be found in the index before it is started. + for (const auto& txn : m_coinbase_txns) { + BOOST_CHECK(!txindex.FindTx(txn->GetHash(), block_hash, tx_disk)); + } + + // BlockUntilSyncedToCurrentChain should return false before txindex is started. + BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain()); + + txindex.Start(); + + // Allow tx index to catch up with the block index. + constexpr int64_t timeout_ms = 10 * 1000; + int64_t time_start = GetTimeMillis(); + while (!txindex.BlockUntilSyncedToCurrentChain()) { + BOOST_REQUIRE(time_start + timeout_ms > GetTimeMillis()); + MilliSleep(100); + } + + // 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)) { + BOOST_ERROR("FindTx failed"); + } else if (tx_disk->GetHash() != txn->GetHash()) { + BOOST_ERROR("Read incorrect tx"); + } + } + + // Check that new transactions in new blocks make it into the index. + for (int i = 0; i < 10; i++) { + CScript coinbase_script_pub_key = GetScriptForDestination(coinbaseKey.GetPubKey().GetID()); + std::vector<CMutableTransaction> no_txns; + const CBlock& block = CreateAndProcessBlock(no_txns, coinbase_script_pub_key); + const CTransaction& txn = *block.vtx[0]; + + BOOST_CHECK(txindex.BlockUntilSyncedToCurrentChain()); + if (!txindex.FindTx(txn.GetHash(), block_hash, tx_disk)) { + BOOST_ERROR("FindTx failed"); + } else if (tx_disk->GetHash() != txn.GetHash()) { + BOOST_ERROR("Read incorrect tx"); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 7a52697859..d32d4b267c 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -24,7 +24,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) static bool -ToMemPool(CMutableTransaction& tx) +ToMemPool(const CMutableTransaction& tx) { LOCK(cs_main); @@ -102,7 +102,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) // should fail. // Capture this interaction with the upgraded_nop argument: set it when evaluating // any script flag that is implemented as an upgraded NOP code. -void ValidateCheckInputsForAllFlags(CMutableTransaction &tx, uint32_t failing_flags, bool add_to_cache) +static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache) { PrecomputedTransactionData txdata(tx); // If we add many more flags, this loop can get too expensive, but we can @@ -315,7 +315,7 @@ 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); - UpdateTransaction(valid_with_witness_tx, 0, sigdata); + UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. ValidateCheckInputsForAllFlags(valid_with_witness_tx, 0, true); @@ -343,7 +343,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) for (int i=0; i<2; ++i) { SignatureData sigdata; ProduceSignature(keystore, MutableTransactionSignatureCreator(&tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata); - UpdateTransaction(tx, i, sigdata); + UpdateInput(tx.vin[i], sigdata); } // This should be valid under all script flags diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index 20ed29f59b..79217fa430 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -48,7 +48,7 @@ const unsigned char MaxArray[] = const uint256 MaxL = uint256(std::vector<unsigned char>(MaxArray,MaxArray+32)); const uint160 MaxS = uint160(std::vector<unsigned char>(MaxArray,MaxArray+20)); -std::string ArrayToString(const unsigned char A[], unsigned int width) +static std::string ArrayToString(const unsigned char A[], unsigned int width) { std::stringstream Stream; Stream << std::hex; diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 1c3acfb1a5..d535f74e91 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -186,27 +186,37 @@ struct TestArgsManager : public ArgsManager LOCK(cs_args); m_config_args.clear(); } - ReadConfigStream(streamConfig); + std::string error; + ReadConfigStream(streamConfig, error); } void SetNetworkOnlyArg(const std::string arg) { LOCK(cs_args); m_network_only_args.insert(arg); } + void SetupArgs(int argv, const char* args[]) + { + for (int i = 0; i < argv; ++i) { + AddArg(args[i], "", false, OptionsCategory::OPTIONS); + } + } }; BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; + const char* avail_args[] = {"-a", "-b", "-ccc", "-d"}; const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; - testArgs.ParseParameters(0, (char**)argv_test); + std::string error; + testArgs.SetupArgs(4, avail_args); + testArgs.ParseParameters(0, (char**)argv_test, error); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(1, (char**)argv_test); + testArgs.ParseParameters(1, (char**)argv_test, error); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(7, (char**)argv_test); + 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) @@ -227,9 +237,12 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; + const char* avail_args[] = {"-a", "-b", "-c", "-d", "-e", "-f"}; const char *argv_test[] = { "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; - testArgs.ParseParameters(7, (char**)argv_test); + std::string error; + testArgs.SetupArgs(6, avail_args); + testArgs.ParseParameters(7, (char**)argv_test, error); // Each letter should be set. for (char opt : "abcdef") @@ -261,8 +274,11 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) TestArgsManager testArgs; // Params test + const char* avail_args[] = {"-foo", "-bar"}; const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; - testArgs.ParseParameters(4, (char**)argv_test); + testArgs.SetupArgs(2, avail_args); + std::string error; + testArgs.ParseParameters(4, (char**)argv_test, error); // This was passed twice, second one overrides the negative setting. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); @@ -274,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); + testArgs.ParseParameters(1, (char**)argv_test, error); testArgs.ReadConfigString(conf_test); // This was passed twice, second one overrides the negative setting, @@ -289,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); + testArgs.ParseParameters(3, (char**)combo_test_args, error); testArgs.ReadConfigString(combo_test_conf); // Command line overrides, but doesn't erase old setting @@ -329,6 +345,8 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) "iii=2\n"; TestArgsManager test_args; + const char* avail_args[] = {"-a", "-b", "-ccc", "-d", "-e", "-fff", "-ggg", "-h", "-i", "-iii"}; + test_args.SetupArgs(10, avail_args); test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map @@ -526,6 +544,8 @@ BOOST_AUTO_TEST_CASE(util_GetArg) BOOST_AUTO_TEST_CASE(util_GetChainName) { TestArgsManager test_args; + const char* avail_args[] = {"-testnet", "-regtest"}; + test_args.SetupArgs(2, avail_args); const char* argv_testnet[] = {"cmd", "-testnet"}; const char* argv_regtest[] = {"cmd", "-regtest"}; @@ -535,39 +555,40 @@ BOOST_AUTO_TEST_CASE(util_GetChainName) // equivalent to "-testnet" // regtest in testnet section is ignored const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; + std::string error; - test_args.ParseParameters(0, (char**)argv_testnet); + test_args.ParseParameters(0, (char**)argv_testnet, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); - test_args.ParseParameters(2, (char**)argv_testnet); + test_args.ParseParameters(2, (char**)argv_testnet, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest); + test_args.ParseParameters(2, (char**)argv_regtest, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); - test_args.ParseParameters(3, (char**)argv_test_no_reg); + test_args.ParseParameters(3, (char**)argv_test_no_reg, error); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both); + test_args.ParseParameters(3, (char**)argv_both, error); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(0, (char**)argv_testnet); + 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); + 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); + 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); + 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); + test_args.ParseParameters(3, (char**)argv_both, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); @@ -575,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); + 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); + 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); + 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); + 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); + test_args.ParseParameters(3, (char**)argv_both, error); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); } @@ -1065,6 +1086,7 @@ static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) ReleaseDirectoryLocks(); ch = true; // Always succeeds rv = write(fd, &ch, 1); + assert(rv == 1); break; case ExitCommand: close(fd); @@ -1078,7 +1100,7 @@ static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) BOOST_AUTO_TEST_CASE(test_LockDirectory) { - fs::path dirname = fs::temp_directory_path() / fs::unique_path(); + fs::path dirname = SetDataDir("test_LockDirectory") / fs::unique_path(); const std::string lockname = ".lock"; #ifndef WIN32 // Revert SIGCHLD to default, otherwise boost.test will catch and fail on @@ -1166,12 +1188,12 @@ BOOST_AUTO_TEST_CASE(test_LockDirectory) BOOST_AUTO_TEST_CASE(test_DirIsWritable) { - // Should be able to write to the system tmp dir. - fs::path tmpdirname = fs::temp_directory_path(); + // Should be able to write to the data dir. + fs::path tmpdirname = SetDataDir("test_DirIsWritable"); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), true); // Should not be able to write to a non-existent dir. - tmpdirname = fs::temp_directory_path() / fs::unique_path(); + tmpdirname = tmpdirname / fs::unique_path(); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), false); fs::create_directory(tmpdirname); diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp new file mode 100644 index 0000000000..37c4f79133 --- /dev/null +++ b/src/test/validation_block_tests.cpp @@ -0,0 +1,184 @@ +// 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 <chainparams.h> +#include <consensus/merkle.h> +#include <consensus/validation.h> +#include <miner.h> +#include <pow.h> +#include <random.h> +#include <test/test_bitcoin.h> +#include <validation.h> +#include <validationinterface.h> + +struct RegtestingSetup : public TestingSetup { + RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} +}; + +BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup) + +struct TestSubscriber : public CValidationInterface { + uint256 m_expected_tip; + + TestSubscriber(uint256 tip) : m_expected_tip(tip) {} + + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override + { + BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash()); + } + + void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex, const std::vector<CTransactionRef>& txnConflicted) override + { + BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock); + BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash()); + + m_expected_tip = block->GetHash(); + } + + void BlockDisconnected(const std::shared_ptr<const CBlock>& block) override + { + BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash()); + + m_expected_tip = block->hashPrevBlock; + } +}; + +std::shared_ptr<CBlock> Block(const uint256& prev_hash) +{ + static int i = 0; + static uint64_t time = Params().GenesisBlock().nTime; + + CScript pubKey; + pubKey << i++ << OP_TRUE; + + auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey, false); + auto pblock = std::make_shared<CBlock>(ptemplate->block); + pblock->hashPrevBlock = prev_hash; + pblock->nTime = ++time; + + CMutableTransaction txCoinbase(*pblock->vtx[0]); + txCoinbase.vout.resize(1); + txCoinbase.vin[0].scriptWitness.SetNull(); + pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); + + return pblock; +} + +std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock) +{ + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + + while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { + ++(pblock->nNonce); + } + + return pblock; +} + +// construct a valid block +const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash) +{ + return FinalizeBlock(Block(prev_hash)); +} + +// construct an invalid block (but with a valid header) +const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash) +{ + auto pblock = Block(prev_hash); + + CMutableTransaction coinbase_spend; + coinbase_spend.vin.push_back(CTxIn(COutPoint(pblock->vtx[0]->GetHash(), 0), CScript(), 0)); + coinbase_spend.vout.push_back(pblock->vtx[0]->vout[0]); + + CTransactionRef tx = MakeTransactionRef(coinbase_spend); + pblock->vtx.push_back(tx); + + auto ret = FinalizeBlock(pblock); + return ret; +} + +void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks) +{ + if (height <= 0 || blocks.size() >= max_size) return; + + bool gen_invalid = GetRand(100) < invalid_rate; + bool gen_fork = GetRand(100) < branch_rate; + + const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root); + blocks.push_back(pblock); + if (!gen_invalid) { + BuildChain(pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); + } + + if (gen_fork) { + blocks.push_back(GoodBlock(root)); + BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks); + } +} + +BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) +{ + // build a large-ish chain that's likely to have some forks + std::vector<std::shared_ptr<const CBlock>> blocks; + while (blocks.size() < 50) { + blocks.clear(); + BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks); + } + + bool ignored; + CValidationState state; + std::vector<CBlockHeader> headers; + std::transform(blocks.begin(), blocks.end(), std::back_inserter(headers), [](std::shared_ptr<const CBlock> b) { return b->GetBlockHeader(); }); + + // Process all the headers so we understand the toplogy of the chain + 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); + SyncWithValidationInterfaceQueue(); + + // subscribe to events (this subscriber will validate event ordering) + const CBlockIndex* initial_tip = nullptr; + { + LOCK(cs_main); + initial_tip = chainActive.Tip(); + } + TestSubscriber sub(initial_tip->GetBlockHash()); + RegisterValidationInterface(&sub); + + // 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; + for (int i = 0; i < 10; i++) { + threads.create_thread([&blocks]() { + bool ignored; + for (int i = 0; i < 1000; i++) { + auto block = blocks[GetRand(blocks.size() - 1)]; + ProcessNewBlock(Params(), block, true, &ignored); + } + + // to make sure that eventually we process the full chain - do it here + for (auto block : blocks) { + if (block->vtx.size() == 1) { + bool processed = ProcessNewBlock(Params(), block, true, &ignored); + assert(processed); + } + } + }); + } + + threads.join_all(); + while (GetMainSignals().CallbacksPending() > 0) { + MilliSleep(100); + } + + UnregisterValidationInterface(&sub); + + BOOST_CHECK_EQUAL(sub.m_expected_tip, chainActive.Tip()->GetBlockHash()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 92ef58e517..7442825300 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -12,7 +12,7 @@ #include <boost/test/unit_test.hpp> /* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */ -int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; } +static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; } static const Consensus::Params paramsDummy = Consensus::Params(); diff --git a/src/threadinterrupt.cpp b/src/threadinterrupt.cpp index 5d932091cb..7da4e136ef 100644 --- a/src/threadinterrupt.cpp +++ b/src/threadinterrupt.cpp @@ -5,6 +5,8 @@ #include <threadinterrupt.h> +CThreadInterrupt::CThreadInterrupt() : flag(false) {} + CThreadInterrupt::operator bool() const { return flag.load(std::memory_order_acquire); diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index 54e3102808..d373e3c371 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -18,6 +18,7 @@ class CThreadInterrupt { public: + CThreadInterrupt(); explicit operator bool() const; void operator()(); void reset(); diff --git a/src/timedata.cpp b/src/timedata.cpp index a803b2fc87..dfb8fe55af 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -17,7 +17,7 @@ static CCriticalSection cs_nTimeOffset; -static int64_t nTimeOffset = 0; +static int64_t nTimeOffset GUARDED_BY(cs_nTimeOffset) = 0; /** * "Never go to sea with two chronometers; take one or three." @@ -110,9 +110,9 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) if (LogAcceptCategory(BCLog::NET)) { for (int64_t n : vSorted) { - LogPrint(BCLog::NET, "%+d ", n); + LogPrint(BCLog::NET, "%+d ", n); /* Continued */ } - LogPrint(BCLog::NET, "| "); + LogPrint(BCLog::NET, "| "); /* Continued */ LogPrint(BCLog::NET, "nTimeOffset = %+d (%+d minutes)\n", nTimeOffset, nTimeOffset/60); } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 717d1cf7e5..1f42ab8fa8 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -251,7 +251,7 @@ bool TorControlConnection::Command(const std::string &cmd, const ReplyHandlerCB& * Grammar is implicitly defined in https://spec.torproject.org/control-spec by * the server reply formats for PROTOCOLINFO (S3.21) and AUTHCHALLENGE (S3.24). */ -static std::pair<std::string,std::string> SplitTorReplyLine(const std::string &s) +std::pair<std::string,std::string> SplitTorReplyLine(const std::string &s) { size_t ptr=0; std::string type; @@ -270,7 +270,7 @@ static std::pair<std::string,std::string> SplitTorReplyLine(const std::string &s * the server reply formats for PROTOCOLINFO (S3.21), AUTHCHALLENGE (S3.24), * and ADD_ONION (S3.27). See also sections 2.1 and 2.3. */ -static std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) +std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) { std::map<std::string,std::string> mapping; size_t ptr=0; @@ -528,8 +528,8 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& if (gArgs.GetArg("-onion", "") == "") { CService resolved(LookupNumeric("127.0.0.1", 9050)); proxyType addrOnion = proxyType(resolved, true); - SetProxy(NET_TOR, addrOnion); - SetLimited(NET_TOR, false); + SetProxy(NET_ONION, addrOnion); + SetLimited(NET_ONION, false); } // Finally - now create the service @@ -773,4 +773,3 @@ void StopTorControl() gBase = nullptr; } } - diff --git a/src/txdb.cpp b/src/txdb.cpp index 45ce94ae42..3635d0ab4b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -9,10 +9,10 @@ #include <hash.h> #include <random.h> #include <pow.h> +#include <shutdown.h> #include <uint256.h> #include <util.h> #include <ui_interface.h> -#include <init.h> #include <stdint.h> @@ -21,7 +21,6 @@ static const char DB_COIN = 'C'; static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; -static const char DB_TXINDEX = 't'; static const char DB_BLOCK_INDEX = 'b'; static const char DB_BEST_BLOCK = 'B'; @@ -236,17 +235,6 @@ bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockF return WriteBatch(batch, true); } -bool CBlockTreeDB::ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) { - return Read(std::make_pair(DB_TXINDEX, txid), pos); -} - -bool CBlockTreeDB::WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> >&vect) { - CDBBatch batch(*this); - for (std::vector<std::pair<uint256,CDiskTxPos> >::const_iterator it=vect.begin(); it!=vect.end(); it++) - batch.Write(std::make_pair(DB_TXINDEX, it->first), it->second); - return WriteBatch(batch); -} - bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) { return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0'); } diff --git a/src/txdb.h b/src/txdb.h index f3454e7d09..100adb428d 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -9,6 +9,7 @@ #include <coins.h> #include <dbwrapper.h> #include <chain.h> +#include <primitives/block.h> #include <map> #include <memory> @@ -35,35 +36,10 @@ static const int64_t nMaxBlockDBCache = 2; //! Max memory allocated to block tree DB specific cache, if -txindex (MiB) // Unlike for the UTXO database, for the txindex scenario the leveldb cache make // a meaningful difference: https://github.com/bitcoin/bitcoin/pull/8273#issuecomment-229601991 -static const int64_t nMaxBlockDBAndTxIndexCache = 1024; +static const int64_t nMaxTxIndexCache = 1024; //! Max memory allocated to coin DB specific cache (MiB) static const int64_t nMaxCoinsDBCache = 8; -struct CDiskTxPos : public CDiskBlockPos -{ - unsigned int nTxOffset; // after header - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITEAS(CDiskBlockPos, *this); - READWRITE(VARINT(nTxOffset)); - } - - CDiskTxPos(const CDiskBlockPos &blockIn, unsigned int nTxOffsetIn) : CDiskBlockPos(blockIn.nFile, blockIn.nPos), nTxOffset(nTxOffsetIn) { - } - - CDiskTxPos() { - SetNull(); - } - - void SetNull() { - CDiskBlockPos::SetNull(); - nTxOffset = 0; - } -}; - /** CCoinsView backed by the coin database (chainstate/) */ class CCoinsViewDB final : public CCoinsView { @@ -112,16 +88,11 @@ class CBlockTreeDB : public CDBWrapper public: explicit CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); - CBlockTreeDB(const CBlockTreeDB&) = delete; - CBlockTreeDB& operator=(const CBlockTreeDB&) = delete; - bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo); bool ReadBlockFileInfo(int nFile, CBlockFileInfo &info); bool ReadLastBlockFile(int &nFile); bool WriteReindexing(bool fReindexing); bool ReadReindexing(bool &fReindexing); - bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos); - bool WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> > &vect); bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index d03429ca81..8090172e3f 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -69,12 +69,12 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan setAllDescendants.insert(cit); stageEntries.erase(cit); const setEntries &setChildren = GetMemPoolChildren(cit); - for (const txiter childEntry : setChildren) { + for (txiter childEntry : setChildren) { cacheMap::iterator cacheIt = cachedDescendants.find(childEntry); if (cacheIt != cachedDescendants.end()) { // We've already calculated this one, just add the entries for this set // but don't traverse again. - for (const txiter cacheEntry : cacheIt->second) { + for (txiter cacheEntry : cacheIt->second) { setAllDescendants.insert(cacheEntry); } } else if (!setAllDescendants.count(childEntry)) { @@ -618,6 +618,7 @@ static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& m void CTxMemPool::check(const CCoinsViewCache *pcoins) const { + LOCK(cs); if (nCheckFrequency == 0) return; @@ -632,7 +633,6 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(pcoins)); const int64_t spendheight = GetSpendHeight(mempoolDuplicate); - LOCK(cs); std::list<const CTxMemPoolEntry*> waitingOnDependants; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { unsigned int i = 0; @@ -693,18 +693,18 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const // Check children against mapNextTx CTxMemPool::setEntries setChildrenCheck; auto iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0)); - int64_t childSizes = 0; + uint64_t child_sizes = 0; for (; iter != mapNextTx.end() && iter->first->hash == it->GetTx().GetHash(); ++iter) { txiter childit = mapTx.find(iter->second->GetHash()); assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions if (setChildrenCheck.insert(childit).second) { - childSizes += childit->GetTxSize(); + child_sizes += childit->GetTxSize(); } } assert(setChildrenCheck == GetMemPoolChildren(it)); // Also check to make sure size is greater than sum with immediate children. // just a sanity check, not definitive that this calc is correct... - assert(it->GetSizeWithDescendants() >= childSizes + it->GetTxSize()); + assert(it->GetSizeWithDescendants() >= child_sizes + it->GetTxSize()); if (fDependsWait) waitingOnDependants.push_back(&(*it)); @@ -1055,11 +1055,36 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpends } } -bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLimit) const { +uint64_t CTxMemPool::CalculateDescendantMaximum(txiter entry) const { + // find parent with highest descendant count + std::vector<txiter> candidates; + setEntries counted; + candidates.push_back(entry); + uint64_t maximum = 0; + while (candidates.size()) { + txiter candidate = candidates.back(); + candidates.pop_back(); + if (!counted.insert(candidate).second) continue; + const setEntries& parents = GetMemPoolParents(candidate); + if (parents.size() == 0) { + maximum = std::max(maximum, candidate->GetCountWithDescendants()); + } else { + for (txiter i : parents) { + candidates.push_back(i); + } + } + } + return maximum; +} + +void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const { LOCK(cs); auto it = mapTx.find(txid); - return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit && - it->GetCountWithDescendants() < chainLimit); + ancestors = descendants = 0; + if (it != mapTx.end()) { + ancestors = it->GetCountWithAncestors(); + descendants = CalculateDescendantMaximum(it); + } } SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} diff --git a/src/txmempool.h b/src/txmempool.h index a1cde6f779..ebfcf36e11 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -440,7 +440,7 @@ public: class CTxMemPool { private: - uint32_t nCheckFrequency; //!< Value n means that n times in 2^32 we check. + uint32_t nCheckFrequency GUARDED_BY(cs); //!< Value n means that n times in 2^32 we check. unsigned int nTransactionsUpdated; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation CBlockPolicyEstimator* minerPolicyEstimator; @@ -451,7 +451,7 @@ private: mutable bool blockSinceLastRollingFeeBump; mutable double rollingMinimumFeeRate; //!< minimum fee to get into the pool, decreases exponentially - void trackPackageRemoved(const CFeeRate& rate); + void trackPackageRemoved(const CFeeRate& rate) EXCLUSIVE_LOCKS_REQUIRED(cs); public: @@ -484,9 +484,9 @@ public: > indexed_transaction_set; mutable CCriticalSection cs; - indexed_transaction_set mapTx; + indexed_transaction_set mapTx GUARDED_BY(cs); - typedef indexed_transaction_set::nth_index<0>::type::iterator txiter; + using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator; std::vector<std::pair<uint256, txiter> > vTxHashes; //!< All tx witness hashes/entries in mapTx, in random order struct CompareIteratorByHash { @@ -496,8 +496,9 @@ public: }; typedef std::set<txiter, CompareIteratorByHash> setEntries; - const setEntries & GetMemPoolParents(txiter entry) const; - const setEntries & GetMemPoolChildren(txiter entry) const; + const setEntries & GetMemPoolParents(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); + const setEntries & GetMemPoolChildren(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); + uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); private: typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap; @@ -512,10 +513,10 @@ private: void UpdateParent(txiter entry, txiter parent, bool add); void UpdateChild(txiter entry, txiter child, bool add); - std::vector<indexed_transaction_set::const_iterator> GetSortedDepthAndScore() const; + std::vector<indexed_transaction_set::const_iterator> GetSortedDepthAndScore() const EXCLUSIVE_LOCKS_REQUIRED(cs); public: - indirectmap<COutPoint, const CTransaction*> mapNextTx; + indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs); std::map<uint256, CAmount> mapDeltas; /** Create a new CTxMemPool. @@ -529,7 +530,7 @@ public: * check does nothing. */ void check(const CCoinsViewCache *pcoins) const; - void setSanityCheck(double dFrequency = 1.0) { nCheckFrequency = static_cast<uint32_t>(dFrequency * 4294967295.0); } + void setSanityCheck(double dFrequency = 1.0) { LOCK(cs); nCheckFrequency = static_cast<uint32_t>(dFrequency * 4294967295.0); } // addUnchecked must updated state for all ancestors of a given transaction, // to track size/count of descendant transactions. First version of @@ -547,7 +548,7 @@ public: void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight); void clear(); - void _clear(); //lock free + void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb); void queryHashes(std::vector<uint256>& vtxid); bool isSpent(const COutPoint& outpoint) const; @@ -572,7 +573,7 @@ public: * Set updateDescendants to true when removing a tx that was in a block, so * that any in-mempool descendants have their ancestor state updated. */ - void RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); + void RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); /** When adding transactions from a disconnected block back to the mempool, * new mempool entries may have children in the mempool (which is generally @@ -600,7 +601,7 @@ public: /** Populate setDescendants with all in-mempool descendants of hash. * Assumes that setDescendants includes all in-mempool descendants of anything * already in it. */ - void CalculateDescendants(txiter it, setEntries& setDescendants) const; + void CalculateDescendants(txiter it, setEntries& setDescendants) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** The minimum fee to get into the mempool, which may itself not be enough * for larger-sized transactions. @@ -619,8 +620,11 @@ public: /** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */ int Expire(int64_t time); - /** Returns false if the transaction is in the mempool and not within the chain limit specified. */ - bool TransactionWithinChainLimit(const uint256& txid, size_t chainLimit) const; + /** + * Calculate the ancestor and descendant count for the given transaction. + * The counts include the transaction itself. + */ + void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const; unsigned long size() { @@ -665,17 +669,17 @@ private: */ void UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, - const std::set<uint256> &setExclude); + const std::set<uint256> &setExclude) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Update ancestors of hash to add/remove it as a descendant transaction. */ - void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors); + void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Set ancestor state for an entry */ - void UpdateEntryForAncestors(txiter it, const setEntries &setAncestors); + void UpdateEntryForAncestors(txiter it, const setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs); /** For each transaction being removed, update ancestors and any direct children. * If updateDescendants is true, then also update in-mempool descendants' * ancestor state. */ - void UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants); + void UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Sever link between specified transaction and direct children. */ - void UpdateChildrenForRemoval(txiter entry); + void UpdateChildrenForRemoval(txiter entry) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Before calling removeUnchecked for a given transaction, * UpdateForRemoveFromMempool must be called on the entire (dependent) set @@ -685,7 +689,7 @@ private: * transactions in a chain before we've updated all the state for the * removal. */ - void removeUnchecked(txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); + void removeUnchecked(txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); }; /** diff --git a/src/ui_interface.h b/src/ui_interface.h index 33099b4e73..3add369df0 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_UI_INTERFACE_H #define BITCOIN_UI_INTERFACE_H +#include <memory> #include <stdint.h> #include <string> @@ -92,7 +93,7 @@ public: boost::signals2::signal<void ()> NotifyAlertChanged; /** A wallet has been loaded. */ - boost::signals2::signal<void (CWallet* wallet)> LoadWallet; + boost::signals2::signal<void (std::shared_ptr<CWallet> wallet)> LoadWallet; /** * Show progress e.g. for verifychain. diff --git a/src/util.cpp b/src/util.cpp index 9a3067259f..ab262b4063 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -412,7 +412,7 @@ void ArgsManager::SelectConfigNetwork(const std::string& network) m_network = network; } -void ArgsManager::ParseParameters(int argc, const char* const argv[]) +bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error) { LOCK(cs_args); m_override_args.clear(); @@ -444,7 +444,43 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[]) } else { m_override_args[key].push_back(val); } + + // Check that the arg is known + if (!(IsSwitchChar(key[0]) && key.size() == 1)) { + if (!IsArgKnown(key, error)) { + error = strprintf("Invalid parameter %s", key.c_str()); + return false; + } + } + } + + // we do not allow -includeconf from command line, so we clear it here + auto it = m_override_args.find("-includeconf"); + if (it != m_override_args.end()) { + if (it->second.size() > 0) { + for (const auto& ic : it->second) { + error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n"; + } + return false; + } + } + return true; +} + +bool ArgsManager::IsArgKnown(const std::string& key, std::string& error) +{ + size_t option_index = key.find('.'); + std::string arg_no_net; + if (option_index == std::string::npos) { + arg_no_net = key; + } else { + arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); + } + + for (const auto& arg_map : m_available_args) { + if (arg_map.second.count(arg_no_net)) return true; } + return false; } std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const @@ -536,6 +572,94 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV m_override_args[strArg] = {strValue}; } +void ArgsManager::AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat) +{ + // Split arg name from its help param + size_t eq_index = name.find('='); + if (eq_index == std::string::npos) { + eq_index = name.size(); + } + + std::map<std::string, Arg>& arg_map = m_available_args[cat]; + auto ret = arg_map.emplace(name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); + assert(ret.second); // Make sure an insertion actually happened +} + +void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names) +{ + for (const std::string& name : names) { + AddArg(name, "", false, OptionsCategory::HIDDEN); + } +} + +std::string ArgsManager::GetHelpMessage() +{ + const bool show_debug = gArgs.GetBoolArg("-help-debug", false); + + std::string usage = ""; + for (const auto& arg_map : m_available_args) { + switch(arg_map.first) { + case OptionsCategory::OPTIONS: + usage += HelpMessageGroup("Options:"); + break; + case OptionsCategory::CONNECTION: + usage += HelpMessageGroup("Connection options:"); + break; + case OptionsCategory::ZMQ: + usage += HelpMessageGroup("ZeroMQ notification options:"); + break; + case OptionsCategory::DEBUG_TEST: + usage += HelpMessageGroup("Debugging/Testing options:"); + break; + case OptionsCategory::NODE_RELAY: + usage += HelpMessageGroup("Node relay options:"); + break; + case OptionsCategory::BLOCK_CREATION: + usage += HelpMessageGroup("Block creation options:"); + break; + case OptionsCategory::RPC: + usage += HelpMessageGroup("RPC server options:"); + break; + case OptionsCategory::WALLET: + usage += HelpMessageGroup("Wallet options:"); + break; + case OptionsCategory::WALLET_DEBUG_TEST: + if (show_debug) usage += HelpMessageGroup("Wallet debugging/testing options:"); + break; + case OptionsCategory::CHAINPARAMS: + usage += HelpMessageGroup("Chain selection options:"); + break; + case OptionsCategory::GUI: + usage += HelpMessageGroup("UI Options:"); + break; + case OptionsCategory::COMMANDS: + usage += HelpMessageGroup("Commands:"); + break; + case OptionsCategory::REGISTER_COMMANDS: + usage += HelpMessageGroup("Register Commands:"); + break; + default: + break; + } + + // When we get to the hidden options, stop + if (arg_map.first == OptionsCategory::HIDDEN) break; + + for (const auto& arg : arg_map.second) { + if (show_debug || !arg.second.m_debug_only) { + std::string name; + if (arg.second.m_help_param.empty()) { + name = arg.first; + } else { + name = arg.first + arg.second.m_help_param; + } + usage += HelpMessageOpt(name, arg.second.m_help_text); + } + } + } + return usage; +} + bool HelpRequested(const ArgsManager& args) { return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help"); @@ -687,7 +811,7 @@ fs::path GetConfigFile(const std::string& confPath) return AbsPathForConfigVal(fs::path(confPath), false); } -void ArgsManager::ReadConfigStream(std::istream& stream) +bool ArgsManager::ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys) { LOCK(cs_args); @@ -698,33 +822,95 @@ void ArgsManager::ReadConfigStream(std::istream& stream) { std::string strKey = std::string("-") + it->string_key; std::string strValue = it->value[0]; + if (InterpretNegatedOption(strKey, strValue)) { m_config_args[strKey].clear(); } else { m_config_args[strKey].push_back(strValue); } + + // Check that the arg is known + if (!IsArgKnown(strKey, error) && !ignore_invalid_keys) { + error = strprintf("Invalid configuration value %s", it->string_key.c_str()); + return false; + } } + return true; } -void ArgsManager::ReadConfigFile(const std::string& confPath) +bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) { { LOCK(cs_args); m_config_args.clear(); } + const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fs::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { - ReadConfigStream(stream); + if (!ReadConfigStream(stream, error, ignore_invalid_keys)) { + return false; + } + // if there is an -includeconf in the override args, but it is empty, that means the user + // passed '-noincludeconf' on the command line, in which case we should not include anything + if (m_override_args.count("-includeconf") == 0) { + std::string chain_id = GetChainName(); + std::vector<std::string> includeconf(GetArgs("-includeconf")); + { + // We haven't set m_network yet (that happens in SelectParams()), so manually check + // for network.includeconf args. + std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + } + + // Remove -includeconf from configuration, so we can warn about recursion + // later + { + LOCK(cs_args); + m_config_args.erase("-includeconf"); + m_config_args.erase(std::string("-") + chain_id + ".includeconf"); + } + + for (const std::string& to_include : includeconf) { + fs::ifstream include_config(GetConfigFile(to_include)); + if (include_config.good()) { + if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { + return false; + } + LogPrintf("Included configuration file %s\n", to_include.c_str()); + } else { + error = "Failed to include configuration file " + to_include; + return false; + } + } + + // Warn about recursive -includeconf + includeconf = GetArgs("-includeconf"); + { + std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + std::string chain_id_final = GetChainName(); + if (chain_id_final != chain_id) { + // Also warn about recursive includeconf for the chain that was specified in one of the includeconfs + includeconf_net = GetArgs(std::string("-") + chain_id_final + ".includeconf"); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + } + } + for (const std::string& to_include : includeconf) { + fprintf(stderr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); + } + } } // If datadir is changed in .conf file: ClearDatadirCache(); if (!fs::is_directory(GetDataDir(false))) { - throw std::runtime_error(strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str())); + error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); + return false; } + return true; } std::string ArgsManager::GetChainName() const @@ -887,7 +1073,9 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; - fseek(file, offset, SEEK_SET); + if (fseek(file, offset, SEEK_SET)) { + return; + } while (length > 0) { unsigned int now = 65536; if (length < now) diff --git a/src/util.h b/src/util.h index ce94f396af..f8bcc0192c 100644 --- a/src/util.h +++ b/src/util.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -20,11 +20,11 @@ #include <sync.h> #include <tinyformat.h> #include <utiltime.h> +#include <utilmemory.h> #include <atomic> #include <exception> #include <map> -#include <memory> #include <set> #include <stdint.h> #include <string> @@ -66,7 +66,7 @@ bool SetupNetworking(); template<typename... Args> bool error(const char* fmt, const Args&... args) { - LogPrintStr("ERROR: " + tfm::format(fmt, args...) + "\n"); + LogPrintf("ERROR: %s\n", tfm::format(fmt, args...)); return false; } @@ -118,18 +118,46 @@ inline bool IsSwitchChar(char c) #endif } +enum class OptionsCategory { + OPTIONS, + CONNECTION, + WALLET, + WALLET_DEBUG_TEST, + ZMQ, + DEBUG_TEST, + CHAINPARAMS, + NODE_RELAY, + BLOCK_CREATION, + RPC, + GUI, + COMMANDS, + REGISTER_COMMANDS, + + HIDDEN // Always the last option to avoid printing these in the help +}; + class ArgsManager { protected: friend class ArgsManagerHelper; + struct Arg + { + std::string m_help_param; + std::string m_help_text; + bool m_debug_only; + + Arg(const std::string& help_param, const std::string& help_text, bool debug_only) : m_help_param(help_param), m_help_text(help_text), m_debug_only(debug_only) {}; + }; + mutable CCriticalSection cs_args; std::map<std::string, std::vector<std::string>> m_override_args; std::map<std::string, std::vector<std::string>> m_config_args; std::string m_network; std::set<std::string> m_network_only_args; + std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args; - void ReadConfigStream(std::istream& stream); + bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false); public: ArgsManager(); @@ -139,8 +167,8 @@ public: */ void SelectConfigNetwork(const std::string& network); - void ParseParameters(int argc, const char*const argv[]); - void ReadConfigFile(const std::string& confPath); + bool ParseParameters(int argc, const char* const argv[], std::string& error); + bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when @@ -229,6 +257,31 @@ public: * @return CBaseChainParams::MAIN by default; raises runtime error if an invalid combination is given. */ std::string GetChainName() const; + + /** + * Add argument + */ + void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat); + + /** + * Add many hidden arguments + */ + void AddHiddenArgs(const std::vector<std::string>& args); + + /** + * Clear available arguments + */ + void ClearArgs() { m_available_args.clear(); } + + /** + * Get the help string + */ + std::string GetHelpMessage(); + + /** + * Check whether we know of this arg + */ + bool IsArgKnown(const std::string& key, std::string& error); }; extern ArgsManager gArgs; @@ -293,19 +346,12 @@ template <typename Callable> void TraceThread(const char* name, Callable func) std::string CopyrightHolders(const std::string& strPrefix); -//! Substitute for C++14 std::make_unique. -template <typename T, typename... Args> -std::unique_ptr<T> MakeUnique(Args&&... args) -{ - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * * @return The return value of sched_setschedule(), or 1 on systems without - * sched_setchedule(). + * sched_setschedule(). */ int ScheduleBatchPriority(void); diff --git a/src/utilmemory.h b/src/utilmemory.h new file mode 100644 index 0000000000..e71fe92284 --- /dev/null +++ b/src/utilmemory.h @@ -0,0 +1,19 @@ +// 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_UTILMEMORY_H +#define BITCOIN_UTILMEMORY_H + +#include <memory> +#include <utility> + +//! Substitute for C++14 std::make_unique. +template <typename T, typename... Args> +std::unique_ptr<T> MakeUnique(Args&&... args) +{ + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} + +#endif diff --git a/src/utiltime.cpp b/src/utiltime.cpp index 34800c7b6d..e60996efe1 100644 --- a/src/utiltime.cpp +++ b/src/utiltime.cpp @@ -79,20 +79,32 @@ void MilliSleep(int64_t n) std::string FormatISO8601DateTime(int64_t nTime) { struct tm ts; time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else gmtime_r(&time_val, &ts); +#endif return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); } std::string FormatISO8601Date(int64_t nTime) { struct tm ts; time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else gmtime_r(&time_val, &ts); +#endif return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } std::string FormatISO8601Time(int64_t nTime) { struct tm ts; time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else gmtime_r(&time_val, &ts); +#endif return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec); } diff --git a/src/validation.cpp b/src/validation.cpp index bce8c4f9e9..a30f1fd0ce 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -16,7 +16,7 @@ #include <consensus/validation.h> #include <cuckoocache.h> #include <hash.h> -#include <init.h> +#include <index/txindex.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -28,6 +28,7 @@ #include <script/script.h> #include <script/sigcache.h> #include <script/standard.h> +#include <shutdown.h> #include <timedata.h> #include <tinyformat.h> #include <txdb.h> @@ -144,13 +145,19 @@ private: */ std::set<CBlockIndex*> m_failed_blocks; + /** + * the ChainState CriticalSection + * A lock that must be held when modifying this ChainState - held in ActivateBestChain() + */ + CCriticalSection m_cs_chainstate; + public: CChain chainActive; BlockMap mapBlockIndex; std::multimap<CBlockIndex*, CBlockIndex*> mapBlocksUnlinked; CBlockIndex *pindexBestInvalid = nullptr; - bool LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree); + bool LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock); @@ -158,8 +165,8 @@ public: * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure * that it doesn't descend from an invalid block, and then add it to mapBlockIndex. */ - bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex); - bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock); + bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view); @@ -170,9 +177,9 @@ public: bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool); // Manual block validity manipulation: - bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex); - bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex); - bool ResetBlockFailureFlags(CBlockIndex *pindex); + bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool ReplayBlocks(const CChainParams& params, CCoinsView* view); bool RewindBlockIndex(const CChainParams& params); @@ -186,9 +193,9 @@ private: bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace); bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool); - CBlockIndex* AddToBlockIndex(const CBlockHeader& block); + CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Create a new block index entry for a given block hash */ - CBlockIndex * InsertBlockIndex(const uint256& hash); + CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Make various assertions about the state of the block index. * @@ -197,11 +204,11 @@ private: void CheckBlockIndex(const Consensus::Params& consensusParams); void InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state); - CBlockIndex* FindMostWorkChain(); - bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBlockIndex *pindexNew, const CDiskBlockPos& pos, const Consensus::Params& consensusParams); + CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const CDiskBlockPos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params); + bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main); } g_chainstate; @@ -217,7 +224,6 @@ uint256 g_best_block; int nScriptCheckThreads = 0; std::atomic_bool fImporting(false); std::atomic_bool fReindex(false); -bool fTxIndex = false; bool fHavePruned = false; bool fPruneMode = false; bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; @@ -477,7 +483,7 @@ static bool IsCurrentForFeeEstimation() * and instead just erase from the mempool as needed. */ -void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, bool fAddToMempool) +static void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, bool fAddToMempool) { AssertLockHeld(cs_main); std::vector<uint256> vHashUpdate; @@ -571,17 +577,17 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (tx.IsCoinBase()) return state.DoS(100, false, REJECT_INVALID, "coinbase"); - // Reject transactions with witness before segregated witness activates (override with -prematurewitness) - bool witnessEnabled = IsWitnessEnabled(chainActive.Tip(), chainparams.GetConsensus()); - if (!gArgs.GetBoolArg("-prematurewitness", false) && tx.HasWitness() && !witnessEnabled) { - return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true); - } - // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; - if (fRequireStandard && !IsStandardTx(tx, reason, witnessEnabled)) + if (fRequireStandard && !IsStandardTx(tx, reason)) return state.DoS(0, false, REJECT_NONSTANDARD, reason); + // Do not work on transactions that are too small. + // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. + // Transactions smaller than this are not relayed to reduce unnecessary malloc overhead. + if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) + return state.DoS(0, false, REJECT_NONSTANDARD, "tx-size-small"); + // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. @@ -645,7 +651,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool view.SetBackend(viewMemPool); // do all inputs exist? - for (const CTxIn txin : tx.vin) { + for (const CTxIn& txin : tx.vin) { if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { coins_to_uncache.push_back(txin.prevout); } @@ -800,13 +806,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // be increased is also an easy-to-reason about way to prevent // DoS attacks via replacements. // - // The mining code doesn't (currently) take children into - // account (CPFP) so we only consider the feerates of - // transactions being directly replaced, not their indirect - // descendants. While that does mean high feerate children are - // ignored when deciding whether or not to replace, we do - // require the replacement to pay more overall fees too, - // mitigating most cases. + // We only consider the feerates of transactions being directly + // replaced, not their indirect descendants. While that does + // mean high feerate children are ignored when deciding whether + // or not to replace, we do require the replacement to pay more + // overall fees too, mitigating most cases. CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { @@ -951,7 +955,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } // Remove conflicting transactions from the mempool - for (const CTxMemPool::txiter it : allConflicting) + for (CTxMemPool::txiter it : allConflicting) { LogPrint(BCLog::MEMPOOL, "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n", it->GetTx().GetHash().ToString(), @@ -1028,28 +1032,8 @@ bool GetTransaction(const uint256& hash, CTransactionRef& txOut, const Consensus return true; } - if (fTxIndex) { - CDiskTxPos postx; - if (pblocktree->ReadTxIndex(hash, postx)) { - CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); - if (file.IsNull()) - return error("%s: OpenBlockFile failed", __func__); - CBlockHeader header; - try { - file >> header; - fseek(file.Get(), postx.nTxOffset, SEEK_CUR); - file >> txOut; - } catch (const std::exception& e) { - return error("%s: Deserialize or I/O error - %s", __func__, e.what()); - } - hashBlock = header.GetHash(); - if (txOut->GetHash() != hash) - return error("%s: txid mismatch", __func__); - return true; - } - - // transaction not found in index, nothing more can be done - return false; + if (g_txindex) { + return g_txindex->FindTx(hash, hashBlock, txOut); } if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it @@ -1145,6 +1129,52 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus return true; } +bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& message_start) +{ + CDiskBlockPos hpos = pos; + hpos.nPos -= 8; // Seek back 8 bytes for meta header + CAutoFile filein(OpenBlockFile(hpos, true), SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) { + return error("%s: OpenBlockFile failed for %s", __func__, pos.ToString()); + } + + try { + CMessageHeader::MessageStartChars blk_start; + unsigned int blk_size; + + filein >> blk_start >> blk_size; + + if (memcmp(blk_start, message_start, CMessageHeader::MESSAGE_START_SIZE)) { + return error("%s: Block magic mismatch for %s: %s versus expected %s", __func__, pos.ToString(), + HexStr(blk_start, blk_start + CMessageHeader::MESSAGE_START_SIZE), + HexStr(message_start, message_start + CMessageHeader::MESSAGE_START_SIZE)); + } + + if (blk_size > MAX_SIZE) { + return error("%s: Block data is larger than maximum deserialization size for %s: %s versus %s", __func__, pos.ToString(), + blk_size, MAX_SIZE); + } + + block.resize(blk_size); // Zeroing of memory is intentional here + filein.read((char*)block.data(), blk_size); + } catch(const std::exception& e) { + return error("%s: Read from block file failed: %s for %s", __func__, e.what(), pos.ToString()); + } + + return true; +} + +bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CBlockIndex* pindex, const CMessageHeader::MessageStartChars& message_start) +{ + CDiskBlockPos block_pos; + { + LOCK(cs_main); + block_pos = pindex->GetBlockPos(); + } + + return ReadRawBlockFromDisk(block, block_pos, message_start); +} + CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) { int halvings = nHeight / consensusParams.nSubsidyHalvingInterval; @@ -1507,7 +1537,7 @@ static bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex *pindex) } /** Abort with a message */ -bool AbortNode(const std::string& strMessage, const std::string& userMessage="") +static bool AbortNode(const std::string& strMessage, const std::string& userMessage="") { SetMiscWarning(strMessage); LogPrintf("*** %s\n", strMessage); @@ -1518,7 +1548,7 @@ bool AbortNode(const std::string& strMessage, const std::string& userMessage="") return false; } -bool AbortNode(CValidationState& state, const std::string& strMessage, const std::string& userMessage="") +static bool AbortNode(CValidationState& state, const std::string& strMessage, const std::string& userMessage="") { AbortNode(strMessage, userMessage); return state.Error(strMessage); @@ -1668,26 +1698,6 @@ static bool WriteUndoDataForBlock(const CBlockUndo& blockundo, CValidationState& return true; } -static bool WriteTxIndexDataForBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex) -{ - if (!fTxIndex) return true; - - CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size())); - std::vector<std::pair<uint256, CDiskTxPos> > vPos; - vPos.reserve(block.vtx.size()); - for (const CTransactionRef& tx : block.vtx) - { - vPos.push_back(std::make_pair(tx->GetHash(), pos)); - pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION); - } - - if (!pblocktree->WriteTxIndex(vPos)) { - return AbortNode(state, "Failed to write transaction index"); - } - - return true; -} - static CCheckQueue<CScriptCheck> scriptcheckqueue(128); void ThreadScriptCheck() { @@ -2079,9 +2089,6 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl setDirtyBlockIndex.insert(pindex); } - if (!WriteTxIndexDataForBlock(block, state, pindex)) - return false; - assert(pindex->phashBlock); // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -2109,13 +2116,12 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & LOCK(cs_main); static int64_t nLastWrite = 0; static int64_t nLastFlush = 0; - static int64_t nLastSetChain = 0; std::set<int> setFilesToPrune; - bool fFlushForPrune = false; - bool fDoFullFlush = false; - int64_t nNow = 0; + bool full_flush_completed = false; try { { + bool fFlushForPrune = false; + bool fDoFullFlush = false; LOCK(cs_LastBlockFile); if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) && !fReindex) { if (nManualPruneHeight > 0) { @@ -2132,7 +2138,7 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & } } } - nNow = GetTimeMicros(); + int64_t nNow = GetTimeMicros(); // Avoid writing/flushing immediately after startup. if (nLastWrite == 0) { nLastWrite = nNow; @@ -2140,9 +2146,6 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & if (nLastFlush == 0) { nLastFlush = nNow; } - if (nLastSetChain == 0) { - nLastSetChain = nNow; - } int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t cacheSize = pcoinsTip->DynamicMemoryUsage(); int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0); @@ -2199,12 +2202,12 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & if (!pcoinsTip->Flush()) return AbortNode(state, "Failed to write to coin database"); nLastFlush = nNow; + full_flush_completed = true; } } - if (fDoFullFlush || ((mode == FlushStateMode::ALWAYS || mode == FlushStateMode::PERIODIC) && nNow > nLastSetChain + (int64_t)DATABASE_WRITE_INTERVAL * 1000000)) { + if (full_flush_completed) { // Update best block in wallet (so we can detect restored wallets). - GetMainSignals().SetBestChain(chainActive.GetLocator()); - nLastSetChain = nNow; + GetMainSignals().ChainStateFlushed(chainActive.GetLocator()); } } catch (const std::runtime_error& e) { return AbortNode(state, std::string("System error while flushing: ") + e.what()); @@ -2560,6 +2563,7 @@ void CChainState::PruneBlockIndexCandidates() { bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { AssertLockHeld(cs_main); + const CBlockIndex *pindexOldTip = chainActive.Tip(); const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork); @@ -2598,8 +2602,9 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (state.IsInvalid()) { // The block violates a consensus rule. - if (!state.CorruptionPossible()) - InvalidChainFound(vpindexToConnect.back()); + if (!state.CorruptionPossible()) { + InvalidChainFound(vpindexToConnect.front()); + } state = CValidationState(); fInvalidFound = true; fContinue = false; @@ -2638,7 +2643,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar return true; } -static void NotifyHeaderTip() { +static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { bool fNotify = false; bool fInitialBlockDownload = false; static CBlockIndex* pindexHeaderOld = nullptr; @@ -2675,6 +2680,12 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& // sanely for performance or correctness! AssertLockNotHeld(cs_main); + // ABC maintains a fair degree of expensive-to-calculate internal state + // because this function periodically releases cs_main so that it does not lock up other threads for too long + // during large connects - and to allow for e.g. the callback queue to drain + // we use m_cs_chainstate to enforce mutual exclusion so that only one caller may execute this function at a time + LOCK(m_cs_chainstate); + CBlockIndex *pindexMostWork = nullptr; CBlockIndex *pindexNewTip = nullptr; int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT); @@ -2685,48 +2696,59 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& // Block until the validation queue drains. This should largely // never happen in normal operation, however may happen during // reindex, causing memory blowup if we run too far ahead. + // Note that if a validationinterface callback ends up calling + // ActivateBestChain this may lead to a deadlock! We should + // probably have a DEBUG_LOCKORDER test for this in the future. SyncWithValidationInterfaceQueue(); } - const CBlockIndex *pindexFork; - bool fInitialDownload; { LOCK(cs_main); - ConnectTrace connectTrace(mempool); // Destructed before cs_main is unlocked + CBlockIndex* starting_tip = chainActive.Tip(); + bool blocks_connected = false; + do { + // We absolutely may not unlock cs_main until we've made forward progress + // (with the exception of shutdown due to hardware issues, low disk space, etc). + ConnectTrace connectTrace(mempool); // Destructed before cs_main is unlocked + + if (pindexMostWork == nullptr) { + pindexMostWork = FindMostWorkChain(); + } - CBlockIndex *pindexOldTip = chainActive.Tip(); - if (pindexMostWork == nullptr) { - pindexMostWork = FindMostWorkChain(); - } + // Whether we have anything to do at all. + if (pindexMostWork == nullptr || pindexMostWork == chainActive.Tip()) { + break; + } - // Whether we have anything to do at all. - if (pindexMostWork == nullptr || pindexMostWork == chainActive.Tip()) - return true; + bool fInvalidFound = false; + std::shared_ptr<const CBlock> nullBlockPtr; + if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) + return false; + blocks_connected = true; - bool fInvalidFound = false; - std::shared_ptr<const CBlock> nullBlockPtr; - if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) - return false; + if (fInvalidFound) { + // Wipe cache, we may need another branch now. + pindexMostWork = nullptr; + } + pindexNewTip = chainActive.Tip(); - if (fInvalidFound) { - // Wipe cache, we may need another branch now. - pindexMostWork = nullptr; - } - pindexNewTip = chainActive.Tip(); - pindexFork = chainActive.FindFork(pindexOldTip); - fInitialDownload = IsInitialBlockDownload(); + for (const PerBlockConnectTrace& trace : connectTrace.GetBlocksConnected()) { + assert(trace.pblock && trace.pindex); + GetMainSignals().BlockConnected(trace.pblock, trace.pindex, trace.conflictedTxs); + } + } while (!chainActive.Tip() || (starting_tip && CBlockIndexWorkComparator()(chainActive.Tip(), starting_tip))); + if (!blocks_connected) return true; - for (const PerBlockConnectTrace& trace : connectTrace.GetBlocksConnected()) { - assert(trace.pblock && trace.pindex); - GetMainSignals().BlockConnected(trace.pblock, trace.pindex, trace.conflictedTxs); - } + const CBlockIndex* pindexFork = chainActive.FindFork(starting_tip); + bool fInitialDownload = IsInitialBlockDownload(); // Notify external listeners about the new tip. // Enqueue while holding cs_main to ensure that UpdatedBlockTip is called in the order in which blocks are connected - GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, fInitialDownload); - - // Always notify the UI if a new block tip was connected if (pindexFork != pindexNewTip) { + // Notify ValidationInterface subscribers + GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, fInitialDownload); + + // Always notify the UI if a new block tip was connected uiInterface.NotifyBlockTip(fInitialDownload, pindexNewTip); } } @@ -2750,6 +2772,7 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& return true; } + bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { return g_chainstate.ActivateBestChain(state, chainparams, std::move(pblock)); } @@ -2881,6 +2904,7 @@ bool CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { if (pindex->nStatus & BLOCK_FAILED_MASK) { pindex->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(pindex); + m_failed_blocks.erase(pindex); } pindex = pindex->pprev; } @@ -2927,7 +2951,7 @@ CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) } /** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */ -bool CChainState::ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBlockIndex *pindexNew, const CDiskBlockPos& pos, const Consensus::Params& consensusParams) +void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const CDiskBlockPos& pos, const Consensus::Params& consensusParams) { pindexNew->nTx = block.vtx.size(); pindexNew->nChainTx = 0; @@ -2971,8 +2995,6 @@ bool CChainState::ReceivedBlockTransactions(const CBlock &block, CValidationStat mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); } } - - return true; } static bool FindBlockPos(CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false) @@ -3371,7 +3393,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& 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 (g_failed_blocks), then mark pindexPrev and any blocks + // has been found invalid (m_failed_blocks), then mark pindexPrev and any blocks // between them as failed. if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) { for (const CBlockIndex* failedit : m_failed_blocks) { @@ -3484,7 +3506,6 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali // request; don't process these. if (pindex->nChainWork < nMinimumChainWork) return true; } - if (fNewBlock) *fNewBlock = true; if (!CheckBlock(block, state, chainparams.GetConsensus()) || !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { @@ -3501,14 +3522,14 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali GetMainSignals().NewPoWValidBlock(pindex, pblock); // Write block to history file + if (fNewBlock) *fNewBlock = true; try { CDiskBlockPos blockPos = SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp); if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; } - if (!ReceivedBlockTransactions(block, state, pindex, blockPos, chainparams.GetConsensus())) - return error("AcceptBlock(): ReceivedBlockTransactions failed"); + ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); } catch (const std::runtime_error& e) { return AbortNode(state, std::string("System error: ") + e.what()); } @@ -3706,6 +3727,15 @@ static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfte int count=0; if (nCurrentUsage + nBuffer >= nPruneTarget) { + // On a prune event, the chainstate DB is flushed. + // To avoid excessive prune events negating the benefit of high dbcache + // values, we should not prune too rapidly. + // So when pruning in IBD, increase the buffer a bit to avoid a re-prune too soon. + if (IsInitialBlockDownload()) { + // Since this is only relevant during IBD, we use a fixed 10% + nBuffer += nPruneTarget / 10; + } + for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) { nBytesToPrune = vinfoBlockFile[fileNumber].nSize + vinfoBlockFile[fileNumber].nUndoSize; @@ -3803,7 +3833,7 @@ CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) { - if (!blocktree.LoadBlockIndexGuts(consensus_params, [this](const uint256& hash){ return this->InsertBlockIndex(hash); })) + 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(); @@ -3811,7 +3841,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo // Calculate nChainWork std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight; vSortedByHeight.reserve(mapBlockIndex.size()); - for (const std::pair<uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) { CBlockIndex* pindex = item.second; vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex)); @@ -3853,7 +3883,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo return true; } -bool static LoadBlockIndexDB(const CChainParams& chainparams) +bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (!g_chainstate.LoadBlockIndex(chainparams.GetConsensus(), *pblocktree)) return false; @@ -3878,7 +3908,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) { CBlockIndex* pindex = item.second; if (pindex->nStatus & BLOCK_HAVE_DATA) { @@ -3903,10 +3933,6 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) pblocktree->ReadReindexing(fReindexing); if(fReindexing) fReindex = true; - // Check whether we have a transaction index - pblocktree->ReadFlag("txindex", fTxIndex); - LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); - return true; } @@ -3965,14 +3991,13 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, nCheckLevel = std::max(0, std::min(4, nCheckLevel)); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); CCoinsViewCache coins(coinsview); - CBlockIndex* pindexState = chainActive.Tip(); + CBlockIndex* pindex; CBlockIndex* pindexFailure = nullptr; int nGoodTransactions = 0; CValidationState state; int reportDone = 0; LogPrintf("[0%%]..."); /* Continued */ - for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) - { + 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)))); if (reportDone < percentageDone/10) { @@ -3981,7 +4006,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, reportDone = percentageDone/10; } uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone, false); - if (pindex->nHeight < chainActive.Height()-nCheckDepth) + if (pindex->nHeight <= chainActive.Height()-nCheckDepth) break; if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { // If pruning, only go back as far as we have data. @@ -4006,13 +4031,12 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, } } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks - if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) { + if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) { assert(coins.GetBestBlock() == pindex->GetBlockHash()); DisconnectResult res = g_chainstate.DisconnectBlock(block, pindex, coins); if (res == DISCONNECT_FAILED) { return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } - pindexState = pindex->pprev; if (res == DISCONNECT_UNCLEAN) { nGoodTransactions = 0; pindexFailure = pindex; @@ -4026,9 +4050,11 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (pindexFailure) return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainActive.Height() - pindexFailure->nHeight + 1, nGoodTransactions); + // store block count as we move pindex at check level >= 4 + int block_count = chainActive.Height() - pindex->nHeight; + // check level 4: try reconnecting blocks if (nCheckLevel >= 4) { - CBlockIndex *pindex = pindexState; 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); @@ -4042,7 +4068,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, } LogPrintf("[DONE].\n"); - LogPrintf("No coin database inconsistencies in last %i blocks (%i transactions)\n", chainActive.Height() - pindexState->nHeight, nGoodTransactions); + LogPrintf("No coin database inconsistencies in last %i blocks (%i transactions)\n", block_count, nGoodTransactions); return true; } @@ -4300,9 +4326,6 @@ bool LoadBlockIndex(const CChainParams& chainparams) // needs_init. LogPrintf("Initializing databases...\n"); - // Use the provided setting for -txindex in the new database - fTxIndex = gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX); - pblocktree->WriteFlag("txindex", fTxIndex); } return true; } @@ -4324,9 +4347,7 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); CBlockIndex *pindex = AddToBlockIndex(block); - CValidationState state; - if (!ReceivedBlockTransactions(block, state, pindex, blockPos, chainparams.GetConsensus())) - return error("%s: genesis block not accepted (%s)", __func__, FormatStateMessage(state)); + ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); } diff --git a/src/validation.h b/src/validation.h index b415a85053..869f847cdb 100644 --- a/src/validation.h +++ b/src/validation.h @@ -104,20 +104,6 @@ static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60; static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; /** Maximum length of reject messages. */ static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111; -/** Average delay between local address broadcasts in seconds. */ -static const unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24 * 60 * 60; -/** Average delay between peer address broadcasts in seconds. */ -static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30; -/** Average delay between trickled inventory transmissions in seconds. - * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */ -static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; -/** Maximum number of inventory items to send per transmission. - * Limits the impact of low-fee transaction floods. */ -static const unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL; -/** Average delay between feefilter broadcasts in seconds. */ -static const unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; -/** Maximum feefilter broadcast delay after significant change. */ -static const unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; /** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; /** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ @@ -171,7 +157,6 @@ extern uint256 g_best_block; extern std::atomic_bool fImporting; extern std::atomic_bool fReindex; extern int nScriptCheckThreads; -extern bool fTxIndex; extern bool fIsBareMultisigStd; extern bool fRequireStandard; extern bool fCheckBlockIndex; @@ -234,19 +219,21 @@ static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; * Note that we guarantee that either the proof-of-work is valid on pblock, or * (and possibly also) BlockChecked will have been called. * - * Call without cs_main held. + * May not be called in a + * validationinterface callback. * * @param[in] pblock The block we want to process. * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers. * @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call * @return True if state.IsValid() */ -bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock); +bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main); /** * Process incoming block headers. * - * Call without cs_main held. + * May not be called in a + * validationinterface callback. * * @param[in] block The block headers themselves * @param[out] state This may be set to an Error state if any error occurred processing them @@ -254,7 +241,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers * @param[out] first_invalid First header that fails validation, if one exists */ -bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex=nullptr, CBlockHeader *first_invalid=nullptr); +bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) LOCKS_EXCLUDED(cs_main); /** Check whether enough disk space is available for an incoming block */ bool CheckDiskSpace(uint64_t nAdditionalBytes = 0, bool blocks_dir = false); @@ -268,7 +255,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB bool LoadGenesisBlock(const CChainParams& chainparams); /** Load the block tree and coins database from disk, * initializing state if we're running with -reindex. */ -bool LoadBlockIndex(const CChainParams& chainparams); +bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Update the chain tip based on database information. */ bool LoadChainTip(const CChainParams& chainparams); /** Unload database information */ @@ -279,7 +266,12 @@ void ThreadScriptCheck(); bool IsInitialBlockDownload(); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256& hash, CTransactionRef& tx, const Consensus::Params& params, uint256& hashBlock, bool fAllowSlow = false, CBlockIndex* blockIndex = nullptr); -/** Find the best known block, and make it the tip of the block chain */ +/** + * Find the best known block, and make it the tip of the block chain + * + * May not be called with cs_main held. May not be called in a + * validationinterface callback. + */ bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock = std::shared_ptr<const CBlock>()); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); @@ -399,14 +391,16 @@ void InitScriptExecutionCache(); /** Functions for disk access for blocks */ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams); bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams); +bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CDiskBlockPos& pos, const CMessageHeader::MessageStartChars& message_start); +bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const CBlockIndex* pindex, const CMessageHeader::MessageStartChars& message_start); /** Functions for validating blocks and updating the block tree */ /** Context-independent validity checks */ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true); -/** Check a block is completely valid from start to finish (only works on top of our current best block, with cs_main held) */ -bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true); +/** Check a block is completely valid from start to finish (only works on top of our current best block) */ +bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Check whether witness commitments are required for block. */ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params); @@ -444,14 +438,18 @@ 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); -/** Mark a block as precious and reorganize. */ -bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex); +/** Mark a block as precious and reorganize. + * + * May not be called in a + * validationinterface callback. + */ +bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); /** Mark a block as invalid. */ -bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex); +bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Remove invalidity status from a block and its descendants. */ -bool ResetBlockFailureFlags(CBlockIndex *pindex); +bool ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** The currently-connected chain of blocks (protected by cs_main). */ extern CChain& chainActive; @@ -496,4 +494,10 @@ bool DumpMempool(); /** Load the mempool from disk. */ bool LoadMempool(); +//! Check whether the block associated with this index entry is pruned or not. +inline bool IsBlockPruned(const CBlockIndex* pblockindex) +{ + return (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0); +} + #endif // BITCOIN_VALIDATION_H diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 746263f113..aff4c44cea 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -5,7 +5,6 @@ #include <validationinterface.h> -#include <init.h> #include <primitives/block.h> #include <scheduler.h> #include <sync.h> @@ -25,8 +24,7 @@ struct MainSignalsInstance { boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected; boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected; boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool; - boost::signals2::signal<void (const CBlockLocator &)> SetBestChain; - boost::signals2::signal<void (const uint256 &)> Inventory; + boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast; boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked; boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock; @@ -80,8 +78,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { 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->SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); - g_signals.m_internals->Inventory.connect(boost::bind(&CValidationInterface::Inventory, 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)); @@ -90,8 +87,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { 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->Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); - g_signals.m_internals->SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1)); + 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)); @@ -106,8 +102,7 @@ void UnregisterAllValidationInterfaces() { } g_signals.m_internals->BlockChecked.disconnect_all_slots(); g_signals.m_internals->Broadcast.disconnect_all_slots(); - g_signals.m_internals->Inventory.disconnect_all_slots(); - g_signals.m_internals->SetBestChain.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(); @@ -166,15 +161,9 @@ void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock> &pblock }); } -void CMainSignals::SetBestChain(const CBlockLocator &locator) { +void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) { m_internals->m_schedulerClient.AddToProcessQueue([locator, this] { - m_internals->SetBestChain(locator); - }); -} - -void CMainSignals::Inventory(const uint256 &hash) { - m_internals->m_schedulerClient.AddToProcessQueue([hash, this] { - m_internals->Inventory(hash); + m_internals->ChainStateFlushed(locator); }); } diff --git a/src/validationinterface.h b/src/validationinterface.h index 63097166af..42cc2e9a20 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -61,7 +61,11 @@ protected: */ ~CValidationInterface() = default; /** - * Notifies listeners of updated block chain tip + * Notifies listeners when the block chain tip advances. + * + * When multiple blocks are connected at once, UpdatedBlockTip will be called on the final tip + * but may not be called on every intermediate tip. If the latter behavior is desired, + * subscribe to BlockConnected() instead. * * Called on a background thread. */ @@ -99,15 +103,20 @@ protected: /** * Notifies listeners of the new active block chain on-disk. * - * Called on a background thread. - */ - virtual void SetBestChain(const CBlockLocator &locator) {} - /** - * Notifies listeners about an inventory item being seen on the network. + * Prior to this callback, any updates are not guaranteed to persist on disk + * (ie clients need to handle shutdown/restart safety by being able to + * understand when some updates were lost due to unclean shutdown). + * + * When this callback is invoked, the validation changes done by any prior + * callback are guaranteed to exist on disk and survive a restart, including + * an unclean shutdown. + * + * Provides a locator describing the best chain, which is likely useful for + * storing current state on disk in client DBs. * * Called on a background thread. */ - virtual void Inventory(const uint256 &hash) {} + virtual void ChainStateFlushed(const CBlockLocator &locator) {} /** Tells listeners to broadcast their data. */ virtual void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) {} /** @@ -157,8 +166,7 @@ public: void TransactionAddedToMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &); - void SetBestChain(const CBlockLocator &); - void Inventory(const uint256 &); + void ChainStateFlushed(const CBlockLocator &); void Broadcast(int64_t nBestBlockTime, CConnman* connman); void BlockChecked(const CBlock&, const CValidationState&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 2f08162ee4..98b4298507 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -22,7 +22,7 @@ public: boost::optional<OutputType> m_change_type; //! If false, allows unselected inputs, but requires all selected inputs be used bool fAllowOtherInputs; - //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria + //! Includes watch only addresses which are solvable bool fAllowWatchOnly; //! Override automatic min/max checks on fee, m_feerate must be set if true bool fOverrideFeeRate; diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 8596ad2adc..a403411e5b 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -286,10 +286,10 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins } if (LogAcceptCategory(BCLog::SELECTCOINS)) { - LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); + LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); /* Continued */ for (unsigned int i = 0; i < vValue.size(); i++) { if (vfBest[i]) { - LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue)); + LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue)); /* Continued */ } } LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest)); diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 4c0c8ff5ec..52842cd978 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -116,7 +116,7 @@ class CCryptoKeyStore : public CBasicKeyStore { private: - CKeyingMaterial vMasterKey; + CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); //! if fUseCrypto is true, mapKeys must be empty //! if fUseCrypto is false, vMasterKey must be empty @@ -126,13 +126,15 @@ private: bool fDecryptionThoroughlyChecked; protected: + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + bool SetCrypted(); //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); bool Unlock(const CKeyingMaterial& vMasterKeyIn); - CryptedKeyMap mapCryptedKeys; + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); public: CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 10a06e4b9a..01b8eacccb 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -53,7 +53,7 @@ void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filena } CCriticalSection cs_db; -std::map<std::string, BerkeleyEnvironment> g_dbenvs; //!< Map from directory name to open db environment. +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) @@ -102,7 +102,7 @@ void BerkeleyEnvironment::Close() int ret = dbenv->close(0); if (ret != 0) - LogPrintf("BerkeleyEnvironment::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret)); + LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret)); if (!fMockDb) DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); } @@ -168,8 +168,12 @@ bool BerkeleyEnvironment::Open(bool retry) nEnvFlags, S_IRUSR | S_IWUSR); if (ret != 0) { - dbenv->close(0); LogPrintf("BerkeleyEnvironment::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); + int ret2 = dbenv->close(0); + if (ret2 != 0) { + LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2)); + } + Reset(); if (retry) { // try moving the database env out of the way fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime()); @@ -690,8 +694,10 @@ void BerkeleyEnvironment::Flush(bool fShutdown) if (mapFileUseCount.empty()) { dbenv->log_archive(&listp, DB_ARCH_REMOVE); Close(); - if (!fMockDb) + if (!fMockDb) { fs::remove_all(fs::path(strPath) / "database"); + } + g_dbenvs.erase(strPath); } } } @@ -790,5 +796,6 @@ void BerkeleyDatabase::Flush(bool shutdown) { if (!IsDummy()) { env->Flush(shutdown); + if (shutdown) env = nullptr; } } diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 4d70dde72a..0eb85a6e5c 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -18,7 +18,7 @@ //! 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) +static feebumper::Result PreconditionChecks(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"); @@ -185,7 +185,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // If the output is not large enough to pay the fee, fail. CAmount nDelta = new_fee - old_fee; assert(nDelta > 0); - mtx = *wtx.tx; + mtx = CMutableTransaction{*wtx.tx}; CTxOut* poutput = &(mtx.vout[nOutput]); if (poutput->nValue < nDelta) { errors.push_back("Change output is too small to bump the fee"); diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 06f9c730c0..076134cdd1 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -6,6 +6,8 @@ #include <chainparams.h> #include <init.h> #include <net.h> +#include <scheduler.h> +#include <outputtype.h> #include <util.h> #include <utilmoneystr.h> #include <validation.h> @@ -18,7 +20,7 @@ class WalletInit : public WalletInitInterface { public: //! Return the wallets help message. - std::string GetHelpString(bool showDebug) const override; + void AddWalletOptions() const override; //! Wallets parameter interaction bool ParameterInteraction() const override; @@ -49,46 +51,38 @@ public: const WalletInitInterface& g_wallet_init_interface = WalletInit(); -std::string WalletInit::GetHelpString(bool showDebug) const +void WalletInit::AddWalletOptions() const { - std::string strUsage = HelpMessageGroup(_("Wallet options:")); - strUsage += HelpMessageOpt("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE))); - strUsage += HelpMessageOpt("-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)"); - strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); - strUsage += HelpMessageOpt("-discardfee=<amt>", strprintf(_("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " - "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target"), - CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE))); - strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), - CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); - strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); - strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE))); - strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), - CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK()))); - strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup")); - strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup")); - strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); - strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET)); - strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); - strUsage += HelpMessageOpt("-wallet=<path>", _("Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)")); - strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); - strUsage += HelpMessageOpt("-walletdir=<dir>", _("Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)")); - strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); - strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)"), DEFAULT_WALLET_RBF)); - strUsage += HelpMessageOpt("-zapwallettxes=<mode>", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + - " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); - - if (showDebug) - { - strUsage += HelpMessageGroup(_("Wallet debugging/testing options:")); - - strUsage += HelpMessageOpt("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE)); - strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET)); - strUsage += HelpMessageOpt("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB)); - strUsage += HelpMessageOpt("-walletrejectlongchains", strprintf(_("Wallet will not create transactions that violate mempool chain limits (default: %u)"), DEFAULT_WALLET_REJECT_LONG_CHAINS)); - } - - return strUsage; + 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("-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). " + "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target", + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), false, OptionsCategory::WALLET); + gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)", + CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), false, OptionsCategory::WALLET); + gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), false, OptionsCategory::WALLET); + gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)", + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), false, OptionsCategory::WALLET); + gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)", + CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), false, OptionsCategory::WALLET); + gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", false, OptionsCategory::WALLET); + gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", false, OptionsCategory::WALLET); + gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), false, OptionsCategory::WALLET); + gArgs.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), false, OptionsCategory::WALLET); + gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", false, OptionsCategory::WALLET); + gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", false, OptionsCategory::WALLET); + gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), false, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", false, OptionsCategory::WALLET); + gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", false, OptionsCategory::WALLET); + gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), false, OptionsCategory::WALLET); + gArgs.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup" + " (1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)", false, OptionsCategory::WALLET); + + gArgs.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), true, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), true, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), true, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), true, OptionsCategory::WALLET_DEBUG_TEST); } bool WalletInit::ParameterInteraction() const @@ -197,55 +191,29 @@ bool WalletInit::Verify() const 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. + bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; + // Keep track of each wallet absolute path to detect duplicates. std::set<fs::path> wallet_paths; - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - // Do some checking on wallet path. It should be either a: - // - // 1. Path where a directory can be created. - // 2. Path to an existing directory. - // 3. Path to a symlink to a directory. - // 4. For backwards compatibility, the name of a data file in -walletdir. - fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); - 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(walletFile).filename() == walletFile))) { - return InitError(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)"), - walletFile, GetWalletDir())); - } + for (const auto& wallet_file : wallet_files) { + fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); if (!wallet_paths.insert(wallet_path).second) { - return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile)); + return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); } - std::string strError; - if (!WalletBatch::VerifyEnvironment(wallet_path, strError)) { - return InitError(strError); - } - - if (gArgs.GetBoolArg("-salvagewallet", false)) { - // Recover readable keypairs: - CWallet dummyWallet("dummy", WalletDatabase::CreateDummy()); - std::string backup_filename; - if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { - return false; - } - } - - std::string strWarning; - bool dbV = WalletBatch::VerifyDatabaseFile(wallet_path, strWarning, strError); - if (!strWarning.empty()) { - InitWarning(strWarning); - } - if (!dbV) { - InitError(strError); - return false; - } + std::string error_string; + std::string warning_string; + bool verify_success = CWallet::Verify(wallet_file, 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; } return true; @@ -259,7 +227,7 @@ bool WalletInit::Open() const } for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); if (!pwallet) { return false; } @@ -271,29 +239,31 @@ bool WalletInit::Open() const void WalletInit::Start(CScheduler& scheduler) const { - for (CWallet* pwallet : GetWallets()) { - pwallet->postInitProcess(scheduler); + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->postInitProcess(); } + + // Run a thread to flush wallet periodically + scheduler.scheduleEvery(MaybeCompactWalletDB, 500); } void WalletInit::Flush() const { - for (CWallet* pwallet : GetWallets()) { + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(false); } } void WalletInit::Stop() const { - for (CWallet* pwallet : GetWallets()) { + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(true); } } void WalletInit::Close() const { - for (CWallet* pwallet : GetWallets()) { + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { RemoveWallet(pwallet); - delete pwallet; } } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 4eb214b8b5..71fd973581 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -4,7 +4,6 @@ #include <chain.h> #include <key_io.h> -#include <rpc/safemode.h> #include <rpc/server.h> #include <validation.h> #include <script/script.h> @@ -52,12 +51,12 @@ std::string static EncodeDumpString(const std::string &str) { return ret.str(); } -std::string DecodeDumpString(const std::string &str) { +static std::string DecodeDumpString(const std::string &str) { std::stringstream ret; for (unsigned int pos = 0; pos < str.length(); pos++) { unsigned char c = str[pos]; if (c == '%' && pos+2 < str.length()) { - c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | + c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); pos += 2; } @@ -66,7 +65,7 @@ std::string DecodeDumpString(const std::string &str) { return ret.str(); } -bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel) +static bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel) { bool fLabelFound = false; CKey key; @@ -90,7 +89,8 @@ bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std: UniValue importprivkey(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -186,7 +186,8 @@ UniValue importprivkey(const JSONRPCRequest& request) UniValue abortrescan(const JSONRPCRequest& request) { - CWallet* const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -204,14 +205,13 @@ UniValue abortrescan(const JSONRPCRequest& request) + HelpExampleRpc("abortrescan", "") ); - ObserveSafeMode(); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); return true; } -void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel); -void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) +static void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel); +static void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); @@ -224,10 +224,11 @@ void ImportScript(CWallet* const pwallet, const CScript& script, const std::stri } if (isRedeemScript) { - if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) { + const CScriptID id(script); + if (!pwallet->HaveCScript(id) && !pwallet->AddCScript(script)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } - ImportAddress(pwallet, CScriptID(script), strLabel); + ImportAddress(pwallet, id, strLabel); } else { CTxDestination destination; if (ExtractDestination(script, destination)) { @@ -236,7 +237,7 @@ void ImportScript(CWallet* const pwallet, const CScript& script, const std::stri } } -void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel) +static void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { CScript script = GetScriptForDestination(dest); ImportScript(pwallet, script, strLabel, false); @@ -247,7 +248,8 @@ void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std UniValue importaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -331,7 +333,8 @@ UniValue importaddress(const JSONRPCRequest& request) UniValue importprunedfunds(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -393,7 +396,8 @@ UniValue importprunedfunds(const JSONRPCRequest& request) UniValue removeprunedfunds(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -431,12 +435,13 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) UniValue importpubkey(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(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() > 4) + 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" @@ -507,7 +512,8 @@ UniValue importpubkey(const JSONRPCRequest& request) UniValue importwallet(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -553,7 +559,7 @@ UniValue importwallet(const JSONRPCRequest& request) file.seekg(0, file.beg); // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which - // we don't want for this progress bar shoing the import progress. uiInterface.ShowProgress does not have a cancel button. + // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. uiInterface.ShowProgress(_("Importing..."), 0, false); // show progress dialog in GUI while (file.good()) { uiInterface.ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); @@ -602,7 +608,8 @@ UniValue importwallet(const JSONRPCRequest& request) } else if(IsHex(vstr[0])) { std::vector<unsigned char> vData(ParseHex(vstr[0])); CScript script = CScript(vData.begin(), vData.end()); - if (pwallet->HaveCScript(script)) { + CScriptID id(script); + if (pwallet->HaveCScript(id)) { LogPrintf("Skipping import of %s (script already present)\n", vstr[0]); continue; } @@ -613,7 +620,7 @@ UniValue importwallet(const JSONRPCRequest& request) } int64_t birth_time = DecodeDumpTime(vstr[1]); if (birth_time > 0) { - pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time; + pwallet->m_script_metadata[id].nCreateTime = birth_time; nTimeBegin = std::min(nTimeBegin, birth_time); } } @@ -640,7 +647,8 @@ UniValue importwallet(const JSONRPCRequest& request) UniValue dumpprivkey(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -683,7 +691,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) UniValue dumpwallet(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -752,13 +761,13 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << "\n"; // add the base58check encoded extended master if the wallet uses HD - CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; - if (!masterKeyID.IsNull()) + CKeyID seed_id = pwallet->GetHDChain().seed_id; + if (!seed_id.IsNull()) { - CKey key; - if (pwallet->GetKey(masterKeyID, key)) { + CKey seed; + if (pwallet->GetKey(seed_id, seed)) { CExtKey masterKey; - masterKey.SetMaster(key.begin(), key.size()); + masterKey.SetSeed(seed.begin(), seed.size()); file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n"; } @@ -773,12 +782,12 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << strprintf("%s %s ", EncodeSecret(key), strTime); if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); - } else if (keyid == masterKeyID) { - file << "hdmaster=1"; + } else if (keyid == seed_id) { + file << "hdseed=1"; } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; - } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "m") { - file << "inactivehdmaster=1"; + } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "s") { + file << "inactivehdseed=1"; } else { file << "change=1"; } @@ -811,7 +820,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) } -UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) +static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { try { bool success = false; @@ -899,12 +908,12 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - if (!pwallet->HaveCScript(redeemScript) && !pwallet->AddCScript(redeemScript)) { + CScriptID redeem_id(redeemScript); + if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } - CTxDestination redeem_dest = CScriptID(redeemScript); - CScript redeemDestination = GetScriptForDestination(redeem_dest); + 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"); @@ -1111,7 +1120,7 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6 } } -int64_t GetImportTimestamp(const UniValue& data, int64_t now) +static int64_t GetImportTimestamp(const UniValue& data, int64_t now) { if (data.exists("timestamp")) { const UniValue& timestamp = data["timestamp"]; @@ -1127,7 +1136,8 @@ int64_t GetImportTimestamp(const UniValue& data, int64_t now) UniValue importmulti(const JSONRPCRequest& mainRequest) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(mainRequest); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(mainRequest); + CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp)) { return NullUniValue; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c4c6701081..78ea5eac92 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 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. @@ -11,16 +11,17 @@ #include <validation.h> #include <key_io.h> #include <net.h> +#include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> #include <rpc/mining.h> #include <rpc/rawtransaction.h> -#include <rpc/safemode.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/sign.h> +#include <shutdown.h> #include <timedata.h> #include <util.h> #include <utilmoneystr.h> @@ -31,8 +32,6 @@ #include <wallet/walletdb.h> #include <wallet/walletutil.h> -#include <init.h> // For StartShutdown - #include <stdint.h> #include <univalue.h> @@ -41,17 +40,26 @@ static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; -CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) +bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { // wallet endpoint was used - std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); - CWallet* pwallet = GetWallet(requestedWallet); + wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + return true; + } + return false; +} + +std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) +{ + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name); if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); return pwallet; } - std::vector<CWallet*> wallets = GetWallets(); + std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(); return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr; } @@ -67,11 +75,6 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) if (pwallet) return true; if (avoidException) return false; if (!HasWallets()) { - // Note: It isn't currently possible to trigger this error because - // wallet RPC methods aren't registered unless a wallet is loaded. But - // this error is being kept as a precaution, because it's possible in - // the future that wallet RPC methods might get or remain registered - // when no wallets are loaded. throw JSONRPCError( RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)"); } @@ -86,7 +89,7 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet) } } -void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) +static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) { int confirms = wtx.GetDepthInMainChain(); entry.pushKV("confirmations", confirms); @@ -121,11 +124,11 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) } entry.pushKV("bip125-replaceable", rbfStatus); - for (const std::pair<std::string, std::string>& item : wtx.mapValue) + for (const std::pair<const std::string, std::string>& item : wtx.mapValue) entry.pushKV(item.first, item.second); } -std::string LabelFromValue(const UniValue& value) +static std::string LabelFromValue(const UniValue& value) { std::string label = value.get_str(); if (label == "*") @@ -133,9 +136,11 @@ std::string LabelFromValue(const UniValue& value) return label; } -UniValue getnewaddress(const JSONRPCRequest& request) +static UniValue getnewaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -165,8 +170,7 @@ UniValue getnewaddress(const JSONRPCRequest& request) OutputType output_type = pwallet->m_default_address_type; if (!request.params[1].isNull()) { - output_type = ParseOutputType(request.params[1].get_str(), pwallet->m_default_address_type); - if (output_type == OutputType::NONE) { + if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } } @@ -198,67 +202,54 @@ CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& la return dest; } -UniValue getlabeladdress(const JSONRPCRequest& request) +static UniValue getaccountaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } - if (!IsDeprecatedRPCEnabled("accounts") && request.strMethod == "getaccountaddress") { + if (!IsDeprecatedRPCEnabled("accounts")) { if (request.fHelp) { throw std::runtime_error("getaccountaddress (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)"); } throw JSONRPCError(RPC_METHOD_DEPRECATED, "getaccountaddress is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts."); } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "getlabeladdress \"label\" ( force ) \n" - "\nReturns the default receiving address for this label. This will reset to a fresh address once there's a transaction that spends to it.\n" + "getaccountaddress \"account\"\n" + "\n\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n" "\nArguments:\n" - "1. \"label\" (string, required) The label for the address. It can also be set to the empty string \"\" to represent the default label.\n" - "2. \"force\" (bool, optional) Whether the label should be created if it does not yet exist. If False, the RPC will return an error if called with a label that doesn't exist.\n" - " Defaults to false (unless the getaccountaddress method alias is being called, in which case defaults to true for backwards compatibility).\n" + "1. \"account\" (string, required) The account for the address. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created and a new address created if there is no account by the given name.\n" "\nResult:\n" - "\"address\" (string) The current receiving address for the label.\n" + "\"address\" (string) The account bitcoin address\n" "\nExamples:\n" - + HelpExampleCli("getlabeladdress", "") - + HelpExampleCli("getlabeladdress", "\"\"") - + HelpExampleCli("getlabeladdress", "\"mylabel\"") - + HelpExampleRpc("getlabeladdress", "\"mylabel\"") + + HelpExampleCli("getaccountaddress", "") + + HelpExampleCli("getaccountaddress", "\"\"") + + HelpExampleCli("getaccountaddress", "\"myaccount\"") + + HelpExampleRpc("getaccountaddress", "\"myaccount\"") ); LOCK2(cs_main, pwallet->cs_wallet); - // Parse the label first so we don't generate a key if there's an error - std::string label = LabelFromValue(request.params[0]); - bool force = request.strMethod == "getaccountaddress"; - if (!request.params[1].isNull()) { - force = request.params[1].get_bool(); - } - - bool label_found = false; - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { - if (item.second.name == label) { - label_found = true; - break; - } - } - if (!force && !label_found) { - throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label)); - } + // Parse the account first so we don't generate a key if there's an error + std::string account = LabelFromValue(request.params[0]); UniValue ret(UniValue::VSTR); - ret = EncodeDestination(GetLabelDestination(pwallet, label)); + ret = EncodeDestination(GetLabelDestination(pwallet, account)); return ret; } -UniValue getrawchangeaddress(const JSONRPCRequest& request) +static UniValue getrawchangeaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -283,10 +274,9 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); } - OutputType output_type = pwallet->m_default_change_type != OutputType::NONE ? pwallet->m_default_change_type : pwallet->m_default_address_type; + OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type; if (!request.params[0].isNull()) { - output_type = ParseOutputType(request.params[0].get_str(), output_type); - if (output_type == OutputType::NONE) { + if (!ParseOutputType(request.params[0].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); } } @@ -305,9 +295,11 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) } -UniValue setlabel(const JSONRPCRequest& request) +static UniValue setlabel(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -338,30 +330,42 @@ UniValue setlabel(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } + std::string old_label = pwallet->mapAddressBook[dest].name; std::string label = LabelFromValue(request.params[1]); if (IsMine(*pwallet, dest)) { - // Detect when changing the label of an address that is the receiving address of another label: - // If so, delete the account record for it. Labels, unlike addresses, can be deleted, - // and if we wouldn't do this, the record would stick around forever. - if (pwallet->mapAddressBook.count(dest)) { - std::string old_label = pwallet->mapAddressBook[dest].name; - if (old_label != label && dest == GetLabelDestination(pwallet, old_label)) { - pwallet->DeleteLabel(old_label); - } - } pwallet->SetAddressBook(dest, label, "receive"); + if (request.strMethod == "setaccount" && old_label != label && dest == GetLabelDestination(pwallet, old_label)) { + // for setaccount, call GetLabelDestination so a new receive address is created for the old account + GetLabelDestination(pwallet, old_label, true); + } } else { pwallet->SetAddressBook(dest, label, "send"); } + // Detect when there are no addresses using this label. + // If so, delete the account record for it. Labels, unlike addresses, can be deleted, + // and if we wouldn't do this, the record would stick around forever. + bool found_address = false; + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + if (item.second.name == label) { + found_address = true; + break; + } + } + if (!found_address) { + pwallet->DeleteLabel(old_label); + } + return NullUniValue; } -UniValue getaccount(const JSONRPCRequest& request) +static UniValue getaccount(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -402,9 +406,11 @@ UniValue getaccount(const JSONRPCRequest& request) } -UniValue getaddressesbyaccount(const JSONRPCRequest& request) +static UniValue getaddressesbyaccount(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -438,7 +444,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request) // Find all addresses that have the given account UniValue ret(UniValue::VARR); - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { const CTxDestination& dest = item.first; const std::string& strName = item.second.name; if (strName == strAccount) { @@ -488,9 +494,11 @@ static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination & return tx; } -UniValue sendtoaddress(const JSONRPCRequest& request) +static UniValue sendtoaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -525,8 +533,6 @@ UniValue sendtoaddress(const JSONRPCRequest& request) + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") ); - ObserveSafeMode(); - // 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(); @@ -577,9 +583,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request) return tx->GetHash().GetHex(); } -UniValue listaddressgroupings(const JSONRPCRequest& request) +static UniValue listaddressgroupings(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -607,8 +615,6 @@ UniValue listaddressgroupings(const JSONRPCRequest& request) + HelpExampleRpc("listaddressgroupings", "") ); - ObserveSafeMode(); - // 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(); @@ -636,9 +642,11 @@ UniValue listaddressgroupings(const JSONRPCRequest& request) return jsonGroupings; } -UniValue signmessage(const JSONRPCRequest& request) +static UniValue signmessage(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -697,9 +705,11 @@ UniValue signmessage(const JSONRPCRequest& request) return EncodeBase64(vchSig.data(), vchSig.size()); } -UniValue getreceivedbyaddress(const JSONRPCRequest& request) +static UniValue getreceivedbyaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -724,8 +734,6 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") ); - ObserveSafeMode(); - // 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(); @@ -749,7 +757,7 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) // Tally CAmount nAmount = 0; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; @@ -764,9 +772,11 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) } -UniValue getreceivedbylabel(const JSONRPCRequest& request) +static UniValue getreceivedbylabel(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -798,8 +808,6 @@ UniValue getreceivedbylabel(const JSONRPCRequest& request) + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") ); - ObserveSafeMode(); - // 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(); @@ -817,7 +825,7 @@ UniValue getreceivedbylabel(const JSONRPCRequest& request) // Tally CAmount nAmount = 0; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; @@ -836,15 +844,18 @@ UniValue getreceivedbylabel(const JSONRPCRequest& request) } -UniValue getbalance(const JSONRPCRequest& request) +static UniValue getbalance(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(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() > 3 && IsDeprecatedRPCEnabled("accounts")) || (request.params.size() != 0 && !IsDeprecatedRPCEnabled("accounts"))) + if (request.fHelp || (request.params.size() > 3 )) throw std::runtime_error( + (IsDeprecatedRPCEnabled("accounts") ? std::string( "getbalance ( \"account\" minconf include_watchonly )\n" "\nIf account is not specified, returns the server's total available balance.\n" "The available balance is what the wallet considers currently spendable, and is\n" @@ -866,8 +877,17 @@ UniValue getbalance(const JSONRPCRequest& request) " balances. In general, account balance calculation is not considered\n" " reliable and has resulted in confusing outcomes, so it is recommended to\n" " avoid passing this argument.\n" - "2. minconf (numeric, optional, default=1) DEPRECATED. Only valid when an account is specified. This argument will be removed in V0.18. To use this deprecated argument, start bitcoind with -deprecatedrpc=accounts. Only include transactions confirmed at least this many times.\n" - "3. include_watchonly (bool, optional, default=false) DEPRECATED. Only valid when an account is specified. This argument will be removed in V0.18. To use this deprecated argument, start bitcoind with -deprecatedrpc=accounts. Also include balance in watch-only addresses (see 'importaddress')\n" + "2. minconf (numeric, optional) Only include transactions confirmed at least this many times. \n" + " The default is 1 if an account is provided or 0 if no account is provided\n") + : std::string( + "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" "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "\nExamples:\n" @@ -879,51 +899,48 @@ UniValue getbalance(const JSONRPCRequest& request) + HelpExampleRpc("getbalance", "\"*\", 6") ); - ObserveSafeMode(); - // 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); - if (IsDeprecatedRPCEnabled("accounts")) { - const UniValue& account_value = request.params[0]; - const UniValue& minconf = request.params[1]; - const UniValue& include_watchonly = request.params[2]; + const UniValue& account_value = request.params[0]; - if (account_value.isNull()) { - if (!minconf.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "getbalance minconf option is only currently supported if an account is specified"); - } - if (!include_watchonly.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "getbalance include_watchonly option is only currently supported if an account is specified"); - } - return ValueFromAmount(pwallet->GetBalance()); - } + int min_depth = 0; + if (IsDeprecatedRPCEnabled("accounts") && !account_value.isNull()) { + // Default min_depth to 1 when an account is provided. + min_depth = 1; + } + if (!request.params[1].isNull()) { + min_depth = request.params[1].get_int(); + } + + isminefilter filter = ISMINE_SPENDABLE; + if (!request.params[2].isNull() && request.params[2].get_bool()) { + filter = filter | ISMINE_WATCH_ONLY; + } + + if (!account_value.isNull()) { const std::string& account_param = account_value.get_str(); const std::string* account = account_param != "*" ? &account_param : nullptr; - int nMinDepth = 1; - if (!minconf.isNull()) - nMinDepth = minconf.get_int(); - isminefilter filter = ISMINE_SPENDABLE; - if(!include_watchonly.isNull()) - if(include_watchonly.get_bool()) - filter = filter | ISMINE_WATCH_ONLY; - - return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account)); + if (!IsDeprecatedRPCEnabled("accounts") && account_param != "*") { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); + } else if (IsDeprecatedRPCEnabled("accounts")) { + return ValueFromAmount(pwallet->GetLegacyBalance(filter, min_depth, account)); + } } - return ValueFromAmount(pwallet->GetBalance()); + return ValueFromAmount(pwallet->GetBalance(filter, min_depth)); } -UniValue getunconfirmedbalance(const JSONRPCRequest &request) +static UniValue getunconfirmedbalance(const JSONRPCRequest &request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -933,8 +950,6 @@ UniValue getunconfirmedbalance(const JSONRPCRequest &request) "getunconfirmedbalance\n" "Returns the server's total unconfirmed balance\n"); - ObserveSafeMode(); - // 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(); @@ -945,9 +960,11 @@ UniValue getunconfirmedbalance(const JSONRPCRequest &request) } -UniValue movecmd(const JSONRPCRequest& request) +static UniValue movecmd(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -980,7 +997,6 @@ UniValue movecmd(const JSONRPCRequest& request) + HelpExampleRpc("move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"") ); - ObserveSafeMode(); LOCK2(cs_main, pwallet->cs_wallet); std::string strFrom = LabelFromValue(request.params[0]); @@ -1003,13 +1019,23 @@ UniValue movecmd(const JSONRPCRequest& request) } -UniValue sendfrom(const JSONRPCRequest& request) +static UniValue sendfrom(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } + if (!IsDeprecatedRPCEnabled("accounts")) { + if (request.fHelp) { + throw std::runtime_error("sendfrom (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)"); + } + throw JSONRPCError(RPC_METHOD_DEPRECATED, "sendfrom is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts."); + } + + if (request.fHelp || request.params.size() < 3 || request.params.size() > 6) throw std::runtime_error( "sendfrom \"fromaccount\" \"toaddress\" amount ( minconf \"comment\" \"comment_to\" )\n" @@ -1039,8 +1065,6 @@ UniValue sendfrom(const JSONRPCRequest& request) + HelpExampleRpc("sendfrom", "\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.01, 6, \"donation\", \"seans outpost\"") ); - ObserveSafeMode(); - // 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(); @@ -1078,9 +1102,11 @@ UniValue sendfrom(const JSONRPCRequest& request) } -UniValue sendmany(const JSONRPCRequest& request) +static UniValue sendmany(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1170,8 +1196,6 @@ UniValue sendmany(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error(help_text); - ObserveSafeMode(); - // 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(); @@ -1250,9 +1274,11 @@ UniValue sendmany(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); // Check funds - CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); - if (totalAmount > nBalance) + if (IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount)) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); + } else if (!IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, nullptr)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds"); + } // Shuffle recipient list std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); @@ -1275,9 +1301,11 @@ UniValue sendmany(const JSONRPCRequest& request) return tx->GetHash().GetHex(); } -UniValue addmultisigaddress(const JSONRPCRequest& request) +static UniValue addmultisigaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1335,16 +1363,14 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) OutputType output_type = pwallet->m_default_address_type; if (!request.params[3].isNull()) { - output_type = ParseOutputType(request.params[3].get_str(), output_type); - if (output_type == OutputType::NONE) { + if (!ParseOutputType(request.params[3].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); } } // Construct using pay-to-script-hash: CScript inner = CreateMultisigRedeemscript(required, pubkeys); - pwallet->AddCScript(inner); - CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type); + CTxDestination dest = AddAndGetDestinationForScript(*pwallet, inner, output_type); pwallet->SetAddressBook(dest, label, "send"); UniValue result(UniValue::VOBJ); @@ -1411,9 +1437,11 @@ public: bool operator()(const T& dest) { return false; } }; -UniValue addwitnessaddress(const JSONRPCRequest& request) +static UniValue addwitnessaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1442,13 +1470,6 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); } - { - LOCK(cs_main); - if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Segregated witness not enabled on network"); - } - } - CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); @@ -1497,7 +1518,7 @@ struct tallyitem } }; -UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) +static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) { // Minimum confirmations int nMinDepth = 1; @@ -1526,7 +1547,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_l // Tally std::map<CTxDestination, tallyitem> mapTally; - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) @@ -1643,9 +1664,11 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_l return ret; } -UniValue listreceivedbyaddress(const JSONRPCRequest& request) +static UniValue listreceivedbyaddress(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1683,8 +1706,6 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") ); - ObserveSafeMode(); - // 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(); @@ -1694,9 +1715,11 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request) return ListReceived(pwallet, request.params, false); } -UniValue listreceivedbylabel(const JSONRPCRequest& request) +static UniValue listreceivedbylabel(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1735,8 +1758,6 @@ UniValue listreceivedbylabel(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbylabel", "6, true, true") ); - ObserveSafeMode(); - // 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(); @@ -1764,7 +1785,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param ret The UniValue into which the result is stored. * @param filter The "is mine" filter bool. */ -void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) +static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) { CAmount nFee; std::string strSentAccount; @@ -1844,7 +1865,7 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s } } -void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret) +static void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret) { bool fAllAccounts = (strAccount == std::string("*")); @@ -1863,7 +1884,9 @@ void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccoun UniValue listtransactions(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -1974,8 +1997,6 @@ UniValue listtransactions(const JSONRPCRequest& request) } if (request.fHelp || request.params.size() > 4) throw std::runtime_error(help_text); - ObserveSafeMode(); - // 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(); @@ -2051,9 +2072,11 @@ UniValue listtransactions(const JSONRPCRequest& request) return ret; } -UniValue listaccounts(const JSONRPCRequest& request) +static UniValue listaccounts(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2088,8 +2111,6 @@ UniValue listaccounts(const JSONRPCRequest& request) + HelpExampleRpc("listaccounts", "6") ); - ObserveSafeMode(); - // 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(); @@ -2105,13 +2126,13 @@ UniValue listaccounts(const JSONRPCRequest& request) includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; std::map<std::string, CAmount> mapAccountBalances; - for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { if (IsMine(*pwallet, entry.first) & includeWatchonly) { // This address belongs to me mapAccountBalances[entry.second.name] = 0; } } - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; CAmount nFee; std::string strSentAccount; @@ -2140,15 +2161,17 @@ UniValue listaccounts(const JSONRPCRequest& request) mapAccountBalances[entry.strAccount] += entry.nCreditDebit; UniValue ret(UniValue::VOBJ); - for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) { + for (const std::pair<const std::string, CAmount>& accountBalance : mapAccountBalances) { ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second)); } return ret; } -UniValue listsinceblock(const JSONRPCRequest& request) +static UniValue listsinceblock(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2202,8 +2225,6 @@ UniValue listsinceblock(const JSONRPCRequest& request) + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") ); - ObserveSafeMode(); - // 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(); @@ -2249,7 +2270,7 @@ UniValue listsinceblock(const JSONRPCRequest& request) UniValue transactions(UniValue::VARR); - for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; if (depth == -1 || tx.GetDepthInMainChain() < depth) { @@ -2287,9 +2308,11 @@ UniValue listsinceblock(const JSONRPCRequest& request) return ret; } -UniValue gettransaction(const JSONRPCRequest& request) +static UniValue gettransaction(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2339,8 +2362,6 @@ UniValue gettransaction(const JSONRPCRequest& request) + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") ); - ObserveSafeMode(); - // 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(); @@ -2383,9 +2404,11 @@ UniValue gettransaction(const JSONRPCRequest& request) return entry; } -UniValue abandontransaction(const JSONRPCRequest& request) +static UniValue abandontransaction(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2407,8 +2430,6 @@ UniValue abandontransaction(const JSONRPCRequest& request) ); } - ObserveSafeMode(); - // 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(); @@ -2429,9 +2450,11 @@ UniValue abandontransaction(const JSONRPCRequest& request) } -UniValue backupwallet(const JSONRPCRequest& request) +static UniValue backupwallet(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2462,9 +2485,11 @@ UniValue backupwallet(const JSONRPCRequest& request) } -UniValue keypoolrefill(const JSONRPCRequest& request) +static UniValue keypoolrefill(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2509,9 +2534,11 @@ static void LockWallet(CWallet* pWallet) pWallet->Lock(); } -UniValue walletpassphrase(const JSONRPCRequest& request) +static UniValue walletpassphrase(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2582,9 +2609,11 @@ UniValue walletpassphrase(const JSONRPCRequest& request) } -UniValue walletpassphrasechange(const JSONRPCRequest& request) +static UniValue walletpassphrasechange(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2631,9 +2660,11 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) } -UniValue walletlock(const JSONRPCRequest& request) +static UniValue walletlock(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2669,9 +2700,11 @@ UniValue walletlock(const JSONRPCRequest& request) } -UniValue encryptwallet(const JSONRPCRequest& request) +static UniValue encryptwallet(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2729,9 +2762,11 @@ UniValue encryptwallet(const JSONRPCRequest& request) return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup."; } -UniValue lockunspent(const JSONRPCRequest& request) +static UniValue lockunspent(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2856,9 +2891,11 @@ UniValue lockunspent(const JSONRPCRequest& request) return true; } -UniValue listlockunspent(const JSONRPCRequest& request) +static UniValue listlockunspent(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2889,7 +2926,6 @@ UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleRpc("listlockunspent", "") ); - ObserveSafeMode(); LOCK2(cs_main, pwallet->cs_wallet); std::vector<COutPoint> vOutpts; @@ -2908,9 +2944,11 @@ UniValue listlockunspent(const JSONRPCRequest& request) return ret; } -UniValue settxfee(const JSONRPCRequest& request) +static UniValue settxfee(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2937,9 +2975,11 @@ UniValue settxfee(const JSONRPCRequest& request) return true; } -UniValue getwalletinfo(const JSONRPCRequest& request) +static UniValue getwalletinfo(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -2961,15 +3001,14 @@ UniValue getwalletinfo(const JSONRPCRequest& request) " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" - " \"hdmasterkeyid\": \"<hash160>\" (string, optional) the Hash160 of the HD master pubkey (only present when HD is enabled)\n" + " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" + " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") ); - ObserveSafeMode(); - // 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(); @@ -2987,20 +3026,22 @@ UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("txcount", (int)pwallet->mapWallet.size()); obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); - CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; - if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { + CKeyID seed_id = pwallet->GetHDChain().seed_id; + if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } if (pwallet->IsCrypted()) { obj.pushKV("unlocked_until", pwallet->nRelockTime); } obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); - if (!masterKeyID.IsNull()) - obj.pushKV("hdmasterkeyid", masterKeyID.GetHex()); + if (!seed_id.IsNull()) { + obj.pushKV("hdseedid", seed_id.GetHex()); + obj.pushKV("hdmasterkeyid", seed_id.GetHex()); + } return obj; } -UniValue listwallets(const JSONRPCRequest& request) +static UniValue listwallets(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -3019,22 +3060,173 @@ UniValue listwallets(const JSONRPCRequest& request) UniValue obj(UniValue::VARR); - for (CWallet* pwallet : GetWallets()) { - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + for (const std::shared_ptr<CWallet>& wallet : GetWallets()) { + if (!EnsureWalletIsAvailable(wallet.get(), request.fHelp)) { return NullUniValue; } - LOCK(pwallet->cs_wallet); + LOCK(wallet->cs_wallet); + + obj.push_back(wallet->GetName()); + } + + return obj; +} + +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" + "\nResult:\n" + "{\n" + " \"name\" : <wallet_name>, (string) The wallet name if loaded successfully.\n" + " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("loadwallet", "\"test.dat\"") + + HelpExampleRpc("loadwallet", "\"test.dat\"") + ); + std::string wallet_file = 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)) { + // The given filename is a directory. Check that there's a wallet.dat file. + fs::path wallet_dat_file = wallet_path / "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."); + } + } + + std::string warning; + if (!CWallet::Verify(wallet_file, 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())); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); + } + AddWallet(wallet); + + wallet->postInitProcess(); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", warning); + + return obj; +} + +static UniValue createwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "createwallet \"wallet_name\"\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" + "\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" + " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("createwallet", "\"testwallet\"") + + HelpExampleRpc("createwallet", "\"testwallet\"") + ); + } + std::string wallet_name = request.params[0].get_str(); + std::string error; + std::string warning; + + 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."); + } + + // Wallet::Verify will check if we're trying to create a wallet with a duplication name. + if (!CWallet::Verify(wallet_name, false, error, warning)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); + } - obj.push_back(pwallet->GetName()); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir())); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } + AddWallet(wallet); + + wallet->postInitProcess(); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", warning); return obj; } -UniValue resendwallettransactions(const JSONRPCRequest& request) +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" + "\nExamples:\n" + + HelpExampleCli("unloadwallet", "wallet_name") + + HelpExampleRpc("unloadwallet", "wallet_name") + ); + } + + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + if (!request.params[0].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet"); + } + } else { + wallet_name = request.params[0].get_str(); + } + + std::shared_ptr<CWallet> wallet = GetWallet(wallet_name); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + + // Release the "main" shared pointer and prevent further notifications. + // Note that any attempt to load the same wallet would fail until the wallet + // is destroyed (see CheckUniqueFileid). + 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. + + return NullUniValue; +} + +static UniValue resendwallettransactions(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -3067,9 +3259,11 @@ UniValue resendwallettransactions(const JSONRPCRequest& request) return result; } -UniValue listunspent(const JSONRPCRequest& request) +static UniValue listunspent(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -3126,8 +3320,6 @@ UniValue listunspent(const JSONRPCRequest& request) + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") ); - ObserveSafeMode(); - int nMinDepth = 1; if (!request.params[0].isNull()) { RPCTypeCheckArgument(request.params[0], UniValue::VNUM); @@ -3189,9 +3381,13 @@ UniValue listunspent(const JSONRPCRequest& request) UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; - LOCK2(cs_main, pwallet->cs_wallet); + { + LOCK2(cs_main, pwallet->cs_wallet); + pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + } + + LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); for (const COutput& out : vecOutputs) { CTxDestination address; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; @@ -3207,10 +3403,11 @@ UniValue listunspent(const JSONRPCRequest& request) if (fValidAddress) { entry.pushKV("address", EncodeDestination(address)); - if (pwallet->mapAddressBook.count(address)) { - entry.pushKV("label", pwallet->mapAddressBook[address].name); + auto i = pwallet->mapAddressBook.find(address); + if (i != pwallet->mapAddressBook.end()) { + entry.pushKV("label", i->second.name); if (IsDeprecatedRPCEnabled("accounts")) { - entry.pushKV("account", pwallet->mapAddressBook[address].name); + entry.pushKV("account", i->second.name); } } @@ -3235,94 +3432,25 @@ UniValue listunspent(const JSONRPCRequest& request) return results; } -UniValue fundrawtransaction(const JSONRPCRequest& request) +void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - 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" - " 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" - " \"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" - - "\nResult:\n" - "{\n" - " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" - " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" - " \"changepos\": n (numeric) The position of the added change output, or -1\n" - "}\n" - "\nExamples:\n" - "\nCreate a transaction with no inputs\n" - + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + - "\nAdd sufficient unsigned inputs to meet the output value\n" - + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + - "\nSign the transaction\n" - + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + - "\nSend the transaction\n" - + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") - ); - - ObserveSafeMode(); - RPCTypeCheck(request.params, {UniValue::VSTR}); - // 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(); CCoinControl coinControl; - int changePosition = -1; + change_position = -1; bool lockUnspents = false; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; - if (!request.params[1].isNull()) { - if (request.params[1].type() == UniValue::VBOOL) { + if (!options.isNull()) { + if (options.type() == UniValue::VBOOL) { // backward compatibility bool only fallback - coinControl.fAllowWatchOnly = request.params[1].get_bool(); + coinControl.fAllowWatchOnly = options.get_bool(); } else { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL}); - - UniValue options = request.params[1]; - + RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj(options, { {"changeAddress", UniValueType(UniValue::VSTR)}, @@ -3349,14 +3477,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) } if (options.exists("changePosition")) - changePosition = options["changePosition"].get_int(); + change_position = options["changePosition"].get_int(); if (options.exists("change_type")) { if (options.exists("changeAddress")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options"); } - coinControl.m_change_type = ParseOutputType(options["change_type"].get_str(), pwallet->m_default_change_type); - if (coinControl.m_change_type == OutputType::NONE) { + coinControl.m_change_type = pwallet->m_default_change_type; + if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str())); } } @@ -3396,18 +3524,10 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) } } - // parse hex string from parameter - CMutableTransaction tx; - bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); - bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); - if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - if (tx.vout.size() == 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); - if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > tx.vout.size())) + if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size())) throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { @@ -3421,24 +3541,107 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) setSubtractFeeFromOutputs.insert(pos); } - CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } +} + +static UniValue fundrawtransaction(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() > 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" + " 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" + " \"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" + + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); + + // parse hex string from parameter + CMutableTransaction tx; + bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); + bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + CAmount fee; + int change_position; + FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(tx)); - result.pushKV("changepos", changePosition); - result.pushKV("fee", ValueFromAmount(nFeeOut)); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); return result; } UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -3505,9 +3708,11 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]); } -UniValue bumpfee(const JSONRPCRequest& request) +static UniValue bumpfee(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; @@ -3656,7 +3861,9 @@ UniValue bumpfee(const JSONRPCRequest& request) UniValue generate(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; @@ -3701,7 +3908,9 @@ UniValue generate(const JSONRPCRequest& request) UniValue rescanblockchain(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -3884,7 +4093,7 @@ public: UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } }; -UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& dest) +static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& dest) { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); @@ -3906,7 +4115,9 @@ static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool v UniValue getaddressinfo(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -3937,13 +4148,14 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " ]\n" " \"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\", \"hdmasterkeyid\") and relation to the wallet (\"ismine\", \"iswatchonly\", \"account\").\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\", \"account\").\n" " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default account\n" " \"account\" : \"account\" (string) DEPRECATED. This field will be removed in V0.18. To see this deprecated field, start bitcoind with -deprecatedrpc=accounts. The account associated with the address, \"\" is the default account\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" - " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\n" + " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n" + " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) alias for hdseedid maintained for backwards compatibility. Will be removed in V0.18.\n" " \"labels\" (object) Array of labels associated with the address.\n" " [\n" " { (json object of label data)\n" @@ -4003,7 +4215,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request) ret.pushKV("timestamp", meta->nCreateTime); if (!meta->hdKeypath.empty()) { ret.pushKV("hdkeypath", meta->hdKeypath); - ret.pushKV("hdmasterkeyid", meta->hdMasterKeyID.GetHex()); + ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); + ret.pushKV("hdmasterkeyid", meta->hd_seed_id.GetHex()); } } @@ -4020,9 +4233,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request) return ret; } -UniValue getaddressesbylabel(const JSONRPCRequest& request) +static UniValue getaddressesbylabel(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -4050,7 +4265,7 @@ UniValue getaddressesbylabel(const JSONRPCRequest& request) // Find all addresses that have the given label UniValue ret(UniValue::VOBJ); - for (const std::pair<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { if (item.second.name == label) { ret.pushKV(EncodeDestination(item.first), AddressBookDataToJSON(item.second, false)); } @@ -4063,9 +4278,11 @@ UniValue getaddressesbylabel(const JSONRPCRequest& request) return ret; } -UniValue listlabels(const JSONRPCRequest& request) +static UniValue listlabels(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } @@ -4101,7 +4318,7 @@ UniValue listlabels(const JSONRPCRequest& request) // Add to a set to sort by label name, then insert into Univalue array std::set<std::string> label_set; - for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { if (purpose.empty() || entry.second.purpose == purpose) { label_set.insert(entry.second.name); } @@ -4115,6 +4332,403 @@ UniValue listlabels(const JSONRPCRequest& request) return ret; } +UniValue sethdseed(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() > 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" + " 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" + "\nExamples:\n" + + HelpExampleCli("sethdseed", "") + + HelpExampleCli("sethdseed", "false") + + HelpExampleCli("sethdseed", "true \"wifkey\"") + + HelpExampleRpc("sethdseed", "true, \"wifkey\"") + ); + } + + if (IsInitialBlockDownload()) { + 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); + + // Do not do anything to non-HD wallets + if (!pwallet->IsHDEnabled()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with -upgradewallet in order to upgrade a non-HD wallet to HD"); + } + + EnsureWalletIsUnlocked(pwallet); + + bool flush_key_pool = true; + if (!request.params[0].isNull()) { + flush_key_pool = request.params[0].get_bool(); + } + + CPubKey master_pub_key; + if (request.params[1].isNull()) { + master_pub_key = pwallet->GenerateNewSeed(); + } else { + CKey key = DecodeSecret(request.params[1].get_str()); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + } + + if (HaveKey(*pwallet, key)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)"); + } + + master_pub_key = pwallet->DeriveNewSeed(key); + } + + pwallet->SetHDSeed(master_pub_key); + if (flush_key_pool) pwallet->NewKeyPool(); + + return NullUniValue; +} + +bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath) +{ + std::stringstream ss(keypath_str); + std::string item; + bool first = true; + while (std::getline(ss, item, '/')) { + if (item.compare("m") == 0) { + if (first) { + first = false; + continue; + } + return false; + } + // Finds whether it is hardened + uint32_t path = 0; + size_t pos = item.find("'"); + if (pos != std::string::npos) { + // The hardened tick can only be in the last index of the string + if (pos != item.size() - 1) { + return false; + } + path |= 0x80000000; + item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick + } + + // Ensure this is only numbers + if (item.find_first_not_of( "0123456789" ) != std::string::npos) { + return false; + } + uint32_t number; + ParseUInt32(item, &number); + path |= number; + + keypath.push_back(path); + first = false; + } + return true; +} + +void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths) +{ + CPubKey vchPubKey; + if (!pwallet->GetPubKey(keyID, vchPubKey)) { + return; + } + CKeyMetadata meta; + auto it = pwallet->mapKeyMetadata.find(keyID); + if (it != pwallet->mapKeyMetadata.end()) { + meta = it->second; + } + std::vector<uint32_t> keypath; + if (!meta.hdKeypath.empty()) { + if (!ParseHDKeypath(meta.hdKeypath, keypath)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken"); + } + // Get the proper master key id + CKey key; + pwallet->GetKey(meta.hd_seed_id, key); + CExtKey masterKey; + masterKey.SetSeed(key.begin(), key.size()); + // Add to map + keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin())); + } else { // Single pubkeys get the master fingerprint of themselves + keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin())); + } + hd_keypaths.emplace(vchPubKey, keypath); +} + +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, 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]; + 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]; + input.non_witness_utxo = wtx.tx; + input.witness_utxo = utxo; + } + + // Get the Sighash type + if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); + } + + SignatureData sigdata; + if (sign) { + complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type); + } else { + complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type); + } + + // Drop the unnecessary UTXO + if (sigdata.witness) { + input.non_witness_utxo = nullptr; + } else { + input.witness_utxo.SetNull(); + } + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths); + } + } + } + + // 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); + PSBTOutput& psbt_out = psbtx.outputs.at(i); + + // Dummy tx so we can use ProduceSignature to get stuff out + CMutableTransaction dummy_tx; + dummy_tx.vin.push_back(CTxIn()); + dummy_tx.vout.push_back(CTxOut()); + + // Fill a SignatureData with output info + SignatureData sigdata; + psbt_out.FillSignatureData(sigdata); + + MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); + ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata); + psbt_out.FromSignatureData(sigdata); + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths); + } + } + } + return complete; +} + +UniValue walletprocesspsbt(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() > 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" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" + "4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("walletprocesspsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); + + // Unserialize the transaction + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + // 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); + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + result.push_back(Pair("complete", complete)); + + return result; +} + +UniValue walletcreatefundedpsbt(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() < 2 || request.params.size() > 6) + 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" + "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" + "5. 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" + " 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" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + "6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + "\nResult:\n" + "{\n" + " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + UniValue::VOBJ + }, true + ); + + CAmount fee; + int change_position; + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]); + + // 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); + + // Fill transaction with out data but don't sign + bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool(); + FillPSBT(pwallet, psbtx, &txConst, 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("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); + return result; +} + extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue importprivkey(const JSONRPCRequest& request); @@ -4131,6 +4745,8 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, + { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, + { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, @@ -4138,11 +4754,12 @@ static const CRPCCommand commands[] = { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, + { "wallet", "createwallet", &createwallet, {"wallet_name"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, { "wallet", "getaddressinfo", &getaddressinfo, {"address"} }, - { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, + { "wallet", "getbalance", &getbalance, {"account|dummy","minconf","include_watchonly"} }, { "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, @@ -4163,31 +4780,33 @@ static const CRPCCommand commands[] = { "wallet", "listtransactions", &listtransactions, {"account|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwallets", &listwallets, {} }, + { "wallet", "loadwallet", &loadwallet, {"filename"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, - { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "sendmany", &sendmany, {"fromaccount|dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, { "wallet", "settxfee", &settxfee, {"amount"} }, { "wallet", "signmessage", &signmessage, {"address","message"} }, { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, + { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} }, { "wallet", "walletlock", &walletlock, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} }, { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} }, { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} }, + { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} }, /** Account functions (deprecated) */ - { "wallet", "getaccountaddress", &getlabeladdress, {"account"} }, + { "wallet", "getaccountaddress", &getaccountaddress, {"account"} }, { "wallet", "getaccount", &getaccount, {"address"} }, { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, { "wallet", "getreceivedbyaccount", &getreceivedbylabel, {"account","minconf"} }, { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} }, { "wallet", "listreceivedbyaccount", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "setaccount", &setlabel, {"address","account"} }, + { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, /** Label functions (to replace non-balance account functions) */ - { "wallet", "getlabeladdress", &getlabeladdress, {"label","force"} }, { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "listlabels", &listlabels, {"purpose"} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 84f161abb5..64556b5824 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -11,6 +11,8 @@ class CRPCTable; class CWallet; class JSONRPCRequest; class UniValue; +struct PartiallySignedTransaction; +class CTransaction; void RegisterWalletRPCCommands(CRPCTable &t); @@ -20,7 +22,7 @@ void RegisterWalletRPCCommands(CRPCTable &t); * @param[in] request JSONRPCRequest that wishes to access a wallet * @return nullptr if no wallet should be used, or a pointer to the CWallet */ -CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request); +std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); std::string HelpRequiringPassphrase(CWallet *); void EnsureWalletIsUnlocked(CWallet *); @@ -28,4 +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); #endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index ac47d4448a..d90be33000 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -2,14 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "wallet/wallet.h" -#include "wallet/coinselection.h" -#include "wallet/coincontrol.h" -#include "amount.h" -#include "primitives/transaction.h" -#include "random.h" -#include "test/test_bitcoin.h" -#include "wallet/test/wallet_test_fixture.h" +#include <wallet/wallet.h> +#include <wallet/coinselection.h> +#include <wallet/coincontrol.h> +#include <amount.h> +#include <primitives/transaction.h> +#include <random.h> +#include <test/test_bitcoin.h> +#include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> #include <random> @@ -536,19 +536,9 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) std::exponential_distribution<double> distribution (100); FastRandomContext rand; - // Output stuff - CAmount out_value = 0; - CoinSet out_set; - CAmount target = 0; - bool bnb_used; - // Run this test 100 times for (int i = 0; i < 100; ++i) { - // Reset - out_value = 0; - target = 0; - out_set.clear(); empty_wallet(); // Make a wallet with 1000 exponentially distributed random inputs @@ -561,11 +551,14 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) CFeeRate rate(rand.randrange(300) + 100); // Generate a random target value between 1000 and wallet balance - target = rand.randrange(balance - 1000) + 1000; + CAmount target = rand.randrange(balance - 1000) + 1000; // Perform selection CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0); CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0); + CoinSet out_set; + CAmount out_value = 0; + bool bnb_used = false; BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_bnb, bnb_used) || testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_knapsack, bnb_used)); BOOST_CHECK_GE(out_value, target); diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp new file mode 100644 index 0000000000..a74ca85349 --- /dev/null +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -0,0 +1,74 @@ +// 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. + +#include <key_io.h> +#include <script/sign.h> +#include <utilstrencodings.h> +#include <wallet/rpcwallet.h> +#include <wallet/wallet.h> +#include <univalue.h> + +#include <boost/test/unit_test.hpp> +#include <test/test_bitcoin.h> +#include <wallet/test/wallet_test_fixture.h> + +BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) + +BOOST_AUTO_TEST_CASE(psbt_updater_test) +{ + // Create prevtxs and add to wallet + CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); + CTransactionRef prev_tx1; + s_prev_tx1 >> prev_tx1; + CWalletTx prev_wtx1(&m_wallet, prev_tx1); + m_wallet.mapWallet.emplace(prev_wtx1.GetHash(), std::move(prev_wtx1)); + + CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION); + CTransactionRef prev_tx2; + s_prev_tx2 >> prev_tx2; + CWalletTx prev_wtx2(&m_wallet, prev_tx2); + m_wallet.mapWallet.emplace(prev_wtx2.GetHash(), std::move(prev_wtx2)); + + // Add scripts + CScript rs1; + CDataStream s_rs1(ParseHex("475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"), SER_NETWORK, PROTOCOL_VERSION); + s_rs1 >> rs1; + m_wallet.AddCScript(rs1); + + CScript rs2; + CDataStream s_rs2(ParseHex("2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"), SER_NETWORK, PROTOCOL_VERSION); + s_rs2 >> rs2; + m_wallet.AddCScript(rs2); + + CScript ws1; + CDataStream s_ws1(ParseHex("47522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"), SER_NETWORK, PROTOCOL_VERSION); + s_ws1 >> ws1; + m_wallet.AddCScript(ws1); + + // Add hd seed + CKey key = DecodeSecret("5KSSJQ7UNfFGwVgpCZDSHm5rVNhMFcFtvWM3zQ8mW4qNDEN7LFd"); // Mainnet and uncompressed form of cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T + CPubKey master_pub_key = m_wallet.DeriveNewSeed(key); + m_wallet.SetHDSeed(master_pub_key); + m_wallet.NewKeyPool(); + + // Call FillPSBT + PartiallySignedTransaction psbtx; + 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); + + // Get the final tx + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + std::string final_hex = HexStr(ssTx.begin(), ssTx.end()); + BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 99c963a348..a946b565f1 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -73,8 +73,8 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); - AddWallet(&wallet); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; @@ -105,7 +105,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); - RemoveWallet(&wallet); + RemoveWallet(wallet); } } @@ -130,38 +130,40 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) LOCK(cs_main); + std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string(); + // Import key into wallet and call dumpwallet to create backup file. { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); - LOCK(wallet.cs_wallet); - wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; - wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + LOCK(wallet->cs_wallet); + wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; + wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); - request.params.push_back((pathTemp / "wallet.backup").string()); - AddWallet(&wallet); + request.params.push_back(backup_file); + AddWallet(wallet); ::dumpwallet(request); - RemoveWallet(&wallet); + RemoveWallet(wallet); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); - request.params.push_back((pathTemp / "wallet.backup").string()); - AddWallet(&wallet); + request.params.push_back(backup_file); + AddWallet(wallet); ::importwallet(request); - RemoveWallet(&wallet); + RemoveWallet(wallet); - LOCK(wallet.cs_wallet); - BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3U); + LOCK(wallet->cs_wallet); + BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); for (size_t i = 0; i < m_coinbase_txns.size(); ++i) { - bool found = wallet.GetWalletTx(m_coinbase_txns[i]->GetHash()); + bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetHash()); bool expected = i >= 100; BOOST_CHECK_EQUAL(found, expected); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ad3dd4cd2c..da585a0603 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -22,11 +22,12 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <script/script.h> -#include <scheduler.h> +#include <shutdown.h> #include <timedata.h> #include <txmempool.h> #include <utilmoneystr.h> #include <wallet/fees.h> +#include <wallet/walletutil.h> #include <algorithm> #include <assert.h> @@ -34,21 +35,24 @@ #include <boost/algorithm/string/replace.hpp> -static std::vector<CWallet*> vpwallets; +static CCriticalSection cs_wallets; +static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets); -bool AddWallet(CWallet* wallet) +bool AddWallet(const std::shared_ptr<CWallet>& wallet) { + LOCK(cs_wallets); assert(wallet); - std::vector<CWallet*>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); + std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i != vpwallets.end()) return false; vpwallets.push_back(wallet); return true; } -bool RemoveWallet(CWallet* wallet) +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet) { + LOCK(cs_wallets); assert(wallet); - std::vector<CWallet*>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); + std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i == vpwallets.end()) return false; vpwallets.erase(i); return true; @@ -56,22 +60,34 @@ bool RemoveWallet(CWallet* wallet) bool HasWallets() { + LOCK(cs_wallets); return !vpwallets.empty(); } -std::vector<CWallet*> GetWallets() +std::vector<std::shared_ptr<CWallet>> GetWallets() { + LOCK(cs_wallets); return vpwallets; } -CWallet* GetWallet(const std::string& name) +std::shared_ptr<CWallet> GetWallet(const std::string& name) { - for (CWallet* wallet : vpwallets) { + LOCK(cs_wallets); + for (const std::shared_ptr<CWallet>& wallet : vpwallets) { if (wallet->GetName() == name) return wallet; } return nullptr; } +// Custom deleter for shared_ptr<CWallet>. +static void ReleaseWallet(CWallet* wallet) +{ + LogPrintf("Releasing wallet %s\n", wallet->GetName()); + wallet->BlockUntilSyncedToCurrentChain(); + wallet->Flush(); + delete wallet; +} + const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); @@ -184,17 +200,17 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k - CKey key; //master key seed (256bit) + CKey seed; //seed (256bit) CExtKey masterKey; //hd master key CExtKey accountKey; //key at m/0' CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal) CExtKey childKey; //key at m/0'/0'/<n>' - // try to get the master key - if (!GetKey(hdChain.masterKeyID, key)) - throw std::runtime_error(std::string(__func__) + ": Master key not found"); + // try to get the seed + if (!GetKey(hdChain.seed_id, seed)) + throw std::runtime_error(std::string(__func__) + ": seed not found"); - masterKey.SetMaster(key.begin(), key.size()); + masterKey.SetSeed(seed.begin(), seed.size()); // derive m/0' // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) @@ -221,7 +237,7 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey } } while (HaveKey(childKey.key.GetPubKey().GetID())); secret = childKey.key; - metadata.hdMasterKeyID = hdChain.masterKeyID; + metadata.hd_seed_id = hdChain.seed_id; // update the chain model in the database if (!batch.WriteHDChain(hdChain)) throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); @@ -447,7 +463,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; } -void CWallet::SetBestChain(const CBlockLocator& loc) +void CWallet::ChainStateFlushed(const CBlockLocator& loc) { WalletBatch batch(*database); batch.WriteBestBlock(loc); @@ -537,12 +553,14 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran for (TxSpends::iterator it = range.first; it != range.second; ++it) { const CWalletTx* wtx = &mapWallet.at(it->second); if (wtx->nOrderPos < nMinOrderPos) { - nMinOrderPos = wtx->nOrderPos;; + nMinOrderPos = wtx->nOrderPos; copyFrom = wtx; } } - assert(copyFrom); + if (!copyFrom) { + return; + } // Now copy data from copyFrom to rest: for (TxSpends::iterator it = range.first; it != range.second; ++it) @@ -591,6 +609,8 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid) { mapTxSpends.insert(std::make_pair(outpoint, wtxid)); + setLockedCoins.erase(outpoint); + std::pair<TxSpends::iterator, TxSpends::iterator> range; range = mapTxSpends.equal_range(outpoint); SyncMetaData(range); @@ -680,9 +700,9 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Lock(); Unlock(strWalletPassphrase); - // if we are using HD, replace the HD master key (seed) with a new one + // if we are using HD, replace the HD seed with a new one if (IsHDEnabled()) { - if (!SetHDMasterKey(GenerateNewHDMasterKey())) { + if (!SetHDSeed(GenerateNewSeed())) { return false; } } @@ -914,11 +934,10 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) CWalletTx& wtx = (*ret.first).second; wtx.BindWallet(this); bool fInsertedNew = ret.second; - if (fInsertedNew) - { + if (fInsertedNew) { wtx.nTimeReceived = GetAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); - wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); wtx.nTimeSmart = ComputeTimeSmart(wtx); AddToSpends(hash); } @@ -989,9 +1008,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) bool CWallet::LoadToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); - CWalletTx& wtx = mapWallet.emplace(hash, wtxIn).first->second; + const auto& ins = mapWallet.emplace(hash, wtxIn); + CWalletTx& wtx = ins.first->second; wtx.BindWallet(this); - wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + if (/* insertion took place */ ins.second) { + wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); + } AddToSpends(hash); for (const CTxIn& txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.hash); @@ -1006,19 +1028,6 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn) return true; } -/** - * Add a transaction to the wallet, or update it. pIndex and posInBlock should - * be set when the transaction was known to be included in a block. When - * pIndex == nullptr, then wallet state is not updated in AddToWallet, but - * notifications happen and cached balances are marked dirty. - * - * If fUpdate is true, existing transactions will be updated. - * TODO: One exception to this is that the abandoned state is cleared under the - * assumption that any further notification of a transaction that was considered - * abandoned is an indication that it is not safe to be considered abandoned. - * Abandoned state should probably be more carefully tracked via different - * posInBlock signals or by checking mempool presence when necessary. - */ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; @@ -1085,6 +1094,16 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); } +void CWallet::MarkInputsDirty(const CTransactionRef& tx) +{ + for (const CTxIn& txin : tx->vin) { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + it->second.MarkDirty(); + } + } +} + bool CWallet::AbandonTransaction(const uint256& hashTx) { LOCK2(cs_main, cs_wallet); @@ -1124,7 +1143,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) batch.WriteTx(wtx); NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too - TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0)); + TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.hash == now) { if (!done.count(iter->second)) { todo.insert(iter->second); @@ -1133,13 +1152,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed - for (const CTxIn& txin : wtx.tx->vin) - { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(wtx.tx); } } @@ -1195,31 +1208,19 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed - for (const CTxIn& txin : wtx.tx->vin) { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(wtx.tx); } } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock) { - const CTransaction& tx = *ptx; - - if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) { + if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be // recomputed, also: - for (const CTxIn& txin : tx.vin) { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(ptx); } void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { @@ -1285,7 +1286,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { LOCK(cs_main); const CBlockIndex* initialChainTip = chainActive.Tip(); - if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { + if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { return; } } @@ -1441,37 +1442,41 @@ CAmount CWallet::GetChange(const CTransaction& tx) const return nChange; } -CPubKey CWallet::GenerateNewHDMasterKey() +CPubKey CWallet::GenerateNewSeed() { CKey key; key.MakeNewKey(true); + return DeriveNewSeed(key); +} +CPubKey CWallet::DeriveNewSeed(const CKey& key) +{ int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); - // calculate the pubkey - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); + // calculate the seed + CPubKey seed = key.GetPubKey(); + assert(key.VerifyPubKey(seed)); - // set the hd keypath to "m" -> Master, refers the masterkeyid to itself - metadata.hdKeypath = "m"; - metadata.hdMasterKeyID = pubkey.GetID(); + // set the hd keypath to "s" -> Seed, refers the seed to itself + metadata.hdKeypath = "s"; + metadata.hd_seed_id = seed.GetID(); { LOCK(cs_wallet); // mem store the metadata - mapKeyMetadata[pubkey.GetID()] = metadata; + mapKeyMetadata[seed.GetID()] = metadata; // write the key&metadata to the database - if (!AddKeyPubKey(key, pubkey)) + if (!AddKeyPubKey(key, seed)) throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); } - return pubkey; + return seed; } -bool CWallet::SetHDMasterKey(const CPubKey& pubkey) +bool CWallet::SetHDSeed(const CPubKey& seed) { LOCK(cs_wallet); // store the keyid (hash160) together with @@ -1479,7 +1484,7 @@ bool CWallet::SetHDMasterKey(const CPubKey& pubkey) // as a hdchain object CHDChain newHdChain; newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; - newHdChain.masterKeyID = pubkey.GetID(); + newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); return true; @@ -1497,7 +1502,7 @@ bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) bool CWallet::IsHDEnabled() const { - return !hdChain.masterKeyID.IsNull(); + return !hdChain.seed_id.IsNull(); } int64_t CWalletTx::GetTxTime() const @@ -1506,45 +1511,6 @@ int64_t CWalletTx::GetTxTime() const return n ? n : nTimeReceived; } -int CWalletTx::GetRequestCount() const -{ - // Returns -1 if it wasn't being tracked - int nRequests = -1; - { - LOCK(pwallet->cs_wallet); - if (IsCoinBase()) - { - // Generated block - if (!hashUnset()) - { - std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); - if (mi != pwallet->mapRequestCount.end()) - nRequests = (*mi).second; - } - } - else - { - // Did anyone request this transaction? - std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash()); - if (mi != pwallet->mapRequestCount.end()) - { - nRequests = (*mi).second; - - // How about the block it's in? - if (nRequests == 0 && !hashUnset()) - { - std::map<uint256, int>::const_iterator _mi = pwallet->mapRequestCount.find(hashBlock); - if (_mi != pwallet->mapRequestCount.end()) - nRequests = (*_mi).second; - else - nRequests = 1; // If it's in someone else's block it must have got out - } - } - } - } - return nRequests; -} - // Helper for producing a max-sized low-S signature (eg 72 bytes) bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout) const { @@ -1738,23 +1704,27 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock fAbortRescan = false; ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup CBlockIndex* tip = nullptr; - double dProgressStart; - double dProgressTip; + double progress_begin; + double progress_end; { LOCK(cs_main); - tip = chainActive.Tip(); - dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); - dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); + if (pindexStop == nullptr) { + tip = chainActive.Tip(); + progress_end = GuessVerificationProgress(chainParams.TxData(), tip); + } else { + progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop); + } } - double gvp = dProgressStart; - while (pindex && !fAbortRescan) + double progress_current = progress_begin; + while (pindex && !fAbortRescan && !ShutdownRequested()) { - if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { - ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); + if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); - LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, gvp); + LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); } CBlock block; @@ -1767,7 +1737,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } } else { ret = pindex; @@ -1778,16 +1748,18 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock { LOCK(cs_main); pindex = chainActive.Next(pindex); - gvp = GuessVerificationProgress(chainParams.TxData(), pindex); - if (tip != chainActive.Tip()) { + progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); + if (pindexStop == nullptr && tip != chainActive.Tip()) { tip = chainActive.Tip(); // in case the tip has changed, update progress max - dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + progress_end = GuessVerificationProgress(chainParams.TxData(), tip); } } } if (pindex && fAbortRescan) { - LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, gvp); + LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); + } else if (pindex && ShutdownRequested()) { + LogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); } ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI } @@ -1936,7 +1908,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const +CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; @@ -1945,8 +1917,20 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (IsCoinBase() && GetBlocksToMaturity() > 0) return 0; - if (fUseCache && fAvailableCreditCached) - return nAvailableCreditCached; + CAmount* cache = nullptr; + bool* cache_used = nullptr; + + if (filter == ISMINE_SPENDABLE) { + cache = &nAvailableCreditCached; + cache_used = &fAvailableCreditCached; + } else if (filter == ISMINE_WATCH_ONLY) { + cache = &nAvailableWatchCreditCached; + cache_used = &fAvailableWatchCreditCached; + } + + if (fUseCache && cache_used && *cache_used) { + return *cache; + } CAmount nCredit = 0; uint256 hashTx = GetHash(); @@ -1955,14 +1939,16 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (!pwallet->IsSpent(hashTx, i)) { const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + " : value out of range"); } } - nAvailableCreditCached = nCredit; - fAvailableCreditCached = true; + if (cache) { + *cache = nCredit; + *cache_used = true; + } return nCredit; } @@ -1980,35 +1966,6 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const -{ - if (pwallet == nullptr) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fAvailableWatchCreditCached) - return nAvailableWatchCreditCached; - - CAmount nCredit = 0; - for (unsigned int i = 0; i < tx->vout.size(); i++) - { - if (!pwallet->IsSpent(GetHash(), i)) - { - const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); - if (!MoneyRange(nCredit)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - } - - nAvailableWatchCreditCached = nCredit; - fAvailableWatchCreditCached = true; - return nCredit; -} - CAmount CWalletTx::GetChange() const { if (fChangeCached) @@ -2056,8 +2013,8 @@ bool CWalletTx::IsTrusted() const bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const { - CMutableTransaction tx1 = *this->tx; - CMutableTransaction tx2 = *_tx.tx; + CMutableTransaction tx1 {*this->tx}; + CMutableTransaction tx2 {*_tx.tx}; for (auto& txin : tx1.vin) txin.scriptSig = CScript(); for (auto& txin : tx2.vin) txin.scriptSig = CScript(); return CTransaction(tx1) == CTransaction(tx2); @@ -2122,7 +2079,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman */ -CAmount CWallet::GetBalance() const +CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const { CAmount nTotal = 0; { @@ -2130,8 +2087,9 @@ CAmount CWallet::GetBalance() const for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted()) - nTotal += pcoin->GetAvailableCredit(); + if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) { + nTotal += pcoin->GetAvailableCredit(true, filter); + } } } @@ -2167,22 +2125,6 @@ CAmount CWallet::GetImmatureBalance() const return nTotal; } -CAmount CWallet::GetWatchOnlyBalance() const -{ - CAmount nTotal = 0; - { - LOCK2(cs_main, cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted()) - nTotal += pcoin->GetAvailableWatchOnlyCredit(); - } - } - - return nTotal; -} - CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { CAmount nTotal = 0; @@ -2192,7 +2134,7 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { const CWalletTx* pcoin = &entry.second; if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableWatchOnlyCredit(); + nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY); } } return nTotal; @@ -2357,10 +2299,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const continue; } - bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); - bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; + bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey); + bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx)); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -2450,8 +2392,11 @@ bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibi if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs)) return false; - if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), eligibility_filter.max_ancestors)) + size_t ancestors, descendants; + mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + if (ancestors > eligibility_filter.max_ancestors || descendants > eligibility_filter.max_descendants) { return false; + } return true; } @@ -2563,16 +2508,17 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm ++it; } - size_t nMaxChainLength = std::min(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); + size_t max_ancestors = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)); + size_t max_descendants = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, nMaxChainLength/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset @@ -2589,9 +2535,8 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) AssertLockHeld(cs_wallet); // mapWallet // sign the new tx - CTransaction txNewConst(tx); int nIn = 0; - for (const auto& input : tx.vin) { + for (auto& input : tx.vin) { std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash); if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { return false; @@ -2599,10 +2544,10 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; SignatureData sigdata; - if (!ProduceSignature(*this, TransactionSignatureCreator(&txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { + if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { return false; } - UpdateTransaction(tx, nIn, sigdata); + UpdateInput(input, sigdata); nIn++; } return true; @@ -2665,7 +2610,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend) { // If -changetype is specified, always use that change type. - if (change_type != OutputType::NONE) { + if (change_type != OutputType::CHANGE_AUTO) { return change_type; } @@ -3021,19 +2966,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac if (sign) { - CTransaction txNewConst(txNew); int nIn = 0; for (const auto& coin : selected_coins) { const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - if (!ProduceSignature(*this, TransactionSignatureCreator(&txNewConst, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) + if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed"); return false; } else { - UpdateTransaction(txNew, nIn, sigdata); + UpdateInput(txNew.vin.at(nIn), sigdata); } nIn++; @@ -3044,7 +2988,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac tx = MakeTransactionRef(std::move(txNew)); // Limit size - if (GetTransactionWeight(*tx) >= MAX_STANDARD_TX_WEIGHT) + if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) { strFailReason = _("Transaction too large"); return false; @@ -3111,9 +3055,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } } - // Track how many getdata requests our transaction gets - mapRequestCount[wtxNew.GetHash()] = 0; - // Get the inserted-CWalletTx from mapWallet so that the // fInMempool flag is cached properly CWalletTx& wtx = mapWallet.at(wtxNew.GetHash()); @@ -3176,14 +3117,15 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) } } - // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + { + LOCK(cs_KeyStore); + // This wallet is in its first run if all of these are empty + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + } if (nLoadWalletRet != DBErrors::LOAD_OK) return nLoadWalletRet; - uiInterface.LoadWallet(this); - return DBErrors::LOAD_OK; } @@ -3191,8 +3133,11 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { AssertLockHeld(cs_wallet); // mapWallet DBErrors nZapSelectTxRet = WalletBatch(*database,"cr+").ZapSelectTx(vHashIn, vHashOut); - for (uint256 hash : vHashOut) - mapWallet.erase(hash); + for (uint256 hash : vHashOut) { + const auto& it = mapWallet.find(hash); + wtxOrdered.erase(it->second.m_it_wtxOrdered); + mapWallet.erase(it); + } if (nZapSelectTxRet == DBErrors::NEED_REWRITE) { @@ -3265,7 +3210,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address) // Delete destdata tuples associated with address std::string strAddress = EncodeDestination(address); - for (const std::pair<std::string, std::string> &item : mapAddressBook[address].destdata) + for (const std::pair<const std::string, std::string> &item : mapAddressBook[address].destdata) { WalletBatch(*database).EraseDestData(strAddress, item.first); } @@ -3313,6 +3258,11 @@ bool CWallet::NewKeyPool() } setExternalKeyPool.clear(); + for (int64_t nIndex : set_pre_split_keypool) { + batch.ErasePool(nIndex); + } + set_pre_split_keypool.clear(); + m_pool_key_to_index.clear(); if (!TopUpKeyPool()) { @@ -3326,13 +3276,15 @@ bool CWallet::NewKeyPool() size_t CWallet::KeypoolCountExternalKeys() { AssertLockHeld(cs_wallet); // setExternalKeyPool - return setExternalKeyPool.size(); + return setExternalKeyPool.size() + set_pre_split_keypool.size(); } void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { AssertLockHeld(cs_wallet); - if (keypool.fInternal) { + if (keypool.m_pre_split) { + set_pre_split_keypool.insert(nIndex); + } else if (keypool.fInternal) { setInternalKeyPool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); @@ -3397,13 +3349,13 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) m_pool_key_to_index[pubkey.GetID()] = index; } if (missingInternal + missingExternal > 0) { - LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); + LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); } } return true; } -void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) +bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); @@ -3414,11 +3366,13 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe TopUpKeyPool(); bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; - std::set<int64_t>& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool; + bool use_split_keypool = set_pre_split_keypool.empty(); + std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; // Get the oldest key - if(setKeyPool.empty()) - return; + if (setKeyPool.empty()) { + return false; + } WalletBatch batch(*database); @@ -3431,14 +3385,18 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe if (!HaveKey(keypool.vchPubKey.GetID())) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } - if (keypool.fInternal != fReturningInternal) { + // If the key was pre-split keypool, we don't care about what type it is + if (use_split_keypool && keypool.fInternal != fReturningInternal) { throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); } + if (!keypool.vchPubKey.IsValid()) { + throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); + } - assert(keypool.vchPubKey.IsValid()); m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); LogPrintf("keypool reserve %d\n", nIndex); } + return true; } void CWallet::KeepKey(int64_t nIndex) @@ -3456,6 +3414,8 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) LOCK(cs_wallet); if (fInternal) { setInternalKeyPool.insert(nIndex); + } else if (!set_pre_split_keypool.empty()) { + set_pre_split_keypool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } @@ -3469,10 +3429,8 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) CKeyPool keypool; { LOCK(cs_wallet); - int64_t nIndex = 0; - ReserveKeyFromKeyPool(nIndex, keypool, internal); - if (nIndex == -1) - { + int64_t nIndex; + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) { if (IsLocked()) return false; WalletBatch batch(*database); result = GenerateNewKey(batch, internal); @@ -3508,6 +3466,9 @@ int64_t CWallet::GetOldestKeyPoolTime() int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); + if (!set_pre_split_keypool.empty()) { + oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); + } } return oldestKey; @@ -3650,7 +3611,7 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co { LOCK(cs_wallet); std::set<CTxDestination> result; - for (const std::pair<CTxDestination, CAddressBookData>& item : mapAddressBook) + for (const std::pair<const CTxDestination, CAddressBookData>& item : mapAddressBook) { const CTxDestination& address = item.first; const std::string& strName = item.second.name; @@ -3671,12 +3632,10 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) if (nIndex == -1) { CKeyPool keypool; - pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal); - if (nIndex != -1) - vchPubKey = keypool.vchPubKey; - else { + if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { return false; } + vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); @@ -3705,8 +3664,8 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) { AssertLockHeld(cs_wallet); bool internal = setInternalKeyPool.count(keypool_id); - if (!internal) assert(setExternalKeyPool.count(keypool_id)); - std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool; + if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); + std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); auto it = setKeyPool->begin(); WalletBatch batch(*database); @@ -3942,7 +3901,71 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const return values; } -CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path) +void CWallet::MarkPreSplitKeys() +{ + WalletBatch batch(*database); + for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { + int64_t index = *it; + CKeyPool keypool; + if (!batch.ReadPool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); + } + keypool.m_pre_split = true; + if (!batch.WritePool(index, keypool)) { + throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); + } + set_pre_split_keypool.insert(index); + it = setExternalKeyPool.erase(it); + } +} + +bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string) +{ + // Do some checking on wallet path. It should be either a: + // + // 1. Path where a directory can be created. + // 2. Path to an existing directory. + // 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()); + 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))) { + 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()); + 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 (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) { + return false; + } + + if (salvage_wallet) { + // Recover readable keypairs: + CWallet dummyWallet("dummy", WalletDatabase::CreateDummy()); + std::string backup_filename; + if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { + return false; + } + } + + return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); +} + +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path) { const std::string& walletFile = name; @@ -3964,7 +3987,9 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& int64_t nStart = GetTimeMillis(); bool fFirstRun = true; - CWallet *walletInstance = new CWallet(name, WalletDatabase::Create(path)); + // 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); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -3993,13 +4018,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& } } + int prev_version = walletInstance->nWalletVersion; if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) { int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); if (nMaxVersion == 0) // the -upgradewallet without argument case { LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); - nMaxVersion = CLIENT_VERSION; + nMaxVersion = FEATURE_LATEST; walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately } else @@ -4012,6 +4038,49 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& walletInstance->SetMaxVersion(nMaxVersion); } + // Upgrade to HD if explicit upgrade + if (gArgs.GetBoolArg("-upgradewallet", false)) { + LOCK(walletInstance->cs_wallet); + + // Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT + int max_version = walletInstance->nWalletVersion; + if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >=FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { + InitError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.")); + return nullptr; + } + + bool hd_upgrade = false; + bool split_upgrade = false; + if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { + LogPrintf("Upgrading wallet to HD\n"); + walletInstance->SetMinVersion(FEATURE_HD); + + // generate a new master key + CPubKey masterPubKey = walletInstance->GenerateNewSeed(); + if (!walletInstance->SetHDSeed(masterPubKey)) { + throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); + } + hd_upgrade = true; + } + // Upgrade to HD chain split if necessary + if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { + LogPrintf("Upgrading wallet to use HD chain split\n"); + walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); + split_upgrade = FEATURE_HD_SPLIT > prev_version; + } + // Mark all keys currently in the keypool as pre-split + if (split_upgrade) { + walletInstance->MarkPreSplitKeys(); + } + // Regenerate the keypool if upgraded to HD + if (hd_upgrade) { + if (!walletInstance->TopUpKeyPool()) { + InitError(_("Unable to generate keys") += "\n"); + return nullptr; + } + } + } + if (fFirstRun) { // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key @@ -4019,12 +4088,12 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& InitError(strprintf(_("Error creating %s: You can't create non-HD wallets with this version."), walletFile)); return nullptr; } - walletInstance->SetMinVersion(FEATURE_NO_DEFAULT_KEY); + walletInstance->SetMinVersion(FEATURE_LATEST); - // generate a new master key - CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey(); - if (!walletInstance->SetHDMasterKey(masterPubKey)) - throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); + // generate a new seed + CPubKey seed = walletInstance->GenerateNewSeed(); + if (!walletInstance->SetHDSeed(seed)) + throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed"); // Top up the keypool if (!walletInstance->TopUpKeyPool()) { @@ -4032,7 +4101,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& return nullptr; } - walletInstance->SetBestChain(chainActive.GetLocator()); + walletInstance->ChainStateFlushed(chainActive.GetLocator()); } else if (gArgs.IsArgSet("-usehd")) { bool useHD = gArgs.GetBoolArg("-usehd", true); if (walletInstance->IsHDEnabled() && !useHD) { @@ -4045,16 +4114,12 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& } } - walletInstance->m_default_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""), DEFAULT_ADDRESS_TYPE); - if (walletInstance->m_default_address_type == OutputType::NONE) { + if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) { InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); return nullptr; } - // If changetype is set in config file or parameter, check that it's valid. - // Default to OutputType::NONE if not set. - walletInstance->m_default_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OutputType::NONE); - if (walletInstance->m_default_change_type == OutputType::NONE && !gArgs.GetArg("-changetype", "").empty()) { + if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) { InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); return nullptr; } @@ -4136,7 +4201,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& } walletInstance->m_last_block_processed = chainActive.Tip(); - RegisterValidationInterface(walletInstance); if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { @@ -4166,7 +4230,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& nStart = GetTimeMillis(); { - WalletRescanReserver reserver(walletInstance); + WalletRescanReserver reserver(walletInstance.get()); if (!reserver.reserve()) { InitError(_("Failed to rescan the wallet during initialization")); return nullptr; @@ -4174,7 +4238,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); - walletInstance->SetBestChain(chainActive.GetLocator()); + walletInstance->ChainStateFlushed(chainActive.GetLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 @@ -4202,6 +4266,12 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& } } } + + uiInterface.LoadWallet(walletInstance); + + // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main. + RegisterValidationInterface(walletInstance.get()); + walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); { @@ -4214,18 +4284,11 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& return walletInstance; } -std::atomic<bool> CWallet::fFlushScheduled(false); - -void CWallet::postInitProcess(CScheduler& scheduler) +void CWallet::postInitProcess() { // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded ReacceptWalletTransactions(); - - // Run a thread to flush wallet periodically - if (!CWallet::fFlushScheduled.exchange(true)) { - scheduler.scheduleEvery(MaybeCompactWalletDB, 500); - } } bool CWallet::BackupWallet(const std::string& strDest) @@ -4237,6 +4300,7 @@ CKeyPool::CKeyPool() { nTime = GetTime(); fInternal = false; + m_pre_split = false; } CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) @@ -4244,6 +4308,7 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) nTime = GetTime(); vchPubKey = vchPubKeyIn; fInternal = internalIn; + m_pre_split = false; } CWalletKey::CWalletKey(int64_t nExpires) @@ -4261,7 +4326,7 @@ void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +int CMerkleTx::GetDepthInMainChain() const { if (hashUnset()) return 0; @@ -4273,7 +4338,6 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const if (!pindex || !chainActive.Contains(pindex)) return 0; - pindexRet = pindex; return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } @@ -4298,35 +4362,6 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& return ret; } -static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; -static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; -static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; - -OutputType ParseOutputType(const std::string& type, OutputType default_type) -{ - if (type.empty()) { - return default_type; - } else if (type == OUTPUT_TYPE_STRING_LEGACY) { - return OutputType::LEGACY; - } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { - return OutputType::P2SH_SEGWIT; - } else if (type == OUTPUT_TYPE_STRING_BECH32) { - return OutputType::BECH32; - } else { - return OutputType::NONE; - } -} - -const std::string& FormatOutputType(OutputType type) -{ - switch (type) { - case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; - case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; - case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; - default: assert(false); - } -} - void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) { if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { @@ -4344,59 +4379,3 @@ void CWallet::LearnAllRelatedScripts(const CPubKey& key) LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); } -CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) -{ - switch (type) { - case OutputType::LEGACY: return key.GetID(); - case OutputType::P2SH_SEGWIT: - case OutputType::BECH32: { - if (!key.IsCompressed()) return key.GetID(); - CTxDestination witdest = WitnessV0KeyHash(key.GetID()); - CScript witprog = GetScriptForDestination(witdest); - if (type == OutputType::P2SH_SEGWIT) { - return CScriptID(witprog); - } else { - return witdest; - } - } - default: assert(false); - } -} - -std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) -{ - CKeyID keyid = key.GetID(); - if (key.IsCompressed()) { - CTxDestination segwit = WitnessV0KeyHash(keyid); - CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); - return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)}; - } else { - return std::vector<CTxDestination>{std::move(keyid)}; - } -} - -CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type) -{ - // Note that scripts over 520 bytes are not yet supported. - switch (type) { - case OutputType::LEGACY: - return CScriptID(script); - case OutputType::P2SH_SEGWIT: - case OutputType::BECH32: { - WitnessV0ScriptHash hash; - CSHA256().Write(script.data(), script.size()).Finalize(hash.begin()); - CTxDestination witdest = hash; - CScript witprog = GetScriptForDestination(witdest); - // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) - if (!IsSolvable(*this, witprog)) return CScriptID(script); - // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. - AddCScript(witprog); - if (type == OutputType::BECH32) { - return witdest; - } else { - return CScriptID(witprog); - } - } - default: assert(false); - } -} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 780c82ac36..2e53ca0c55 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -7,6 +7,7 @@ #define BITCOIN_WALLET_WALLET_H #include <amount.h> +#include <outputtype.h> #include <policy/feerate.h> #include <streams.h> #include <tinyformat.h> @@ -32,11 +33,11 @@ #include <utility> #include <vector> -bool AddWallet(CWallet* wallet); -bool RemoveWallet(CWallet* wallet); +bool AddWallet(const std::shared_ptr<CWallet>& wallet); +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); -std::vector<CWallet*> GetWallets(); -CWallet* GetWallet(const std::string& name); +std::vector<std::shared_ptr<CWallet>> GetWallets(); +std::shared_ptr<CWallet> GetWallet(const std::string& name); //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; @@ -68,7 +69,6 @@ class CCoinControl; class COutput; class CReserveKey; class CScript; -class CScheduler; class CTxMemPool; class CBlockPolicyEstimator; class CWalletTx; @@ -89,19 +89,16 @@ enum WalletFeature FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written - FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version -}; + FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool -enum class OutputType { - NONE, - LEGACY, - P2SH_SEGWIT, - BECH32, + FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL }; //! Default for -addresstype constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT}; +//! Default for -changetype +constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO}; /** A key pool entry */ class CKeyPool @@ -110,6 +107,7 @@ public: int64_t nTime; CPubKey vchPubKey; bool fInternal; // for change outputs + bool m_pre_split; // For keys generated before keypool split upgrade CKeyPool(); CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn); @@ -132,9 +130,18 @@ public: (this will be the case for any wallet before the HD chain split version) */ fInternal = false; } + try { + READWRITE(m_pre_split); + } + catch (std::ios_base::failure&) { + /* flag as postsplit address if we can't read the m_pre_split boolean + (this will be the case for any wallet that upgrades to HD chain split)*/ + m_pre_split = false; + } } else { READWRITE(fInternal); + READWRITE(m_pre_split); } } }; @@ -247,9 +254,8 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain(const CBlockIndex* &pindexRet) const; - int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } - bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } + int GetDepthInMainChain() const; + bool IsInMainChain() const { return GetDepthInMainChain() > 0; } int GetBlocksToMaturity() const; bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } @@ -319,6 +325,7 @@ public: char fFromMe; std::string strFromAccount; int64_t nOrderPos; //!< position in ordered transaction list + std::multimap<int64_t, std::pair<CWalletTx*, CAccountingEntry*>>::const_iterator m_it_wtxOrdered; // memory only mutable bool fDebitCached; @@ -439,9 +446,8 @@ public: CAmount GetDebit(const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const; CAmount GetImmatureCredit(bool fUseCache=true) const; - CAmount GetAvailableCredit(bool fUseCache=true) const; + CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const; CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const; - CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const; CAmount GetChange() const; // Get the marginal bytes if spending the specified output from this transaction @@ -465,7 +471,6 @@ public: bool IsTrusted() const; int64_t GetTxTime() const; - int GetRequestCount() const; // RelayWalletTransaction may only be called if fBroadcastTransactions! bool RelayWalletTransaction(CConnman* connman); @@ -642,8 +647,10 @@ struct CoinEligibilityFilter const int conf_mine; const int conf_theirs; const uint64_t max_ancestors; + const uint64_t max_descendants; - CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors) {} + CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {} + CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {} }; class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime @@ -654,7 +661,6 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions class CWallet final : public CCryptoKeyStore, public CValidationInterface { private: - static std::atomic<bool> fFlushScheduled; std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver std::mutex mutexScanning; @@ -682,23 +688,42 @@ private: void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); void AddToSpends(const uint256& wtxid); + /** + * Add a transaction to the wallet, or update it. pIndex and posInBlock should + * be set when the transaction was known to be included in a block. When + * pIndex == nullptr, then wallet state is not updated in AddToWallet, but + * notifications happen and cached balances are marked dirty. + * + * If fUpdate is true, existing transactions will be updated. + * TODO: One exception to this is that the abandoned state is cleared under the + * assumption that any further notification of a transaction that was considered + * abandoned is an indication that it is not safe to be considered abandoned. + * Abandoned state should probably be more carefully tracked via different + * posInBlock signals or by checking mempool presence when necessary. + */ + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ 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 SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); - /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected. + /* 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. */ - void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0); + void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* the HD chain data model (external chain counters) */ CHDChain hdChain; /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal = false); + 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> set_pre_split_keypool; int64_t m_max_keypool_index = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; @@ -713,7 +738,7 @@ private: * of the other AddWatchOnly which accepts a timestamp and sets * nTimeFirstKey more intelligently for more efficient rescans. */ - bool AddWatchOnly(const CScript& dest) override; + bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Wallet filename from wallet=<path> command line or config option. @@ -764,7 +789,8 @@ public: */ const std::string& GetName() const { return m_name; } - void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool); + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void MarkPreSplitKeys(); // Map from Key ID to key metadata. std::map<CKeyID, CKeyMetadata> mapKeyMetadata; @@ -796,7 +822,6 @@ public: int64_t nOrderPosNext = 0; uint64_t nAccountingEntryNumber = 0; - std::map<uint256, int> mapRequestCount; std::map<CTxDestination, CAddressBookData> mapAddressBook; @@ -805,12 +830,12 @@ public: const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature - bool CanSupportFeature(enum WalletFeature wf) const { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } + bool CanSupportFeature(enum WalletFeature wf) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * 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; + 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_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. @@ -833,11 +858,11 @@ public: bool IsSpent(const uint256& hash, unsigned int n) const; - bool IsLockedCoin(uint256 hash, unsigned int n) const; - void LockCoin(const COutPoint& output); - void UnlockCoin(const COutPoint& output); - void UnlockAllCoins(); - void ListLockedCoins(std::vector<COutPoint>& vOutpts) const; + bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void ListLockedCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* * Rescan abort properties @@ -850,18 +875,18 @@ public: * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false); + CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, and saves it to disk. - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey); + bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) - bool LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata); - bool LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata); + bool LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } - void UpdateTimeFirstKey(int64_t nCreateTime); + bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } + void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override; @@ -882,8 +907,8 @@ public: std::vector<std::string> GetDestValues(const std::string& prefix) const; //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnly(const CScript& dest, int64_t nCreateTime); - bool RemoveWatchOnly(const CScript &dest) override; + bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool RemoveWatchOnly(const CScript &dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript &dest); @@ -894,16 +919,16 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); - void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const; + void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; /** * Increment the next transaction order id * @return next transaction order id */ - int64_t IncOrderPosNext(WalletBatch *batch = nullptr); + int64_t IncOrderPosNext(WalletBatch *batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); DBErrors ReorderTransactions(); - bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = ""); + bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "") EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew = false); void MarkDirty(); @@ -912,7 +937,6 @@ public: 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; - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; @@ -920,10 +944,9 @@ public: void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); - CAmount GetBalance() const; + CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; CAmount GetUnconfirmedBalance() const; CAmount GetImmatureBalance() const; - CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; @@ -936,7 +959,7 @@ public: * calling CreateTransaction(); */ bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); - bool SignTransaction(CMutableTransaction& tx); + bool SignTransaction(CMutableTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Create a new transaction paying the recipients with a set of coins @@ -973,12 +996,27 @@ public: CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE}; CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE}; OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE}; - OutputType m_default_change_type{OutputType::NONE}; // Default to OutputType::NONE if not set by -changetype + OutputType m_default_change_type{DEFAULT_CHANGE_TYPE}; bool NewKeyPool(); - size_t KeypoolCountExternalKeys(); + size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); - void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); + + /** + * Reserves a key from the keypool and sets nIndex to its index + * + * @param[out] nIndex the index of the key in keypool + * @param[out] keypool the keypool the key was drawn from, which could be the + * the pre-split pool if present, or the internal or external pool + * @param fRequestedInternal true if the caller would like the key drawn + * from the internal keypool, false if external is preferred + * + * @return true if succeeded, false if failed due to empty keypool + * @throws std::runtime_error if keypool read failed, key was invalid, + * was not found in the wallet, or was misclassified in the internal + * or external keypool + */ + bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); bool GetKeyFromPool(CPubKey &key, bool internal = false); @@ -986,10 +1024,10 @@ public: /** * Marks all keys in the keypool up to and including reserve_key as used. */ - void MarkReserveKeysAsUsed(int64_t keypool_id); + void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } - std::set< std::set<CTxDestination> > GetAddressGroupings(); + std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::map<CTxDestination, CAmount> GetAddressBalances(); std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; @@ -1013,11 +1051,11 @@ public: bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const; CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; CAmount GetChange(const CTransaction& tx) const; - void SetBestChain(const CBlockLocator& loc) override; + void ChainStateFlushed(const CBlockLocator& loc) override; DBErrors LoadWallet(bool& fFirstRunRet); DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); - DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut); + DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); @@ -1025,19 +1063,9 @@ public: const std::string& GetLabelName(const CScript& scriptPubKey) const; - void Inventory(const uint256 &hash) override - { - { - LOCK(cs_wallet); - std::map<uint256, int>::iterator mi = mapRequestCount.find(hash); - if (mi != mapRequestCount.end()) - (*mi).second++; - } - } - void GetScriptForMining(std::shared_ptr<CReserveScript> &script); - unsigned int GetKeyPoolSize() + unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool return setInternalKeyPool.size() + setExternalKeyPool.size(); @@ -1056,11 +1084,14 @@ public: std::set<uint256> GetConflicts(const uint256& txid) const; //! Check if a given transaction has any of its outputs spent by another transaction in the wallet - bool HasWalletSpend(const uint256& txid) const; + bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Flush wallet (bitdb flush) void Flush(bool shutdown=false); + /** Wallet is about to be unloaded */ + boost::signals2::signal<void ()> NotifyUnload; + /** * Address book entry changed. * @note called with lock cs_wallet held. @@ -1097,14 +1128,17 @@ public: /** 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); + /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path); + static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path); /** * Wallet post-init setup * Gives the wallet a chance to register repetitive tasks and complete post-init tasks */ - void postInitProcess(CScheduler& scheduler); + void postInitProcess(); bool BackupWallet(const std::string& strDest); @@ -1115,14 +1149,17 @@ public: /* Returns true if HD is enabled */ bool IsHDEnabled() const; - /* Generates a new HD master key (will not be activated) */ - CPubKey GenerateNewHDMasterKey(); + /* Generates a new HD seed (will not be activated) */ + CPubKey GenerateNewSeed(); - /* Set the current HD master key (will reset the chain child index counters) - Sets the master key's version based on the current wallet version (so the + /* Derives a new HD seed (will not be activated) */ + CPubKey DeriveNewSeed(const CKey& key); + + /* Set the current HD seed (will reset the chain child index counters) + Sets the seed's version based on the current wallet version (so the caller must ensure the current wallet version is correct before calling this function). */ - bool SetHDMasterKey(const CPubKey& key); + bool SetHDSeed(const CPubKey& key); /** * Blocks until the wallet state is up-to-date to /at least/ the current @@ -1130,7 +1167,7 @@ public: * Obviously holding cs_main/cs_wallet when going into this call may cause * deadlock */ - void BlockUntilSyncedToCurrentChain(); + void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_wallet); /** * Explicitly make the wallet learn the related scripts for outputs to the @@ -1146,12 +1183,6 @@ public: */ void LearnAllRelatedScripts(const CPubKey& key); - /** - * Get a destination of the requested type (if possible) to the specified script. - * This function will automatically add the necessary scripts to the wallet. - */ - CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); - /** Whether a given output is spendable by this wallet */ bool OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibility_filter) const; }; @@ -1218,18 +1249,6 @@ public: } }; -OutputType ParseOutputType(const std::string& str, OutputType default_type); -const std::string& FormatOutputType(OutputType type); - -/** - * Get a destination of the requested type (if possible) to the specified key. - * The caller must make sure LearnRelatedScripts has been called beforehand. - */ -CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); - -/** Get all destinations (potentially) supported by the wallet for the given key. */ -std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); - /** RAII object to check and reserve a wallet rescan */ class WalletRescanReserver { diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5b275131af..9c25ee7d76 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -246,9 +246,9 @@ public: } }; -bool +static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, - CWalletScanState &wss, std::string& strType, std::string& strErr) + CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { try { // Unserialize @@ -537,7 +537,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) int nMinVersion = 0; if (m_batch.Read((std::string)"minversion", nMinVersion)) { - if (nMinVersion > CLIENT_VERSION) + if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; pwallet->LoadMinVersion(nMinVersion); } @@ -640,7 +640,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW int nMinVersion = 0; if (m_batch.Read((std::string)"minversion", nMinVersion)) { - if (nMinVersion > CLIENT_VERSION) + if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; } @@ -756,7 +756,7 @@ void MaybeCompactWalletDB() return; } - for (CWallet* pwallet : GetWallets()) { + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { WalletDatabase& dbh = pwallet->GetDBHandle(); unsigned int nUpdateCounter = dbh.nUpdateCounter; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index a73d727c0c..3237376f63 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -62,7 +62,7 @@ class CHDChain public: uint32_t nExternalChainCounter; uint32_t nInternalChainCounter; - CKeyID masterKeyID; //!< master key hash160 + CKeyID seed_id; //!< seed hash160 static const int VERSION_HD_BASE = 1; static const int VERSION_HD_CHAIN_SPLIT = 2; @@ -76,7 +76,7 @@ public: { READWRITE(this->nVersion); READWRITE(nExternalChainCounter); - READWRITE(masterKeyID); + READWRITE(seed_id); if (this->nVersion >= VERSION_HD_CHAIN_SPLIT) READWRITE(nInternalChainCounter); } @@ -86,7 +86,7 @@ public: nVersion = CHDChain::CURRENT_VERSION; nExternalChainCounter = 0; nInternalChainCounter = 0; - masterKeyID.SetNull(); + seed_id.SetNull(); } }; @@ -99,7 +99,7 @@ public: int nVersion; int64_t nCreateTime; // 0 means unknown std::string hdKeypath; //optional HD/bip32 keypath - CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key + CKeyID hd_seed_id; //id of the HD seed used to derive this key CKeyMetadata() { @@ -120,7 +120,7 @@ public: if (this->nVersion >= VERSION_WITH_HDDATA) { READWRITE(hdKeypath); - READWRITE(hdMasterKeyID); + READWRITE(hd_seed_id); } } @@ -129,7 +129,7 @@ public: nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; hdKeypath.clear(); - hdMasterKeyID.SetNull(); + hd_seed_id.SetNull(); } }; diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 5bfde6faaf..020d8971cb 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -13,7 +13,7 @@ class CRPCTable; class WalletInitInterface { public: /** Get wallet help string */ - virtual std::string GetHelpString(bool showDebug) const = 0; + virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; /** Register wallet RPC*/ diff --git a/src/warnings.cpp b/src/warnings.cpp index 572c766600..534745f998 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -9,9 +9,9 @@ #include <warnings.h> CCriticalSection cs_warnings; -std::string strMiscWarning; -bool fLargeWorkForkFound = false; -bool fLargeWorkInvalidChainFound = false; +std::string strMiscWarning GUARDED_BY(cs_warnings); +bool fLargeWorkForkFound GUARDED_BY(cs_warnings) = false; +bool fLargeWorkInvalidChainFound GUARDED_BY(cs_warnings) = false; void SetMiscWarning(const std::string& strWarning) { @@ -40,7 +40,6 @@ void SetfLargeWorkInvalidChainFound(bool flag) std::string GetWarnings(const std::string& strFor) { std::string strStatusBar; - std::string strRPC; std::string strGUI; const std::string uiAlertSeperator = "<hr />"; @@ -51,9 +50,6 @@ std::string GetWarnings(const std::string& strFor) strGUI = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"); } - if (gArgs.GetBoolArg("-testsafemode", DEFAULT_TESTSAFEMODE)) - strStatusBar = strRPC = strGUI = "testsafemode enabled"; - // Misc warnings like out of disk space and clock is wrong if (strMiscWarning != "") { @@ -63,12 +59,12 @@ std::string GetWarnings(const std::string& strFor) if (fLargeWorkForkFound) { - strStatusBar = strRPC = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."; + strStatusBar = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."; strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."); } else if (fLargeWorkInvalidChainFound) { - strStatusBar = strRPC = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."; + strStatusBar = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."; strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."); } @@ -76,8 +72,6 @@ std::string GetWarnings(const std::string& strFor) return strGUI; else if (strFor == "statusbar") return strStatusBar; - else if (strFor == "rpc") - return strRPC; assert(!"GetWarnings(): invalid parameter"); return "error"; } diff --git a/src/warnings.h b/src/warnings.h index 3d7ac5aab4..904e8c0440 100644 --- a/src/warnings.h +++ b/src/warnings.h @@ -15,13 +15,10 @@ bool GetfLargeWorkForkFound(); void SetfLargeWorkInvalidChainFound(bool flag); /** Format a string that describes several potential problems detected by the core. * strFor can have three values: - * - "rpc": get critical warnings, which should put the client in safe mode if non-empty * - "statusbar": get all warnings * - "gui": get all warnings, translated (where possible) for GUI * This function only returns the highest priority warning of the set selected by strFor. */ std::string GetWarnings(const std::string& strFor); -static const bool DEFAULT_TESTSAFEMODE = false; - #endif // BITCOIN_WARNINGS_H diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index 68b425fa08..8cbc969972 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -1,4 +1,4 @@ -// 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. @@ -29,6 +29,15 @@ CZMQNotificationInterface::~CZMQNotificationInterface() } } +std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotifiers() const +{ + std::list<const CZMQAbstractNotifier*> result; + for (const auto* n : notifiers) { + result.push_back(n); + } + return result; +} + CZMQNotificationInterface* CZMQNotificationInterface::Create() { CZMQNotificationInterface* notificationInterface = nullptr; @@ -180,3 +189,5 @@ void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CB TransactionAddedToMempool(ptx); } } + +CZMQNotificationInterface* g_zmq_notification_interface = nullptr; diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index dee926ea5e..a0cc26a162 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -1,4 +1,4 @@ -// 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. @@ -18,6 +18,8 @@ class CZMQNotificationInterface final : public CValidationInterface public: virtual ~CZMQNotificationInterface(); + std::list<const CZMQAbstractNotifier*> GetActiveNotifiers() const; + static CZMQNotificationInterface* Create(); protected: @@ -37,4 +39,6 @@ private: std::list<CZMQAbstractNotifier*> notifiers; }; +extern CZMQNotificationInterface* g_zmq_notification_interface; + #endif // BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp new file mode 100644 index 0000000000..4f88bf4eb9 --- /dev/null +++ b/src/zmq/zmqrpc.cpp @@ -0,0 +1,61 @@ +// 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 <zmq/zmqrpc.h> + +#include <rpc/server.h> +#include <zmq/zmqabstractnotifier.h> +#include <zmq/zmqnotificationinterface.h> + +#include <univalue.h> + +namespace { + +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" + "\nResult:\n" + "[\n" + " { (json object)\n" + " \"type\": \"pubhashtx\", (string) Type of notification\n" + " \"address\": \"...\" (string) Address of the publisher\n" + " },\n" + " ...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getzmqnotifications", "") + + HelpExampleRpc("getzmqnotifications", "") + ); + } + + UniValue result(UniValue::VARR); + if (g_zmq_notification_interface != nullptr) { + for (const auto* n : g_zmq_notification_interface->GetActiveNotifiers()) { + UniValue obj(UniValue::VOBJ); + obj.pushKV("type", n->GetType()); + obj.pushKV("address", n->GetAddress()); + result.push_back(obj); + } + } + + return result; +} + +const CRPCCommand commands[] = +{ // category name actor (function) argNames + // ----------------- ------------------------ ----------------------- ---------- + { "zmq", "getzmqnotifications", &getzmqnotifications, {} }, +}; + +} // anonymous namespace + +void RegisterZMQRPCCommands(CRPCTable& t) +{ + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/zmq/zmqrpc.h b/src/zmq/zmqrpc.h new file mode 100644 index 0000000000..5a810a16fb --- /dev/null +++ b/src/zmq/zmqrpc.h @@ -0,0 +1,12 @@ +// 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_ZMQ_ZMQRPC_H +#define BITCOIN_ZMQ_ZMQRPC_H + +class CRPCTable; + +void RegisterZMQRPCCommands(CRPCTable& t); + +#endif // BITCOIN_ZMQ_ZMRRPC_H diff --git a/test/functional/README.md b/test/functional/README.md index 21050cc2fa..e6365222ff 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -20,6 +20,8 @@ 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) +- 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 - Use a module-level docstring to describe what the test is testing, and how it is testing it. @@ -74,7 +76,7 @@ over the network (`CBlock`, `CTransaction`, etc, along with the network-level wrappers for them, `msg_block`, `msg_tx`, etc). - P2P tests have two threads. One thread handles all network communication -with the bitcoind(s) being tested (using python's asyncore package); the other +with the bitcoind(s) being tested in a callback-based event loop; the other implements the test logic. - `P2PConnection` is the class used to connect to a bitcoind. `P2PInterface` @@ -82,10 +84,6 @@ contains the higher level logic for processing P2P payloads and connecting to the Bitcoin Core node application logic. For custom behaviour, subclass the P2PInterface object and override the callback methods. -- Call `network_thread_start()` after all `P2PInterface` objects are created to -start the networking thread. (Continue with the test logic in your existing -thread.) - - Can be used to write tests where specific P2P protocol behavior is tested. Examples tests are `p2p_unrequested_blocks.py`, `p2p_compactblocks.py`. diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index d1bf9206b2..91b6415a7c 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -63,7 +63,7 @@ def get_log_events(source, logfile): Log events may be split over multiple lines. We use the timestamp regex match as the marker for a new log event.""" try: - with open(logfile, 'r') as infile: + with open(logfile, 'r', encoding='utf-8') as infile: event = '' timestamp = '' for line in infile: diff --git a/test/functional/data/rpc_getblockstats.json b/test/functional/data/rpc_getblockstats.json new file mode 100644 index 0000000000..750abc5c42 --- /dev/null +++ b/test/functional/data/rpc_getblockstats.json @@ -0,0 +1,204 @@ +{ + "blocks": [ + "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1cd16b94f20a8a3dda91027c888025f2ec1a07ddcb2786bdff5916e66c00406f194ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002014341131c18d3b3aa30056a0f7a97c9ac852d3fd0ec9c76f7a25e83c01e7f821bf83574fb606f25c59200c844443201faf923ef5284fd4401f3104a323c601491a4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002078616da95299bd42cd8f813c8043816ec5741de466be3162e16bfff471808461f671e694afaf534d37df484f1990fc19a65fc26964b38141b7f8ecf61b8a50241a4ae75affff7f200500000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020a08613f37d305835a3a1553e77a479eba0f34c06c52e429ece54f5973cd77a7086a1efcaf75f1cd5be2c9deb6a7850225757a2cfc3031a91cc1330b3af4acc891b4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03540101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203b304fa1ce0505c2366982939ac148d9124c5ac747cc9aea133cea9916484966305de0e8d049f2be65c68d64d2c45746def5a9b4fcb8e298692b53b83b4690241b4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03550101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020fbdf49978ec4f0b23704b6772a614336872587e29c463f375836ffd775248837fed9f3fdfc33f076c6663ae78070fad7263c1e24161f3ee1a4857b8931815e2c1b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03560101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020c37548b9ca256b9ff17187d4d4309cf3143845b0a5811d3ca5427b2fddf000731a10985dfd473561c070c3527c3fe3941834cf51b3dfbacb501b44c69c9745ce1b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03570101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020383dd3766c0675440f26370ad62d687e335ea3a650dec9b02fe544107cc1823a13b98696d41562945457d655f4c6921f736068f7a72afd1ad6b335f2857d16631c4ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03580101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000207476dd96d81f53e63934ce28c9e89022e0f24d040ec3c838443e925fc3a2f230a94d0cbcefb4a151191dd7664153944d9eee3b7b46d4ba997f397ed2b72c3afe1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03590101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000209e425c73eea16cce98c0d47d6070aca29f0524eab4b97af84c386aa5322dd43055002f097e929bc6ad88ce869968e1b049aab7f6e45a5b869cf4349afd5d43e01c4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000202090e16a514fad40386413a100bbaa4fc14086a8d3501ac64c91fdef922e834a369e409444d0ec496eb0dd9a47f1fe81a7ab974bab28c50a912b994acf13b5f91c4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020777b767e42624c52775b331f19e81ba03be2f51a0608166cd5388c1a47d5e776473570bb9bba553a7db4a9a3083533027c54af1fea3ef6ef67757ef2255d64631c4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002076bb2bf3251a51ec367a42f8584043171a5d53157394cd776ebd017e2982127653d953aca3e2217f56533c043c07b9a926a30672ebce2562f1d06a6dc5044e7b1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020e6d7f02292655b73fc1f1958b09633ba07265d71d2a2784060b354cbbf1900202e9c9b02b63170002a94a0c9d8d787e2faa4c074a1ebdeb2855555347321dd101d4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020d61f077b0ed326e17f0a3d5af3fa876b72b434a252c9c3248d20130ed744287fcb10da470222dd29c7a07e2da7eb25d6499ed3919676df89cc630bd1b23fbb411d4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205efa9741cf51533ed6e07a97c71768372f53ca9c6df83894d64fe94c718eee23a207441e79ecdcf99ef3326385f5f675e2dea84c85ab8973219c63f92847ed5b1d4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03600101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020368386b0a0f46b2a2c4648eb9cb5dd1380c4f22e437e0bd49420670993361e5b9026632c2ddbb4b31b3c3118c51e43ea4d78e05c0aca0956278ead26a263d1521d4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401110101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020dd0a1594bbff6345a3e34f326e5ee605c855f5e0a5c363fc39615a8b1539b736200b51297dbee4aadf9b536cd2afe7617651e0a1d0f0610f436518a2a4dc54621d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401120101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020f302a1092709dc27a32d7229d391b90824a75828692c4bb2ca8f0ca5c88b3613c2e18797ffe8b367336338f90b2cf8c3f66277eb1e1ddbe18c052977294f10691d4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401130101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203dcc77aac703a8cd0e799384b74383c1d5f236426f77d516694607fc88fe85581276a20ceb98d02e6355c9dba4312e2fdc9832f4302cc307e1263f2df0aadd6a1e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020dfe109704a0b2801aee4232c31fb744145a7c80dd91a7727e16d4057719d5c3730f8296243521d82d96ed75c5af800a722fc9dde2e02af95c8c9822190ba07b21e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401150101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000201ef9ef2699bc36fd646bb9ba8629644bc98396122f6753710caf0315d7539f751382d3d85f17eb8b42cf17e54baa327886dcf6fa63207e097df8f9b84cc5422f1e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401160101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020834c91f23cd91b727be08b892f1c1a2f33c1e66d66f35607925fa1be4bca2c25b4145a73b1c71b945f5bb9ede3d8d95c9a3b12a0a81b7b14f440ec5146dd4ec71e4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401170101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205743202bf1e543a9be2a59b62be6a5a494511fab96968007b8d7199ea60a524697227ba473ceaf48d4f48ee17f8ee6cd2f1f5ddff03a641642ece240e7872f8f1e4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401180101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000207469f5c1841bf57275d82db23e5a8f0e8512af1eb10119c238519cdd6cdced34fd96dc659a874b3f5d30fbe6ea421a6b9791dfce8450f8851e4b90d80f0f794e1e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401190101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020b6338f55fcc473b744d53d675b4a83dcd80ddec9d02ad3323cf1ff50ac0412239d986ec20885d772fdc67803273aaec43871426ac93d3815846a8cd13dea5af11f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020a19d9edf2d22415cf226b4e1416c8a3097e0af222efac2bfeab15fa1f07b3f24c18580c4004de6d6244a30ce431c4be3ca44731509fc6b11710c792efed5e9191f4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000208f9f29a27424ec01ce77485617088506ca8faeef69300f0a474ad63ca5d32972d6049609fa3588d6ffab4d9d89a90636ac94c0ca1995f7768163abeb25dbf2bc1f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020fbd87d530a9ec3835ab579337fd16e512cec6c4779ab4d84e7256b3333dece28de1065c8c3d3d166e057139ac59af6f4f2c0d241b6269bbea6f61c5eff3dba431f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020dc6ef4f436baab2d6880f242a2588313a2739ac694e30319344045ee318c9524d0ec7fbcaca30ce85392cb03b64015ece769afb50fd07db05c15ec49abf7d71c1f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020386bf4cbb3708ae6345b9f2459bdd99d07422b05f9b005d2d4d1d3bf87d47359ebb22b3a15c8e94ddd8129527873b9bebfa10c54d11196961376efbcaad3c4681f4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020600e892f12ad82a23ce12684d3ffa0887eab5e3e97804fa651050b23366cc55ef2468e65c3d3cf49650657eb47d0b0b7949c71dcc0922eb824523157b7eff478204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401200101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002068e4aec52a3f4e44279e3a65cd476237bdfcc2328390bb31b8a903f89ddfa70e8d669f61b469acb31b1d4ecdc238e6616a83a30644a5d06fc2ca1aad6449d09b204ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401210101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020cf2428ae9a5014b910275807f54a8bbbeec47d462f9d284ada60329b4955ff10cf83c44ddde39a709aef54fb302c7f1cb36db8fe7c3befce20dcc3729767518c204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401220101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205801eef8cca082407ce4798648c4d3ba0fc4dd2d4459eccfe5300c7960760d16cb3dd78a2f22fb88717a175e45c53d34f970b94ef9f7cd1b6c279294d427d163204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401230101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002088320983a4bcb95b9f342994c6943c227f3102d3b16282f048ceb8e15748662a52d1207591a0a364bc9245a76e36530f147ec4d1b4e1917676b4071f542c3b19204ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401240101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020acd85b6d8087d3b6bd2a208a2e39b75da459c0e0eb14088075a23a2e043e8a4ed5a1754491f8180d293b42e6c04ec3f82e29c1f2600dc8607616f69a4a464e6e204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401250101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204873bcb379f78da4497ce1e22f6bfb63537b89c8c522257a7b7bef74e515ed1b6b235faab048fa73a76c68fdbdde6a4ee7ebe0a3b7b23df24ce75dbd2cf49c33214ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401260101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020a87f6d13bae8e2e07996c3316e8e0da6aec7d1aee6b80aa5883018e4d136db3e9a498ff7d322ad93863e0a5318af7e7d0ed683fd2e4ecf523f2b7369106dbe4a214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401270101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020d9b0618450a51c08f43187c479e20d351f0466464409bb3071dc0af7c51d65498a198cef23dddd2c4b93d9d3288ae922584e221a9ef1ded3dba5a2ad494d9237214ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401280101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020167dff31847b8dcad472bb6bb7d0af53b245f0f1d4c9f83d4ce14a0f05d42d7f0f2638ffc0e6896230f28df1865ef133dccb1f027545c6a1177dffd1fecf8a01214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401290101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000208236c04424573692504e777e179b9247e54622b118239311413812f13cefbf6e39a639143f599dc76208b2014de12a364716df2918af9186453e3676dca743d7214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020fab7b55aeb59f63315dbc10784c55e55635a7600cc4f3b94a00003007e7fc90b4af016248e9908882f8a7f0bd8743c8da82da119446e8b02e4d3b8d1d938a3aa214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020794b4160d8fd4ebe7611d0fc9d3e04f3038a485669f74075aa153852ed181121c577f4c0b7151f6d78a16e3c21ab291b53ced8a5c4f05a22caf24a25ed029d56224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204a8acdb549d2ed922360ae0e81de6c913c3fd84b0de51abacbf97162a99f7c26c656b252d3259c33ffc7e5d403843accc6ace9b7e60e911e2347a6a0b0abd122224ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020dee6d00c7058b3422e4273c98c16181e04ea93116496a8442de546c2ee9fd86b550013f39e004b3829dc3717b17fcfedc87de9450315fcca540963119cb264c1224ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000202ebca471f5fb2226a790233c3d0bc323f73d935872f3e15b66bd5fdcb822101d7b68788ed61d3fb8cb746f627e09db2fc09d8a07672747709d92ae400e053e78224ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ee500470fc1c71a82f2cbb9f8d5723bcdf57b8051fc458a8dbd8d0dbf60d0e40d1a0be0e50f3312d4830e3900186e5a6760d44006c164b4f0758218ef2b2de8e224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401300101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205057e8d8a7451d79325851dc8e0f4dfdb1dfaaab637509d9e61f0e064af5ee5c185221c53c0cf43261b3c238c0e8117da5d6ac60085615f7a3d8027726cb2143224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401310101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000200907f01d9c5c872296796ca77feb62eb414cc080e13a93e59e181528bc19c336eaabacb1ebcbd20b26f6bacdc712ffe17d4c8131e7f99b9cac309c0683737c04234ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401320101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000209bd6a4ec962d9e6199c0a2f39481a7ecc322fedbd2320cbe7dc984c6ab958421fe7a5c7f2513a3c3de9ddf7b211f5bfe85675b31183c4bb98ae79ab28cd055ac234ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401330101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000202b8a6381b7c9476fd77be379a929863db6b7edd9858d6eca0f68a430dc87cc3a9c6c8b34bfadfdacbe95cb1d4ae5ce4e4f4ccf0300171d7a91cdc97f620c7b37234ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401340101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203d3e9a0b4decf12b4402a2178b60599311c8a9c7d50ece365a61ca29530da7056754ddf7d77a11cc8ce680a74fae01d851bda024fc9c51712c55b4a190caef24234ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401350101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002067fa05082d4f7a29a3985f30798940cd854c76a2b20e9560b2047f7753193f71ac61b8df17a8a63f099c8f55869301fc2a0aa37355a4a2f89078135ee72a1362234ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401360101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020249684272865fec3ad62ecb73dfd930500e32e475306c9e5d4b6d545e3687b0a48deaeebfafa213f0a560994b3f4000d5e2b93951d7e5be40073503877292dc7234ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401370101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204671db1df91098669bb03c3e8504d432da99853601366c7de7585bb8f23a6e1e2996a16c11a0f9ae87d937f566c8bbd919040528c1bf8dae4e22a8f0ac5f935a244ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401380101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203046500cabfee552ed114c505279cd75c28faa811adcb5010c53c14df5de3216d265321a4168c70fff1c85fd83bd976cc03c8c1fb6567398cc24053e343ad137244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401390101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020591533cf8dd1fa1872c7e6d62ef714b28886f38f7921ee614e13b748eaf923282a96287277f18b1113a9c3ac384fd7b43bccd0e45114908ca5396a76cbd736fd244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205286b5747d0244a55e1dce435fdbb9f300d7138fff3b16767bc09196eb00256c6795e3f372a2d5d137244a4fd72fd799e8de913f868f22269eaa628d7d2970a1244ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000209649b55f6a5ff0d5db73aeb7089fcce605fb9dd23971e577b73747ffab586f4edf84581240223e2d8912a6eca049e06aa46ddbf271af08e9ac9605505311418b244ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020fe7ff82c11ad3a3db3dc11a03a67e0b86b727d232c80f37209b028bc2689296c0da76fb756e1c9833f38b198cfe843ab820fbc0c38e30f8f858f6ffdbd64e834244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020c02e6109b6aaf6643cac109ad5b5be7f7ec47c7993335bdceea6e0e490ae9067eb1fcee49ecc40a61477f934e3b9821f2cc7ada429fcca2cd645866742854c40254ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020956c46fce0adfc8a30d91c7b7f328f17c2a90771083884c4fdb7f24640598f6fa69c4e5971bd3b6cdc7e3ee98e03a969b28c3220fdc685cc2eb77293763ca4ba254ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203eec7d0ea9c539f47e684eec9dba900e27c805b8200f237924c7df8065957726c83caf659fa341bf45f733a92cf76daf2cfb6bccbf969a2753f5f7d9cda4bd1e254ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401400101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203c022b42283bf651ee4d536fe71b7e5382e9783d4a85f8bc159f00b97f16d82d312ac4f89f1b1496de576811f2ff17de44b512db0beddae59e030e2ce3eba4df254ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401410101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205da9709dea4bea4f3229d0eed439a4820a0e817f9fbbcd8bb355cd8052702973403358080a2881b823a740f58b6b0c922b42189e06326478cc33390f2c704743254ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401420101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020447c49f664916cc13a839e27f7cbe0b09dea990dec71dd479b537ecfb771c7159ae6241727c2645ce00817909ba97d43447fe2e146bce55b7dccd5bcd27d2e3d254ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401430101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ae3b214b1495953eea3b99af7ade4b8d22d615d598dd6c790c6576d6453ef35d37fbd3446f8431f00052f278c3e0359beba54a2f5064bc6be4990478fcf4e086264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401440101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204cc68f9f571f59145e1f7505550d56da13a797fbc7e5a178dd7f6f9241d91f007f2400f7aa1b32b30bf869e4da86f75eaf2baae182efc45c42c6245f06aba675264ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401450101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002096d55714a83c3d030cc72141eac3577b8a394b8366b2c93354fcecdafeab025022f5e26de7e4eba3f8e4a09f243b55ea6f08bfd013e2051cf1d5df20dcb3331b264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401460101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000202ccf49665bd46e4915dcc9221f5fc72124bb73fb11fed8869dc5862a47950c0d654693a1d86098212d68fa9a27f9df0faafea4aecebd8b203f2ab8f3f0b86196264ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401470101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000205d0be5aea546eab7734cbee757ea5f4983ab3a3f202323c1c88590bdbc8b561974cb0de6549bfdec92322ad53d8a4192433edab0e33a10e7561d2e27c7759f8a264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401480101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020fc3d70d4f690d7d7da90b1c832a51c2a92940cdd9fccda6a909c7256ad567160550e1089b48fe75f0f1e6f712cc2a1d4aad384a4ceacf1c71576420fe8e7de46264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401490101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020995ab1f2293e3aa1e7bef418919bdce60032e89e60223b8c12b17488c50af83b3a360b4f89551aa0ced646a6210b0c3d3f7d0464faa248b9d252c89615babc9f274ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020e02dd5e1201ebe7ef453e77df3021cffde1b5447b9eced017963489e005ac247e1f2b80e91180cd9744c3e126eb8a0fb34ced45587da6b0fa9fd1c6946f049bf274ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002081b265800d61feb37525c58aacc7e6d32cfc6b6579784f9952dca51c3addeb37da900ae4ec1b2075056001a361c0fc8f9ac1ca018bde2a2bd8fc587424dfe18f274ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000207daba0dc6fccc067a19dec46770d1443fc715b50540242ff26073a6748bcd9583804c315b783d4f9aac4dc8f109fa60bcbf1c3cf98a7e1e593fcb969e88aafae274ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204a35ab2d9c4656c4992d2445ee566e4f5cd0468358292c94839de2b270ba0628de9148db03da59ca2eb727a4f7a03f2b9ee1e4045f37bdaa4b75a89de56b63ee274ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020215ed7b31d118a45a06407133e41f3f9d25c913ff98f3e798144f981d9145305266efc9274f7c836aa0f8b831732aec2c1d85c5bb9176af4c889a707cca380fc274ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020425321a1f12ed4613b63b3cc5b4c7674242c3de67ea86984f2d9bb2766f6220a5c460ce6840832b7ef05f206c5d255bc2d8ea83753a7f5ef5c0bf3dbcbc7c74a284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401500101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204e088cfd312091bf589f0fc24131dba20ed83f716530a7b033356e9ec803be23a73c4af5b64fc631d20aa1f646c7c61c4cc5d4be6e2fa29570030d72219726a0284ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401510101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204287a2bf40300e39b45d6667d6e17abba37d98c41efc7023fd21b643fb2f6b76d915d10e1f9e224d3bfdf5203da57173f0b8d8eedc92ed9ff23372927e86073b284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401520101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002083137929ca902f409a1f35385b4d31e0f5163d488d71b00ade724d7831986a280a187c13fe0db45aed5fa4f5089d544a617da9f4f80fe6ce1d0711b228f3a6bd284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401530101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020b38c074d78cbcc8bbbf20c48e30639c2dcf444488efee59f5aa01c48322a30418009c11df7e14de6a6d0d72cc22f20ff5fdba3a7dbe409409ff92b1302340fb8284ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401540101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020a6865148220c7f5ac3259f57fa381676d6be5fdab56eed8e060808fcce4118492476fe6d5ecc681e7a18c20a04239143de1c2cddefbb08f15153b8e9b0c22f62284ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401550101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ae146263f4087106b8c51ffb5cdddb03a4e593ea1a22a8f6580eaa374185126db2187dbe694a1b17b7c5664115307965407274c25bf6ae02049817685f923256294ae75affff7f200600000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401560101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020718abe22705f33134812b56d04c129d453da890e27caf6e77d2bfb3f9be460083634f35fc8ea4a29b0f30d6c6b4926455a31de62d2f763bc95c8cd1a7a425599294ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401570101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002084537c50520f4e6b0173926320c3193f6b4259c9a724fe202337d3d5cf7da70b1104e1d6f25c411165a856a0bfdd5ddbe168238425c05271962aa6e5ebb676fb294ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401580101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204bae65b948c9790468cfc8fbf78a81e7aec5407dbab18c18ef1ab37a13f0c257792cd1246dac5ce265a8439e539db1aebe4776535d422315bac8e57a51605aa6294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401590101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000201e68985b2a15920524c3c2da77354c0ccfb202c3ab4005128eb2743aa33f0a5b0527a624a6d1f808c896d8cd1552c251a4ae5310e6c62493977b0965d17f580d294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015a0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ab1d90854505caa1ee748c1987f4cb6674844b84d17224bc3983fdd6e44a930b2edf3e5a90be7a9080ad15014ce796b38b6c19ddd3754443243fc277bcd2368a294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015b0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002062242f316efa083c17e2173d9f9831afc1465f3fe84431d8e52fec71ae358b243e29246e0db770708987f2cff380920321636926d9c66c2c808d2d5f0ed27d672a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015c0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020258c1a9b0ca17819d8d7fea5178e9c72c2e7c6769696f70098e0d5d2a9dc0e5cf039f5f686d727c6158ef5405ee8794ec2f89c641093a1dff0f5240263a441bc2a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015d0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020434ce26cddae99571a1d663419d715f88e6ff0e413c45f21c44b1991d041fd2745534db221054e2233052aaffe7d7232a91b3f0918c14eac74b2f704b37bfb8e2a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015e0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ec94475f112ea37dddd84f066e61446b431d28d2d9b6fbb336d16295e7eece52f1dc01abb3b27e71d8a25c3ed2d6fbbc5900bf954738ed63d57689ba5e68f6532a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015f0101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000206188cca18a2be3b7f0b500a724f1b73312873488f04c5082abd81ca0b2250a75ec0efb2fa994ad23e1f39416e69976e66fc4ef9f101bd4f60fcee1f8c45a2b802a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401600101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000202384b91bd0bfb1385ef42bda9b2fa43930aa8889214bb14f63c61a74f380dc7f9d547f2925ad39b9161fa55b9cd258463fd058234778a7c7b3061113d64179812a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401610101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204c266a3008c3bd95ae4bc8228bf878595cabf76cc2ef3fed32936777fe37833a1a13ea016868b2352a8d6d1eb35d0aee784cef06ed50655b8d84c089324351df2b4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401620101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ab2c78d1249de8482cf26bfb1616ac04f7b8404eafcae3e0654f398943fd41571b4d7549862be0b06ed1408ddc3e7c01b07af24c203a18fe9744e214b14d2d652b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401630101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002031aa0fc5e0242a9c9d91024b6b7e67544da72c4773537f881ee5caacd45a3c38c650ff5fa0ffd080b3e5f9752527b718e8c6b731467028e14b0b009d4e7406ee2b4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401640101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000200418c57609fc6ea3f24f569dc3afe163538a6aab940fe8c3d73c72cd781221380482c6f8ef2c19429da2f7a842bb811496aa86247c55f7ecb3337718b694d9472b4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401650101ffffffff0200f2052a01000000232103836d350d84c3782a7579c5f542c7e70fe628d3062c6bac563d2264c21e56f70eac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000204f36abaf10d6a8b3113103f3413475db0640c89a39f02a718e8bd2190fe87f1d6ffaf2b9fdbed67ebae4b36c97f9582814c99c90d0b5123f14da4861bf3e4f742b4ae75affff7f200100000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401660101ffffffff02b000062a01000000232102f6869601b2b9980b07fc047e309c1fc1433e08c5bf0498115c5c0e117ef59bfdac0000000000000000266a24aa21a9ed5896cd6a40cd126b09e317cbd179f39e2bcef2f9d423751d980258396416e671012000000000000000000000000000000000000000000000000000000000000000000000000002000000011cd16b94f20a8a3dda91027c888025f2ec1a07ddcb2786bdff5916e66c00406f0000000048473044022046d00465c4508cfd02fcb878b19d120e28be28e40658b1f15458828891ed1541022036aac054f36a42666dfb7b42a20506315a0b72232ce7704406e23c7a9515178701feffffff0200286bee0000000017a91481ddd4a9708ba8088cdcfeab9583ede8d83a298c8750bb9a3b0000000017a91484e16967722289584257803688aae36cd64480688765000000", + "00000020cc7c39992f2dae21e0ebd958f6ba77a6d0bcc568e044b9b61ca4d77536a4214e7b4ade79dd733ea72d97993aaac27f2263b91b1500467350ff35ea40c2850d392b4ae75affff7f200100000004020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401670101ffffffff0230d0062a01000000232102f58ba54b2d51c4e2a6096f9d266261b45f1026a86ba88c29ed8070dfe3f5ec6bac0000000000000000266a24aa21a9edb824d92cb231c2366f0726aedf8bfc2705239a40ae6b10a106534e8b5395a09d01200000000000000000000000000000000000000000000000000000000000000000000000000200000000010196bac2a0f6212b8e5710f8c7214dd32c393d38d20b7703cf0b7b25276bb6ab8001000000171600144f8a6c8d4c6c309b1e2b725b7496859e172ea367feffffff02781ef5050000000017a9149f00bafb542049fe32d532b0ea7494ebb7ae41398750daa4350000000017a9147794e6dc43de332ca7a095e582478c331446686d8702483045022100bd85ed3954f1151c2fde32c4021a32c96d7defa4a57c14b1a056be9b361a8e49022054947bf6fdb535c46cbee62efc1262cc0389a6eaa19afbcfa98fb1fb30c3ef230121031b2371df07fb88dddf590b246dc74defc265fdbfe258e4b168250859b806f5ce660000000200000001bf83574fb606f25c59200c844443201faf923ef5284fd4401f3104a323c601490000000049483045022100d1c4b09b488f6375ee4540a531a13b5549e88e2459bd88c84867e293c54862740220222e8af70c8d8b1139c2a7b616d9132bde94244edca7eee3c7a783b12839dadc01feffffff0250196bee0000000017a91490e5e33cfedf18d5cf911d6f853770c62e1f5d028700ca9a3b0000000017a91422311ee58518edf3a2289012461dc66bc8739d2687660000000200000000010196bac2a0f6212b8e5710f8c7214dd32c393d38d20b7703cf0b7b25276bb6ab800000000017160014b81faaafa52f7723f539f8d595147b1a112b38ecfeffffff0208bd9a3b0000000017a9146b2e611708a94d9c674dd08c7c4c1fbb97bcdba987005ed0b20000000017a914933a20f07bab1d8f341f391819466a271f0cfd648702483045022100dfa8b0052c7825e6abcea05d10fc82550a8d6538681ffc8188d48ba789e4d9b40220697371cdf527a6ecd1887f87da3c87dca3419e4a1aa2683e5c4e035199084d100121021cc37c2ec090f30ea0b49508ae8a7d65d9759903932666da56671c1faa445d5d53000000" + ], + "mocktime": 1525107225, + "stats": [ + { + "avgfee": 0, + "avgfeerate": 0, + "avgtxsize": 0, + "blockhash": "1d7fe80f19d28b8e712af0399ac84006db753441f3033111b3a8d610afab364f", + "height": 101, + "ins": 0, + "maxfee": 0, + "maxfeerate": 0, + "maxtxsize": 0, + "medianfee": 0, + "medianfeerate": 0, + "mediantime": 1525107242, + "mediantxsize": 0, + "minfee": 0, + "minfeerate": 0, + "mintxsize": 0, + "outs": 2, + "subsidy": 5000000000, + "swtotal_size": 0, + "swtotal_weight": 0, + "swtxs": 0, + "time": 1525107243, + "total_out": 0, + "total_size": 0, + "total_weight": 0, + "totalfee": 0, + "txs": 1, + "utxo_increase": 2, + "utxo_size_inc": 173 + }, + { + "avgfee": 3760, + "avgfeerate": 20, + "avgtxsize": 187, + "blockhash": "4e21a43675d7a41cb6b944e068c5bcd0a677baf658d9ebe021ae2d2f99397ccc", + "height": 102, + "ins": 1, + "maxfee": 3760, + "maxfeerate": 20, + "maxtxsize": 187, + "medianfee": 3760, + "medianfeerate": 20, + "mediantime": 1525107242, + "mediantxsize": 187, + "minfee": 3760, + "minfeerate": 20, + "mintxsize": 187, + "outs": 4, + "subsidy": 5000000000, + "swtotal_size": 0, + "swtotal_weight": 0, + "swtxs": 0, + "time": 1525107243, + "total_out": 4999996240, + "total_size": 187, + "total_weight": 748, + "totalfee": 3760, + "txs": 2, + "utxo_increase": 3, + "utxo_size_inc": 234 + }, + { + "avgfee": 18960, + "avgfeerate": 109, + "avgtxsize": 228, + "blockhash": "22d9b8b9c2a37c81515f3fc84f7241f6c07dbcea85ef16b00bcc33ae400a030f", + "height": 103, + "ins": 3, + "maxfee": 49800, + "maxfeerate": 300, + "maxtxsize": 248, + "medianfee": 3760, + "medianfeerate": 20, + "mediantime": 1525107243, + "mediantxsize": 248, + "minfee": 3320, + "minfeerate": 20, + "mintxsize": 188, + "outs": 8, + "subsidy": 5000000000, + "swtotal_size": 496, + "swtotal_weight": 1324, + "swtxs": 2, + "time": 1525107243, + "total_out": 9999939360, + "total_size": 684, + "total_weight": 2076, + "totalfee": 56880, + "txs": 4, + "utxo_increase": 5, + "utxo_size_inc": 380 + } + ] +}
\ No newline at end of file diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json new file mode 100644 index 0000000000..b85bc31c48 --- /dev/null +++ b/test/functional/data/rpc_psbt.json @@ -0,0 +1,78 @@ +{ + "invalid" : [ + "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==", + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==", + "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=", + "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==", + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA" + ], + "valid" : [ + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA", + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==", + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", + "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=" + ], + "creator" : [ + { + "inputs" : [ + { + "txid":"75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", + "vout":0 + }, + { + "txid":"1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", + "vout":1 + } + ], + "outputs" : [ + { + "bcrt1qmpwzkuwsqc9snjvgdt4czhjsnywa5yjdqpxskv":1.49990000 + }, + { + "bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1 + } + ], + "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" + } + ], + "signer" : [ + { + "privkeys" : [ + "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr", + "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d" + ], + "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + }, + { + "privkeys" : [ + "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", + "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" + ], + "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + } + ], + "combiner" : [ + { + "combine" : [ + "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + ], + "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" + } + ], + "finalizer" : [ + { + "finalize" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + } + ], + "extractor" : [ + { + "extract" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "result" : "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000" + } + ] +}
\ No newline at end of file diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 05d1c1bf4e..e2f1cc05b3 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -21,8 +21,6 @@ from test_framework.mininode import ( mininode_lock, msg_block, msg_getdata, - network_thread_join, - network_thread_start, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -135,9 +133,6 @@ class ExampleTest(BitcoinTestFramework): # Create P2P connections to two of the nodes self.nodes[0].add_p2p_connection(BaseNode()) - # Start up network handling in another thread. This needs to be called - # after the P2P connections have been created. - network_thread_start() # wait_for_verack ensures that the P2P connection is fully up. self.nodes[0].p2p.wait_for_verack() @@ -189,14 +184,9 @@ class ExampleTest(BitcoinTestFramework): connect_nodes(self.nodes[1], 2) self.log.info("Add P2P connection to node2") - # We can't add additional P2P connections once the network thread has started. Disconnect the connection - # to node0, wait for the network thread to terminate, then connect to node2. This is specific to - # the current implementation of the network thread and may be improved in future. self.nodes[0].disconnect_p2ps() - network_thread_join() self.nodes[2].add_p2p_connection(BaseNode()) - network_thread_start() self.nodes[2].p2p.wait_for_verack() self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us") diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 5a09142412..933a4740dd 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -33,16 +33,16 @@ import time from test_framework.blocktools import (create_block, create_coinbase) from test_framework.key import CECKey -from test_framework.mininode import (CBlockHeader, - COutPoint, - CTransaction, - CTxIn, - CTxOut, - network_thread_join, - network_thread_start, - P2PInterface, - msg_block, - msg_headers) +from test_framework.messages import ( + CBlockHeader, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + msg_block, + msg_headers +) +from test_framework.mininode import P2PInterface from test_framework.script import (CScript, OP_TRUE) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -68,12 +68,12 @@ class AssumeValidTest(BitcoinTestFramework): def send_blocks_until_disconnected(self, p2p_conn): """Keep sending blocks to the node until we're disconnected.""" for i in range(len(self.blocks)): - if p2p_conn.state != "connected": + if not p2p_conn.is_connected: break try: p2p_conn.send_message(msg_block(self.blocks[i])) except IOError as e: - assert str(e) == 'Not connected, no pushbuf' + assert not p2p_conn.is_connected break def assert_blockchain_height(self, node, height): @@ -98,8 +98,6 @@ class AssumeValidTest(BitcoinTestFramework): # Connect to node0 p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) - - network_thread_start() self.nodes[0].p2p.wait_for_verack() # Build the blockchain @@ -160,9 +158,7 @@ class AssumeValidTest(BitcoinTestFramework): self.block_time += 1 height += 1 - # We're adding new connections so terminate the network thread self.nodes[0].disconnect_p2ps() - network_thread_join() # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) @@ -172,8 +168,6 @@ class AssumeValidTest(BitcoinTestFramework): p2p1 = self.nodes[1].add_p2p_connection(BaseNode()) p2p2 = self.nodes[2].add_p2p_connection(BaseNode()) - network_thread_start() - p2p0.wait_for_verack() p2p1.wait_for_verack() p2p2.wait_for_verack() diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index eee38ce648..d8521550b5 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -67,7 +67,7 @@ class BIP68Test(BitcoinTestFramework): # If sequence locks were used, this would require 1 block for the # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 - tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] + tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] tx1.vout = [CTxOut(value, CScript([b'a']))] tx1_signed = self.nodes[0].signrawtransactionwithwallet(ToHex(tx1))["hex"] @@ -80,7 +80,7 @@ class BIP68Test(BitcoinTestFramework): tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] - tx2.vout = [CTxOut(int(value-self.relayfee*COIN), CScript([b'a']))] + tx2.vout = [CTxOut(int(value - self.relayfee * COIN), CScript([b'a' * 35]))] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx2)) @@ -222,7 +222,7 @@ class BIP68Test(BitcoinTestFramework): tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] - tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee*COIN), CScript([b'a']))] + tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), CScript([b'a' * 35]))] tx.rehash() if (orig_tx.hash in node.getrawmempool()): @@ -350,7 +350,7 @@ class BIP68Test(BitcoinTestFramework): tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)] - tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))] + tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), CScript([b'a' * 35]))] tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3)) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 38b76239e5..62c0582381 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -20,7 +20,7 @@ from test_framework.messages import ( uint256_from_compact, uint256_from_str, ) -from test_framework.mininode import P2PDataStore, network_thread_start, network_thread_join +from test_framework.mininode import P2PDataStore from test_framework.script import ( CScript, MAX_SCRIPT_ELEMENT_SIZE, @@ -32,6 +32,7 @@ from test_framework.script import ( OP_ELSE, OP_ENDIF, OP_EQUAL, + OP_DROP, OP_FALSE, OP_HASH160, OP_IF, @@ -82,10 +83,7 @@ class FullBlockTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] # convenience reference to the node - # reconnect_p2p() expects the network thread to be running - network_thread_start() - - self.reconnect_p2p() + self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} self.coinbase_key = CECKey() @@ -1218,7 +1216,7 @@ class FullBlockTest(BitcoinTestFramework): block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py - def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): + def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])): return create_transaction(spend_tx, n, b"", value, script) # sign a transaction, using the key we know about @@ -1296,16 +1294,11 @@ class FullBlockTest(BitcoinTestFramework): self.blocks[block_number] = block return block - def reconnect_p2p(self): + def bootstrap_p2p(self): """Add a 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.""" - - network_thread_join() - self.nodes[0].disconnect_p2ps() + Helper to connect and wait for version handshake.""" self.nodes[0].add_p2p_connection(P2PDataStore()) - network_thread_start() # We need to wait for the initial getheaders from the peer before we # start populating our blockstore. If we don't, then we may run ahead # to the next subtest before we receive the getheaders. We'd then send @@ -1314,6 +1307,14 @@ class FullBlockTest(BitcoinTestFramework): # unexpectedly disconnected if the DoS score for that error is 50. self.nodes[0].p2p.wait_for_getheaders(timeout=5) + def reconnect_p2p(self): + """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() + def sync_blocks(self, blocks, success=True, reject_code=None, reject_reason=None, request_block=True, reconnect=False, timeout=60): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index e9a8945e76..b484bffe0d 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -67,10 +67,6 @@ class BIP65Test(BitcoinTestFramework): def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) - - network_thread_start() - - # wait_for_verack ensures that the P2P connection is fully up. self.nodes[0].p2p.wait_for_verack() self.log.info("Mining %d blocks", CLTV_HEIGHT - 2) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index e9924451d1..3abc1f4c68 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -36,13 +36,15 @@ class ConfArgsTest(BitcoinTestFramework): f.write("datadir=" + new_data_dir + "\n") f.write(conf_file_contents) - self.nodes[0].assert_start_raises_init_error(['-conf=' + conf_file], 'Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.') + # Temporarily disabled, because this test would access the user's home dir (~/.bitcoin) + #self.nodes[0].assert_start_raises_init_error(['-conf=' + conf_file], 'Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.') # Create the directory and ensure the config file now works os.mkdir(new_data_dir) - self.start_node(0, ['-conf='+conf_file, '-wallet=w1']) - self.stop_node(0) - assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1')) + # 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', 'wallets', 'w1')) # Ensure command line argument overrides datadir in conf os.mkdir(new_data_dir_2) diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 37d60aad61..2499214fbd 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -49,7 +49,7 @@ import time from test_framework.blocktools import create_coinbase, create_block from test_framework.messages import ToHex, CTransaction -from test_framework.mininode import network_thread_start, P2PDataStore +from test_framework.mininode import P2PDataStore from test_framework.script import ( CScript, OP_CHECKSEQUENCEVERIFY, @@ -183,7 +183,6 @@ class BIP68_112_113Test(BitcoinTestFramework): def run_test(self): self.nodes[0].add_p2p_connection(P2PDataStore()) - network_thread_start() self.nodes[0].p2p.wait_for_verack() self.log.info("Generate blocks in the past for coinbase outputs.") diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 02dcc3e55d..13224466d3 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -56,8 +56,6 @@ class BIP66Test(BitcoinTestFramework): def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) - network_thread_start() - # wait_for_verack ensures that the P2P connection is fully up. self.nodes[0].p2p.wait_for_verack() diff --git a/test/functional/feature_help.py b/test/functional/feature_help.py index fd4a72f628..d38275a9ca 100755 --- a/test/functional/feature_help.py +++ b/test/functional/feature_help.py @@ -36,6 +36,17 @@ class HelpTest(BitcoinTestFramework): output = self.nodes[0].process.stdout.read() assert b'version' in output self.log.info("Version text received: {} (...)".format(output[0:60])) + + # Test that arguments not in the help results in an error + self.log.info("Start bitcoind with -fakearg to make sure it does not start") + self.nodes[0].start(extra_args=['-fakearg'], stderr=subprocess.PIPE, stdout=subprocess.PIPE) + # Node should exit immediately and output an error to stderr + ret_code = self.nodes[0].process.wait(timeout=1) + assert_equal(ret_code, 1) + output = self.nodes[0].process.stderr.read() + assert b'Error parsing command line arguments' in output + self.log.info("Error message received: {} (...)".format(output[0:60])) + # Clean up TestNode state self.nodes[0].running = False self.nodes[0].process = None diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py new file mode 100755 index 0000000000..9a7a0ca103 --- /dev/null +++ b/test/functional/feature_includeconf.py @@ -0,0 +1,80 @@ +#!/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 the includeconf argument + +Verify that: + +1. adding includeconf to the configuration file causes the includeconf + file to be loaded in the correct order. +2. includeconf cannot be used as a command line argument. +3. includeconf cannot be used recursively (ie includeconf can only + be used from the base config file). +4. multiple includeconf arguments can be specified in the main config + file. +""" +import os + +from test_framework.test_framework import BitcoinTestFramework + +class IncludeConfTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + + def setup_chain(self): + super().setup_chain() + # Create additional config files + # - tmpdir/node0/relative.conf + with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + f.write("uacomment=relative\n") + # - tmpdir/node0/relative2.conf + with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f: + f.write("uacomment=relative2\n") + with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: + f.write("uacomment=main\nincludeconf=relative.conf\n") + + def run_test(self): + self.log.info("-includeconf works from config file. subversion should end with 'main; relative)/'") + + subversion = self.nodes[0].getnetworkinfo()["subversion"] + assert subversion.endswith("main; relative)/") + + self.log.info("-includeconf cannot be used as command-line arg") + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") + + self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") + with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: + f.write("includeconf=relative2.conf\n") + self.start_node(0) + + subversion = self.nodes[0].getnetworkinfo()["subversion"] + assert subversion.endswith("main; relative)/") + self.stop_node(0, expected_stderr="warning: -includeconf cannot be used from included files; ignoring -includeconf=relative2.conf") + + self.log.info("-includeconf cannot contain invalid arg") + with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + f.write("foo=bar\n") + self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Invalid configuration value foo") + + self.log.info("-includeconf cannot be invalid path") + os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) + self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") + + self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") + with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + # Restore initial file contents + f.write("uacomment=relative\n") + + with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: + f.write("includeconf=relative2.conf\n") + + self.start_node(0) + + subversion = self.nodes[0].getnetworkinfo()["subversion"] + assert subversion.endswith("main; relative; relative2)/") + +if __name__ == '__main__': + IncludeConfTest().main() diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 072ba6c7c7..c413ecf705 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -57,7 +57,6 @@ class MaxUploadTest(BitcoinTestFramework): for _ in range(3): p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn())) - network_thread_start() for p2pc in p2p_conns: p2pc.wait_for_verack() @@ -100,7 +99,7 @@ class MaxUploadTest(BitcoinTestFramework): assert_equal(p2p_conns[0].block_receive_map[big_old_block], i+1) assert_equal(len(self.nodes[0].getpeerinfo()), 3) - # At most a couple more tries should succeed (depending on how long + # At most a couple more tries should succeed (depending on how long # the test has been running so far). for i in range(3): p2p_conns[0].send_message(getdata_request) @@ -148,8 +147,6 @@ class MaxUploadTest(BitcoinTestFramework): # Reconnect to self.nodes[0] self.nodes[0].add_p2p_connection(TestP2PConn()) - - network_thread_start() self.nodes[0].p2p.wait_for_verack() #retrieve 20 blocks which should be enough to break the 1MB limit diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index 8964c8d64b..6d51f31e35 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -36,7 +36,7 @@ class NotificationsTest(BitcoinTestFramework): wait_until(lambda: os.path.isfile(self.block_filename) and os.stat(self.block_filename).st_size >= (block_count * 65), timeout=10) # file content should equal the generated blocks hashes - with open(self.block_filename, 'r') as f: + with open(self.block_filename, 'r', encoding="utf-8") as f: assert_equal(sorted(blocks), sorted(l.strip() for l in f.read().splitlines())) self.log.info("test -walletnotify") @@ -45,7 +45,7 @@ class NotificationsTest(BitcoinTestFramework): # file content should equal the generated transaction hashes txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) - with open(self.tx_filename, 'r') as f: + with open(self.tx_filename, 'r', encoding="ascii") as f: assert_equal(sorted(txids_rpc), sorted(l.strip() for l in f.read().splitlines())) os.remove(self.tx_filename) @@ -58,7 +58,7 @@ class NotificationsTest(BitcoinTestFramework): # file content should equal the generated transaction hashes txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) - with open(self.tx_filename, 'r') as f: + with open(self.tx_filename, 'r', encoding="ascii") as f: assert_equal(sorted(txids_rpc), sorted(l.strip() for l in f.read().splitlines())) # Mine another 41 up-version blocks. -alertnotify should trigger on the 51st. diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index 7db6a03b45..24659eac77 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -15,7 +15,7 @@ Generate 427 more blocks. from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -from test_framework.mininode import CTransaction, network_thread_start +from test_framework.messages import CTransaction from test_framework.blocktools import create_coinbase, create_block, add_witness_commitment from test_framework.script import CScript from io import BytesIO @@ -42,7 +42,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', '-walletprematurewitness', '-vbparams=segwit:0:999999999999', '-addresstype=legacy', "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy', "-deprecatedrpc=addwitnessaddress"]] def run_test(self): self.address = self.nodes[0].getnewaddress() @@ -50,7 +50,6 @@ class NULLDUMMYTest(BitcoinTestFramework): self.wit_address = self.nodes[0].addwitnessaddress(self.address) self.wit_ms_address = self.nodes[0].addmultisigaddress(1, [self.address], '', 'p2sh-segwit')['address'] - network_thread_start() self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 coinbase_txid = [] for i in self.coinbase_blocks: diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 60859de7a5..2d10c547e2 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -79,9 +79,9 @@ class ProxyTest(BitcoinTestFramework): # Note: proxies are not used to connect to local nodes # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost args = [ - ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'], - ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'], - ['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'], + ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'], + ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'], + ['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'], [] ] if self.have_ipv6: diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 3adde8dd73..d400507a66 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -124,7 +124,7 @@ class PruneTest(BitcoinTestFramework): # Reboot node 1 to clear its mempool (hopefully make the invalidate faster) # Lower the block max size so we don't keep mining all our big mempool transactions (from disconnected blocks) self.stop_node(1) - self.start_node(1, extra_args=["-maxreceivebuffer=20000","-blockmaxweight=20000", "-checkblocks=5", "-disablesafemode"]) + self.start_node(1, extra_args=["-maxreceivebuffer=20000","-blockmaxweight=20000", "-checkblocks=5"]) height = self.nodes[1].getblockcount() self.log.info("Current block height: %d" % height) @@ -147,7 +147,7 @@ class PruneTest(BitcoinTestFramework): # Reboot node1 to clear those giant tx's from mempool self.stop_node(1) - self.start_node(1, extra_args=["-maxreceivebuffer=20000","-blockmaxweight=20000", "-checkblocks=5", "-disablesafemode"]) + self.start_node(1, extra_args=["-maxreceivebuffer=20000","-blockmaxweight=20000", "-checkblocks=5"]) self.log.info("Generating new longer chain of 300 more blocks") self.nodes[1].generate(300) @@ -260,10 +260,17 @@ class PruneTest(BitcoinTestFramework): # should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000) assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500)) + # Save block transaction count before pruning, assert value + block1_details = node.getblock(node.getblockhash(1)) + assert_equal(block1_details["nTx"], len(block1_details["tx"])) + # mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight) node.generate(6) assert_equal(node.getblockchaininfo()["blocks"], 1001) + # Pruned block should still know the number of transactions + assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"]) + # negative heights should raise an exception assert_raises_rpc_error(-8, "Negative", node.pruneblockchain, -10) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index d6ab5ecc37..c087960782 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -123,7 +123,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) @@ -132,7 +132,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Should fail because we haven't changed the fee tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(1*COIN, CScript([b'b']))] + tx1b.vout = [CTxOut(1 * COIN, CScript([b'b' * 35]))] tx1b_hex = txToHex(tx1b) # This will raise an exception due to insufficient fee @@ -143,7 +143,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Extra 0.1 BTC fee tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] + tx1b.vout = [CTxOut(int(0.9 * COIN), CScript([b'b' * 35]))] tx1b_hex = txToHex(tx1b) # Replacement still disabled even with "enough fee" assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[1].sendrawtransaction, tx1b_hex, True) @@ -175,7 +175,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): remaining_value -= 1*COIN tx = CTransaction() tx.vin = [CTxIn(prevout, nSequence=0)] - tx.vout = [CTxOut(remaining_value, CScript([1]))] + tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))] tx_hex = txToHex(tx) txid = self.nodes[0].sendrawtransaction(tx_hex, True) chain_txids.append(txid) @@ -185,7 +185,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # child fees - 40 BTC - so this attempt is rejected. dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - 30*COIN, CScript([1]))] + dbl_tx.vout = [CTxOut(initial_nValue - 30 * COIN, CScript([1] * 35))] dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception due to insufficient fee @@ -194,7 +194,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Accepted with sufficient fee dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(1*COIN, CScript([1]))] + dbl_tx.vout = [CTxOut(1 * COIN, CScript([1] * 35))] dbl_tx_hex = txToHex(dbl_tx) self.nodes[0].sendrawtransaction(dbl_tx_hex, True) @@ -247,7 +247,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Attempt double-spend, will fail because too little fee paid dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - fee*n, CScript([1]))] + dbl_tx.vout = [CTxOut(initial_nValue - fee * n, CScript([1] * 35))] dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) @@ -255,7 +255,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # 1 BTC fee is enough dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - fee*n - 1*COIN, CScript([1]))] + dbl_tx.vout = [CTxOut(initial_nValue - fee * n - 1 * COIN, CScript([1] * 35))] dbl_tx_hex = txToHex(dbl_tx) self.nodes[0].sendrawtransaction(dbl_tx_hex, True) @@ -275,7 +275,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - 2*fee*n, CScript([1]))] + dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, CScript([1] * 35))] dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) @@ -290,7 +290,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) self.nodes[0].sendrawtransaction(tx1a_hex, True) @@ -311,7 +311,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1a = CTransaction() tx1a.vin = [CTxIn(utxo1, nSequence=0)] - tx1a.vout = [CTxOut(int(1.1*COIN), CScript([b'a']))] + tx1a.vout = [CTxOut(int(1.1 * COIN), CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) @@ -330,7 +330,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Spend tx1a's output to test the indirect case. tx1b = CTransaction() tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] - tx1b.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1b.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1b_hex = txToHex(tx1b) tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True) tx1b_txid = int(tx1b_txid, 16) @@ -351,7 +351,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1 = CTransaction() tx1.vin = [CTxIn(confirmed_utxo)] - tx1.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1_hex = txToHex(tx1) self.nodes[0].sendrawtransaction(tx1_hex, True) @@ -390,7 +390,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): for i in range(MAX_REPLACEMENT_LIMIT+1): tx_i = CTransaction() tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)] - tx_i.vout = [CTxOut(split_value-fee, CScript([b'a']))] + tx_i.vout = [CTxOut(split_value - fee, CScript([b'a' * 35]))] tx_i_hex = txToHex(tx_i) self.nodes[0].sendrawtransaction(tx_i_hex, True) @@ -423,14 +423,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Create a non-opting in transaction tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)] - tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) # Shouldn't be able to double-spend tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] + tx1b.vout = [CTxOut(int(0.9 * COIN), CScript([b'b' * 35]))] tx1b_hex = txToHex(tx1b) # This will raise an exception @@ -441,14 +441,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Create a different non-opting in transaction tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)] - tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx2a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx2a_hex = txToHex(tx2a) tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True) # Still shouldn't be able to double-spend tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] + tx2b.vout = [CTxOut(int(0.9 * COIN), CScript([b'b' * 35]))] tx2b_hex = txToHex(tx2b) # This will raise an exception @@ -471,12 +471,12 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx3b = CTransaction() tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] - tx3b.vout = [CTxOut(int(0.5*COIN), CScript([b'e']))] + tx3b.vout = [CTxOut(int(0.5 * COIN), CScript([b'e' * 35]))] tx3b_hex = txToHex(tx3b) tx3c = CTransaction() tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)] - tx3c.vout = [CTxOut(int(0.5*COIN), CScript([b'f']))] + tx3c.vout = [CTxOut(int(0.5 * COIN), CScript([b'f' * 35]))] tx3c_hex = txToHex(tx3c) self.nodes[0].sendrawtransaction(tx3b_hex, True) @@ -493,7 +493,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) @@ -519,14 +519,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))] + tx2a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx2a_hex = txToHex(tx2a) self.nodes[0].sendrawtransaction(tx2a_hex, True) # Lower fee, but we'll prioritise it tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2b.vout = [CTxOut(int(1.01*COIN), CScript([b'a']))] + tx2b.vout = [CTxOut(int(1.01 * COIN), CScript([b'a' * 35]))] tx2b.rehash() tx2b_hex = txToHex(tx2b) diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index e835b9d777..b10306b283 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -16,7 +16,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.mininode import sha256, CTransaction, CTxIn, COutPoint, CTxOut, COIN, ToHex, FromHex from test_framework.address import script_to_p2sh, key_to_p2pkh -from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, OP_TRUE +from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, OP_TRUE, OP_DROP from io import BytesIO NODE_0 = 0 @@ -42,9 +42,9 @@ class SegWitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"], - ["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"], - ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [["-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"], + ["-blockversion=4", "-promiscuousmempoolflags=517", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"], + ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]] def setup_network(self): super().setup_network() @@ -129,21 +129,6 @@ class SegWitTest(BitcoinTestFramework): self.nodes[0].generate(260) #block 423 sync_blocks(self.nodes) - self.log.info("Verify default node can't accept any witness format txs before fork") - # unsigned, no scriptsig - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", wit_ids[NODE_0][WIT_V0][0], False) - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", wit_ids[NODE_0][WIT_V1][0], False) - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V0][0], False) - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False) - # unsigned with redeem script - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V0][0], False, witness_script(False, self.pubkey[0])) - self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False, witness_script(True, self.pubkey[0])) - # signed - self.fail_accept(self.nodes[0], "no-witness-yet", wit_ids[NODE_0][WIT_V0][0], True) - self.fail_accept(self.nodes[0], "no-witness-yet", wit_ids[NODE_0][WIT_V1][0], True) - self.fail_accept(self.nodes[0], "no-witness-yet", p2sh_ids[NODE_0][WIT_V0][0], True) - self.fail_accept(self.nodes[0], "no-witness-yet", p2sh_ids[NODE_0][WIT_V1][0], True) - 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 @@ -164,6 +149,16 @@ class SegWitTest(BitcoinTestFramework): segwit_tx_list = self.nodes[2].getblock(block[0])["tx"] assert_equal(len(segwit_tx_list), 5) + self.log.info("Verify default node can't accept txs with missing witness") + # unsigned, no scriptsig + self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", wit_ids[NODE_0][WIT_V0][0], False) + self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", wit_ids[NODE_0][WIT_V1][0], False) + self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V0][0], False) + self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False) + # unsigned with redeem script + self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V0][0], False, witness_script(False, self.pubkey[0])) + 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)) @@ -212,7 +207,7 @@ class SegWitTest(BitcoinTestFramework): # Now create tx2, which will spend from txid1. tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(txid1, 16), 0), b'')) - tx.vout.append(CTxOut(int(49.99*COIN), CScript([OP_TRUE]))) + tx.vout.append(CTxOut(int(49.99 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx2_hex = self.nodes[0].signrawtransactionwithwallet(ToHex(tx))['hex'] txid2 = self.nodes[0].sendrawtransaction(tx2_hex) tx = FromHex(CTransaction(), tx2_hex) @@ -221,7 +216,7 @@ class SegWitTest(BitcoinTestFramework): # Now create tx3, which will spend from txid2 tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(txid2, 16), 0), b"")) - tx.vout.append(CTxOut(int(49.95*COIN), CScript([OP_TRUE]))) # Huge fee + tx.vout.append(CTxOut(int(49.95 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) # Huge fee tx.calc_sha256() txid3 = self.nodes[0].sendrawtransaction(ToHex(tx)) assert(tx.wit.is_null()) @@ -303,8 +298,10 @@ class SegWitTest(BitcoinTestFramework): v = self.nodes[0].getaddressinfo(i) if (v['isscript']): [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - # bare and p2sh multisig with compressed keys should always be spendable - spendable_anytime.extend([bare, p2sh]) + # p2sh multisig with compressed keys should always be spendable + spendable_anytime.extend([p2sh]) + # bare multisig can be watched and signed, but is not treated as ours + solvable_after_importaddress.extend([bare]) # P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after direct importaddress spendable_after_importaddress.extend([p2wsh, p2sh_p2wsh]) else: @@ -320,8 +317,10 @@ class SegWitTest(BitcoinTestFramework): v = self.nodes[0].getaddressinfo(i) if (v['isscript']): [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - # bare and p2sh multisig with uncompressed keys should always be spendable - spendable_anytime.extend([bare, p2sh]) + # p2sh multisig with uncompressed keys should always be spendable + spendable_anytime.extend([p2sh]) + # bare multisig can be watched and signed, but is not treated as ours + solvable_after_importaddress.extend([bare]) # P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen unseen_anytime.extend([p2wsh, p2sh_p2wsh]) else: diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index 2985569a8f..a03c20b088 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -12,7 +12,7 @@ import re from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import msg_block -from test_framework.mininode import P2PInterface, network_thread_start, mininode_lock +from test_framework.mininode import P2PInterface, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import wait_until @@ -65,7 +65,6 @@ class VersionBitsWarningTest(BitcoinTestFramework): # Handy alias node = self.nodes[0] node.add_p2p_connection(P2PInterface()) - network_thread_start() node.p2p.wait_for_verack() # Mine one period worth of blocks diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 86ccea4394..def71c5f0f 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -3,11 +3,10 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the ZMQ notification interface.""" -import configparser -import os import struct -from test_framework.test_framework import BitcoinTestFramework, SkipTest +from test_framework.test_framework import ( + BitcoinTestFramework, skip_if_no_bitcoind_zmq, skip_if_no_py3_zmq) from test_framework.mininode import CTransaction from test_framework.util import (assert_equal, bytes_to_hex_str, @@ -39,20 +38,9 @@ class ZMQTest (BitcoinTestFramework): self.num_nodes = 2 def setup_nodes(self): - # Try to import python3-zmq. Skip this test if the import fails. - try: - import zmq - except ImportError: - raise SkipTest("python3-zmq module not available.") - - # Check that bitcoin has been built with ZMQ enabled. - config = configparser.ConfigParser() - if not self.options.configfile: - self.options.configfile = os.path.abspath(os.path.join(os.path.dirname(__file__), "../config.ini")) - config.read_file(open(self.options.configfile)) - - if not config["components"].getboolean("ENABLE_ZMQ"): - raise SkipTest("bitcoind has not been built with zmq enabled.") + skip_if_no_py3_zmq() + skip_if_no_bitcoind_zmq(self) + import zmq # Initialize ZMQ context and socket. # All messages are received in the same socket which means diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 8880db8002..79cf159f43 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -70,7 +70,10 @@ class MempoolPackagesTest(BitcoinTestFramework): assert_equal(mempool[x]['descendantcount'], descendant_count) descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) + assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) + assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) + assert_equal(mempool[x]['fees']['descendant'], descendant_fees) descendant_size += mempool[x]['size'] assert_equal(mempool[x]['descendantsize'], descendant_size) descendant_count += 1 @@ -132,6 +135,7 @@ class MempoolPackagesTest(BitcoinTestFramework): ancestor_fees = 0 for x in chain: ancestor_fees += mempool[x]['fee'] + assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001')) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000) # Undo the prioritisetransaction for later tests @@ -145,6 +149,7 @@ class MempoolPackagesTest(BitcoinTestFramework): descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] + assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001')) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) # Adding one more transaction on to the chain should fail. @@ -170,7 +175,9 @@ class MempoolPackagesTest(BitcoinTestFramework): descendant_fees += mempool[x]['fee'] if (x == chain[-1]): assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002)) + assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee']+satoshi_round(0.00002)) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000) + assert_equal(mempool[x]['fees']['descendant'], descendant_fees+satoshi_round(0.00002)) # TODO: check that node1's mempool is as expected diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index b433f11aa5..85f1af6682 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -120,7 +120,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"] # This will raise an exception due to min relay fee not being met - assert_raises_rpc_error(-26, "min relay fee not met, 0 < 134 (code 66)", self.nodes[0].sendrawtransaction, tx_hex) + assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex) assert(tx_id not in self.nodes[0].getrawmempool()) # This is a less than 1000-byte transaction, so just set the fee diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 1657d97281..17aacd8152 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -12,7 +12,8 @@ from test_framework.mininode import * from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment -from test_framework.script import CScript, OP_TRUE +from test_framework.script import CScript, OP_TRUE, OP_DROP + # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): @@ -86,7 +87,7 @@ class TestP2PConn(P2PInterface): This is used when we want to send a message into the node that we expect will get us disconnected, eg an invalid block.""" self.send_message(message) - wait_until(lambda: self.state != "connected", timeout=timeout, lock=mininode_lock) + wait_until(lambda: not self.is_connected, timeout=timeout, lock=mininode_lock) class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): @@ -423,7 +424,7 @@ class CompactBlocksTest(BitcoinTestFramework): for i in range(num_transactions): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) - tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) + tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx.rehash() utxo = [tx.sha256, 0, tx.vout[0].nValue] block.vtx.append(tx) @@ -787,13 +788,11 @@ class CompactBlocksTest(BitcoinTestFramework): assert_equal(int(node.getbestblockhash(), 16), block.sha256) def run_test(self): - # Setup the p2p connections and start up the network thread. + # Setup the p2p connections self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) self.segwit_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK|NODE_WITNESS) self.old_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK) - network_thread_start() - self.test_node.wait_for_verack() # We will need UTXOs to construct transactions in later tests. diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index 7c954cdca2..5b3fa0186a 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -47,9 +47,8 @@ class FeeFilterTest(BitcoinTestFramework): node1.generate(1) sync_blocks(self.nodes) - # Setup the p2p connections and start up the network thread. + # Setup the p2p connections self.nodes[0].add_p2p_connection(TestP2PConn()) - network_thread_start() self.nodes[0].p2p.wait_for_verack() # Test that invs are received for all txs at feerate of 20 sat/byte @@ -69,7 +68,7 @@ class FeeFilterTest(BitcoinTestFramework): # Change tx fee rate to 10 sat/byte and test they are no longer received node1.settxfee(Decimal("0.00010000")) [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)] - sync_mempools(self.nodes) # must be sure node 0 has received all txs + sync_mempools(self.nodes) # must be sure node 0 has received all txs # Send one transaction from node0 that should be received, so that we # we can sync the test on receipt (if node1's txs were relayed, they'd diff --git a/test/functional/p2p_fingerprint.py b/test/functional/p2p_fingerprint.py index 516ce8555b..61f9ec014b 100755 --- a/test/functional/p2p_fingerprint.py +++ b/test/functional/p2p_fingerprint.py @@ -18,7 +18,6 @@ from test_framework.mininode import ( msg_block, msg_getdata, msg_getheaders, - network_thread_start, wait_until, ) from test_framework.test_framework import BitcoinTestFramework @@ -76,8 +75,6 @@ class P2PFingerprintTest(BitcoinTestFramework): # last month but that have over a month's worth of work are also withheld. def run_test(self): node0 = self.nodes[0].add_p2p_connection(P2PInterface()) - - network_thread_start() node0.wait_for_verack() # Set node time to 60 days ago diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index e1f328ba77..c981968026 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -14,7 +14,7 @@ import copy from test_framework.blocktools import create_block, create_coinbase, create_transaction from test_framework.messages import COIN -from test_framework.mininode import network_thread_start, P2PDataStore +from test_framework.mininode import P2PDataStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -28,8 +28,6 @@ class InvalidBlockRequestTest(BitcoinTestFramework): # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node node.add_p2p_connection(P2PDataStore()) - - network_thread_start() node.p2p.wait_for_verack() best_block = node.getblock(node.getbestblockhash()) diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 69ce529ad6..a7a86f89fd 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -6,24 +6,46 @@ In this test we connect to one node over p2p, and test tx requests.""" from test_framework.blocktools import create_block, create_coinbase, create_transaction -from test_framework.messages import COIN -from test_framework.mininode import network_thread_start, P2PDataStore +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, +) +from test_framework.mininode import P2PDataStore from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + wait_until, +) -class InvalidTxRequestTest(BitcoinTestFramework): +class InvalidTxRequestTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [["-whitelist=127.0.0.1"]] + + def bootstrap_p2p(self, *, num_connections=1): + """Add a P2P connection to the node. + + Helper to connect and wait for version handshake.""" + for _ in range(num_connections): + self.nodes[0].add_p2p_connection(P2PDataStore()) + self.nodes[0].p2p.wait_for_verack() + + def reconnect_p2p(self, **kwargs): + """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(**kwargs) def run_test(self): - # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node - node.add_p2p_connection(P2PDataStore()) - network_thread_start() - node.p2p.wait_for_verack() + self.bootstrap_p2p() # Add one p2p connection to the node best_block = self.nodes[0].getbestblockhash() tip = int(best_block, 16) @@ -44,13 +66,82 @@ class InvalidTxRequestTest(BitcoinTestFramework): # b'\x64' is OP_NOTIF # Transaction will be rejected with code 16 (REJECT_INVALID) - tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN - 12000) - node.p2p.send_txs_and_test([tx1], node, success=False, reject_code=16, reject_reason=b'mandatory-script-verify-flag-failed (Invalid OP_IF construction)') + # and we get disconnected immediately + self.log.info('Test a transaction that is rejected') + tx1 = create_transaction(block1.vtx[0], 0, b'\x64' * 35, 50 * COIN - 12000) + node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True) + + # Make two p2p connections to provide the node with orphans + # * p2ps[0] will send valid orphan txs (one with low fee) + # * p2ps[1] will send an invalid orphan tx (and is later disconnected for that) + self.reconnect_p2p(num_connections=2) + + self.log.info('Test orphan transaction handling ... ') + # Create a root transaction that we withhold until all dependend transactions + # are sent out and in the orphan cache + SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51' + tx_withhold = CTransaction() + tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) + tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_withhold.calc_sha256() + + # Our first orphan tx with some outputs to create further orphan txs + tx_orphan_1 = CTransaction() + tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) + tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 + tx_orphan_1.calc_sha256() + + # A valid transaction with low fee + tx_orphan_2_no_fee = CTransaction() + tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) + tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + + # A valid transaction with sufficient fee + tx_orphan_2_valid = CTransaction() + tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) + tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_2_valid.calc_sha256() + + # An invalid transaction with negative fee + tx_orphan_2_invalid = CTransaction() + tx_orphan_2_invalid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) + tx_orphan_2_invalid.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + + self.log.info('Send the orphans ... ') + # Send valid orphan txs from p2ps[0] + node.p2p.send_txs_and_test([tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) + # Send invalid tx from p2ps[1] + node.p2ps[1].send_txs_and_test([tx_orphan_2_invalid], node, success=False) + + assert_equal(0, node.getmempoolinfo()['size']) # Mempool should be empty + assert_equal(2, len(node.getpeerinfo())) # p2ps[1] is still connected + + self.log.info('Send the withhold tx ... ') + node.p2p.send_txs_and_test([tx_withhold], node, success=True) + + # Transactions that should end up in the mempool + expected_mempool = { + t.hash + for t in [ + tx_withhold, # The transaction that is the root for all orphans + tx_orphan_1, # The orphan transaction that splits the coins + tx_orphan_2_valid, # The valid transaction (with sufficient fee) + ] + } + # Transactions that do not end up in the mempool + # tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx) + # tx_orphan_invaid, because it has negative fee (p2ps[1] is disconnected for relaying that tx) - # Verify valid transaction - tx1 = create_transaction(block1.vtx[0], 0, b'', 50 * COIN - 12000) - node.p2p.send_txs_and_test([tx1], node, success=True) + wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) # p2ps[1] is no longer connected + assert_equal(expected_mempool, set(node.getrawmempool())) + # restart node with sending BIP61 messages disabled, check that it disconnects without sending the reject message + self.log.info('Test a transaction that is rejected, with BIP61 disabled') + self.restart_node(0, ['-enablebip61=0','-persistmempool=0']) + self.reconnect_p2p(num_connections=1) + node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True) + # send_txs_and_test will have waited for disconnect, so we can safely check that no reject has been received + assert_equal(node.p2p.reject_code_received, None) if __name__ == '__main__': InvalidTxRequestTest().main() diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index 198dcc1490..ecb9a56fe1 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -103,8 +103,6 @@ class P2PLeakTest(BitcoinTestFramework): unsupported_service_bit5_node = self.nodes[0].add_p2p_connection(CLazyNode(), services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_5) unsupported_service_bit7_node = self.nodes[0].add_p2p_connection(CLazyNode(), services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_7) - network_thread_start() - wait_until(lambda: no_version_bannode.ever_connected, timeout=10, lock=mininode_lock) wait_until(lambda: no_version_idlenode.ever_connected, timeout=10, lock=mininode_lock) wait_until(lambda: no_verack_idlenode.version_received, timeout=10, lock=mininode_lock) @@ -118,17 +116,16 @@ class P2PLeakTest(BitcoinTestFramework): time.sleep(5) #This node should have been banned - assert no_version_bannode.state != "connected" + assert not no_version_bannode.is_connected # These nodes should have been disconnected - assert unsupported_service_bit5_node.state != "connected" - assert unsupported_service_bit7_node.state != "connected" + assert not unsupported_service_bit5_node.is_connected + assert not unsupported_service_bit7_node.is_connected self.nodes[0].disconnect_p2ps() - # Wait until all connections are closed and the network thread has terminated + # Wait until all connections are closed wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0) - network_thread_join() # Make sure no unexpected messages came in assert(no_version_bannode.unexpected_msg == False) @@ -143,11 +140,9 @@ class P2PLeakTest(BitcoinTestFramework): allowed_service_bit5_node = self.nodes[0].add_p2p_connection(P2PInterface(), services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_5) allowed_service_bit7_node = self.nodes[0].add_p2p_connection(P2PInterface(), services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_7) - # Network thread stopped when all previous P2PInterfaces disconnected. Restart it - network_thread_start() - wait_until(lambda: allowed_service_bit5_node.message_count["verack"], lock=mininode_lock) wait_until(lambda: allowed_service_bit7_node.message_count["verack"], lock=mininode_lock) + if __name__ == '__main__': P2PLeakTest().main() diff --git a/test/functional/p2p_mempool.py b/test/functional/p2p_mempool.py index e54843b26f..5a1fb60fb5 100755 --- a/test/functional/p2p_mempool.py +++ b/test/functional/p2p_mempool.py @@ -21,7 +21,6 @@ class P2PMempoolTests(BitcoinTestFramework): def run_test(self): # Add a p2p connection self.nodes[0].add_p2p_connection(P2PInterface()) - network_thread_start() self.nodes[0].p2p.wait_for_verack() #request mempool diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 301d8c181a..4a24e24daf 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -9,7 +9,7 @@ and that it responds to getdata requests for blocks correctly: - send a block within 288 + 2 of the tip - disconnect peers who request blocks older than that.""" from test_framework.messages import CInv, msg_getdata, msg_verack -from test_framework.mininode import NODE_BLOOM, NODE_NETWORK_LIMITED, NODE_WITNESS, P2PInterface, wait_until, mininode_lock, network_thread_start, network_thread_join +from test_framework.mininode import NODE_BLOOM, NODE_NETWORK_LIMITED, NODE_WITNESS, P2PInterface, wait_until, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, disconnect_nodes, connect_nodes_bi, sync_blocks @@ -48,7 +48,6 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) - network_thread_start() node.wait_for_verack() expected_services = NODE_BLOOM | NODE_WITNESS | NODE_NETWORK_LIMITED @@ -74,9 +73,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): self.log.info("Check local address relay, do a fresh connection.") self.nodes[0].disconnect_p2ps() - network_thread_join() node1 = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) - network_thread_start() node1.wait_for_verack() node1.send_message(msg_verack()) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 4fecd4ffee..52f6482d56 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -3,17 +3,86 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test segwit transactions and blocks on P2P network.""" +from binascii import hexlify +import math +import random +import struct +import time -from test_framework.mininode import * -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -from test_framework.script import * from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, get_witness_script, WITNESS_COMMITMENT_HEADER from test_framework.key import CECKey, CPubKey -import math -import time -import random -from binascii import hexlify +from test_framework.messages import ( + BIP125_SEQUENCE_NUMBER, + CBlock, + CBlockHeader, + CInv, + COutPoint, + CTransaction, + CTxIn, + CTxInWitness, + CTxOut, + CTxWitness, + MAX_BLOCK_BASE_SIZE, + MSG_WITNESS_FLAG, + NODE_NETWORK, + NODE_WITNESS, + msg_block, + msg_getdata, + msg_headers, + msg_inv, + msg_tx, + msg_witness_block, + msg_witness_tx, + ser_uint256, + ser_vector, + sha256, + uint256_from_str, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, + wait_until, +) +from test_framework.script import ( + CScript, + CScriptNum, + CScriptOp, + MAX_SCRIPT_ELEMENT_SIZE, + OP_0, + OP_1, + OP_16, + OP_2DROP, + OP_CHECKMULTISIG, + OP_CHECKSIG, + OP_DROP, + OP_DUP, + OP_ELSE, + OP_ENDIF, + OP_EQUAL, + OP_EQUALVERIFY, + OP_HASH160, + OP_IF, + OP_RETURN, + OP_TRUE, + SIGHASH_ALL, + SIGHASH_ANYONECANPAY, + SIGHASH_NONE, + SIGHASH_SINGLE, + SegwitVersion1SignatureHash, + SignatureHash, + hash160, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + bytes_to_hex_str, + connect_nodes, + disconnect_nodes, + get_bip9_status, + hex_str_to_bytes, + sync_blocks, + sync_mempools, +) # The versionbit bit used to signal activation of SegWit VB_WITNESS_BIT = 1 @@ -22,14 +91,32 @@ VB_TOP_BITS = 0x20000000 MAX_SIGOP_COST = 80000 +class UTXO(): + """Used to keep track of anyone-can-spend outputs that we can use in the tests.""" + def __init__(self, sha256, n, value): + self.sha256 = sha256 + self.n = n + self.nValue = value + +def get_p2pkh_script(pubkeyhash): + """Get the script associated with a P2PKH.""" + return CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)]) + +def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key): + """Add signature for a P2PK witness program.""" + tx_hash = SegwitVersion1SignatureHash(script, tx_to, in_idx, hashtype, value) + signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1') + tx_to.wit.vtxinwit[in_idx].scriptWitness.stack = [signature, script] + tx_to.rehash() -# Calculate the virtual size of a witness block: -# (base + witness/4) def get_virtual_size(witness_block): + """Calculate the virtual size of a witness block. + + Virtual size is base + witness/4.""" base_size = len(witness_block.serialize(with_witness=False)) total_size = len(witness_block.serialize(with_witness=True)) # the "+3" is so we round up - vsize = int((3*base_size + total_size + 3)/4) + vsize = int((3 * base_size + total_size + 3) / 4) return vsize def test_transaction_acceptance(rpc, p2p, tx, with_witness, accepted, reason=None): @@ -43,7 +130,7 @@ def test_transaction_acceptance(rpc, p2p, tx, with_witness, accepted, reason=Non p2p.send_message(tx_message) p2p.sync_with_ping() assert_equal(tx.hash in rpc.getrawmempool(), accepted) - if (reason != None and not accepted): + if (reason is not None and not accepted): # Check the rejection reason as well. with mininode_lock: assert_equal(p2p.last_message["reject"].reason, reason) @@ -59,7 +146,7 @@ def test_witness_block(rpc, p2p, block, accepted, with_witness=True, reason=None p2p.send_message(msg_block(block)) p2p.sync_with_ping() assert_equal(rpc.getbestblockhash() == block.hash, accepted) - if (reason != None and not accepted): + if (reason is not None and not accepted): # Check the rejection reason as well. with mininode_lock: assert_equal(p2p.last_message["reject"].reason, reason) @@ -73,18 +160,22 @@ class TestP2PConn(P2PInterface): for inv in message.inv: self.getdataset.add(inv.hash) - def announce_tx_and_wait_for_getdata(self, tx, timeout=60): + def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True): with mininode_lock: self.last_message.pop("getdata", None) self.send_message(msg_inv(inv=[CInv(1, tx.sha256)])) - self.wait_for_getdata(timeout) + if success: + self.wait_for_getdata(timeout) + else: + time.sleep(timeout) + assert not self.last_message.get("getdata") def announce_block_and_wait_for_getdata(self, block, use_header, timeout=60): with mininode_lock: self.last_message.pop("getdata", None) self.last_message.pop("getheaders", None) msg = msg_headers() - msg.headers = [ CBlockHeader(block) ] + msg.headers = [CBlockHeader(block)] if use_header: self.send_message(msg) else: @@ -100,25 +191,6 @@ class TestP2PConn(P2PInterface): self.wait_for_block(blockhash, timeout) return self.last_message["block"].block -# Used to keep track of anyone-can-spend outputs that we can use in the tests -class UTXO(): - def __init__(self, sha256, n, nValue): - self.sha256 = sha256 - self.n = n - self.nValue = nValue - -# Helper for getting the script associated with a P2PKH -def GetP2PKHScript(pubkeyhash): - return CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)]) - -# Add signature for a P2PK witness program. -def sign_P2PK_witness_input(script, txTo, inIdx, hashtype, value, key): - tx_hash = SegwitVersion1SignatureHash(script, txTo, inIdx, hashtype, value) - signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1') - txTo.wit.vtxinwit[inIdx].scriptWitness.stack = [signature, script] - txTo.rehash() - - class SegWitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -132,48 +204,121 @@ class SegWitTest(BitcoinTestFramework): connect_nodes(self.nodes[0], 2) self.sync_all() - ''' Helpers ''' - # Build a block on top of node0's tip. - def build_next_block(self, nVersion=4): + # Helper functions + + def build_next_block(self, version=4): + """Build a block on top of node0's tip.""" tip = self.nodes[0].getbestblockhash() height = self.nodes[0].getblockcount() + 1 block_time = self.nodes[0].getblockheader(tip)["mediantime"] + 1 block = create_block(int(tip, 16), create_coinbase(height), block_time) - block.nVersion = nVersion + block.version = version block.rehash() return block - # Adds list of transactions to block, adds witness commitment, then solves. def update_witness_block_with_transactions(self, block, tx_list, nonce=0): + """Add list of transactions to block, adds witness commitment, then solves.""" block.vtx.extend(tx_list) add_witness_commitment(block, nonce) block.solve() - return - ''' Individual tests ''' - def test_witness_services(self): - self.log.info("Verifying NODE_WITNESS service bit") - assert((self.test_node.nServices & NODE_WITNESS) != 0) + def run_test(self): + # Setup the p2p connections + # self.test_node sets NODE_WITNESS|NODE_NETWORK + self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK | NODE_WITNESS) + # self.old_node sets only NODE_NETWORK + self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK) + # self.std_node is for testing node1 (fRequireStandard=true) + self.std_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK | NODE_WITNESS) + + for conn in (self.test_node, self.old_node, self.std_node): + conn.wait_for_verack() + + assert self.test_node.nServices & NODE_WITNESS != 0 + + # Keep a place to store utxo's that can be used in later tests + self.utxo = [] + + # Segwit status 'defined' + self.segwit_status = 'defined' + + self.test_non_witness_transaction() + self.test_unnecessary_witness_before_segwit_activation() + self.test_v0_outputs_arent_spendable() + self.test_block_relay() + self.advance_to_segwit_started() + + # Segwit status 'started' + + self.test_getblocktemplate_before_lockin() + self.advance_to_segwit_lockin() + + # Segwit status 'locked_in' + + self.test_unnecessary_witness_before_segwit_activation() + self.test_witness_tx_relay_before_segwit_activation() + self.test_block_relay() + self.test_standardness_v0() + self.advance_to_segwit_active() + # Segwit status 'active' - # See if sending a regular transaction works, and create a utxo - # to use in later tests. + self.test_p2sh_witness() + self.test_witness_commitments() + self.test_block_malleability() + self.test_witness_block_size() + self.test_submit_block() + self.test_extra_witness_data() + self.test_max_witness_push_length() + self.test_max_witness_program_length() + self.test_witness_input_length() + self.test_block_relay() + self.test_tx_relay_after_segwit_activation() + self.test_standardness_v0() + self.test_segwit_versions() + self.test_premature_coinbase_witness_spend() + self.test_uncompressed_pubkey() + self.test_signature_version_1() + self.test_non_standard_witness_blinding() + self.test_non_standard_witness() + self.test_upgrade_after_activation() + self.test_witness_sigops() + + # Individual tests + + def subtest(func): # noqa: N805 + """Wraps the subtests for logging and state assertions.""" + def func_wrapper(self, *args, **kwargs): + self.log.info("Subtest: {} (Segwit status = {})".format(func.__name__, self.segwit_status)) + # Assert segwit status is as expected + assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], self.segwit_status) + func(self, *args, **kwargs) + # Each subtest should leave some utxos for the next subtest + assert self.utxo + sync_blocks(self.nodes) + # Assert segwit status is as expected at end of subtest + assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], self.segwit_status) + + return func_wrapper + + @subtest def test_non_witness_transaction(self): + """See if sending a regular transaction works, and create a utxo to use in later tests.""" # Mine a block with an anyone-can-spend coinbase, # let it mature, then try to spend it. - self.log.info("Testing non-witness transaction") - block = self.build_next_block(nVersion=1) + + block = self.build_next_block(version=1) block.solve() self.test_node.send_message(msg_block(block)) - self.test_node.sync_with_ping() # make sure the block was processed + self.test_node.sync_with_ping() # make sure the block was processed txid = block.vtx[0].sha256 - self.nodes[0].generate(99) # let the block mature + self.nodes[0].generate(99) # let the block mature # Create a transaction that spends the coinbase tx = CTransaction() tx.vin.append(CTxIn(COutPoint(txid, 0), b"")) - tx.vout.append(CTxOut(49*100000000, CScript([OP_TRUE]))) + tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx.calc_sha256() # Check that serializing it with or without witness is the same @@ -181,24 +326,19 @@ class SegWitTest(BitcoinTestFramework): assert_equal(msg_tx(tx).serialize(), msg_witness_tx(tx).serialize()) self.test_node.send_message(msg_witness_tx(tx)) - self.test_node.sync_with_ping() # make sure the tx was processed + self.test_node.sync_with_ping() # make sure the tx was processed assert(tx.hash in self.nodes[0].getrawmempool()) # Save this transaction for later - self.utxo.append(UTXO(tx.sha256, 0, 49*100000000)) + self.utxo.append(UTXO(tx.sha256, 0, 49 * 100000000)) self.nodes[0].generate(1) - - # Verify that blocks with witnesses are rejected before activation. + @subtest def test_unnecessary_witness_before_segwit_activation(self): - self.log.info("Testing behavior of unnecessary witnesses") - # For now, rely on earlier tests to have created at least one utxo for - # us to use - assert(len(self.utxo) > 0) - assert(get_bip9_status(self.nodes[0], 'segwit')['status'] != 'active') + """Verify that blocks with witnesses are rejected before activation.""" tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE]))) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, CScript([OP_TRUE]))) tx.wit.vtxinwit.append(CTxInWitness()) tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)])] @@ -208,15 +348,12 @@ class SegWitTest(BitcoinTestFramework): assert(tx.sha256 != tx.calc_sha256(with_witness=True)) # Construct a segwit-signaling block that includes the transaction. - block = self.build_next_block(nVersion=(VB_TOP_BITS|(1 << VB_WITNESS_BIT))) + block = self.build_next_block(version=(VB_TOP_BITS | (1 << VB_WITNESS_BIT))) self.update_witness_block_with_transactions(block, [tx]) # Sending witness data before activation is not allowed (anti-spam # rule). test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) - # TODO: fix synchronization so we can test reject reason - # Right now, bitcoind delays sending reject messages for blocks - # until the future, making synchronization here difficult. - #assert_equal(self.test_node.last_message["reject"].reason, "unexpected-witness") + wait_until(lambda: 'reject' in self.test_node.last_message and self.test_node.last_message["reject"].reason == b"unexpected-witness") # But it should not be permanently marked bad... # Resend without witness information. @@ -224,83 +361,136 @@ class SegWitTest(BitcoinTestFramework): self.test_node.sync_with_ping() assert_equal(self.nodes[0].getbestblockhash(), block.hash) - sync_blocks(self.nodes) + # Update our utxo list; we spent the first entry. + self.utxo.pop(0) + self.utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue)) - # Create a p2sh output -- this is so we can pass the standardness - # rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped - # in P2SH). - p2sh_program = CScript([OP_TRUE]) - p2sh_pubkey = hash160(p2sh_program) - scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + @subtest + def test_block_relay(self): + """Test that block requests to NODE_WITNESS peer are with MSG_WITNESS_FLAG. - # Now check that unnecessary witnesses can't be used to blind a node - # to a transaction, eg by violating standardness checks. - tx2 = CTransaction() - tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey)) - tx2.rehash() - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, False, True) - self.nodes[0].generate(1) - sync_blocks(self.nodes) + This is true regardless of segwit activation. + Also test that we don't ask for blocks from unupgraded peers.""" - # We'll add an unnecessary witness to this transaction that would cause - # it to be non-standard, to test that violating policy with a witness before - # segwit activation doesn't blind a node to a transaction. Transactions - # rejected for having a witness before segwit activation shouldn't be added - # to the rejection cache. - tx3 = CTransaction() - tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), CScript([p2sh_program]))) - tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptPubKey)) - tx3.wit.vtxinwit.append(CTxInWitness()) - tx3.wit.vtxinwit[0].scriptWitness.stack = [b'a'*400000] - tx3.rehash() - # Note that this should be rejected for the premature witness reason, - # rather than a policy check, since segwit hasn't activated yet. - test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx3, True, False, b'no-witness-yet') + blocktype = 2 | MSG_WITNESS_FLAG - # If we send without witness, it should be accepted. - test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx3, False, True) + # test_node has set NODE_WITNESS, so all getdata requests should be for + # witness blocks. + # Test announcing a block via inv results in a getdata, and that + # announcing a version 4 or random VB block with a header results in a getdata + block1 = self.build_next_block() + block1.solve() - # Now create a new anyone-can-spend utxo for the next test. - tx4 = CTransaction() - tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), CScript([p2sh_program]))) - tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, CScript([OP_TRUE]))) - tx4.rehash() - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, False, True) - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx4, False, True) + self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False) + assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) + test_witness_block(self.nodes[0].rpc, self.test_node, block1, True) - self.nodes[0].generate(1) - sync_blocks(self.nodes) + block2 = self.build_next_block(version=4) + block2.solve() - # Update our utxo list; we spent the first entry. - self.utxo.pop(0) - self.utxo.append(UTXO(tx4.sha256, 0, tx4.vout[0].nValue)) - - # ~6 months after segwit activation, the SCRIPT_VERIFY_WITNESS flag was - # backdated so that it applies to all blocks, going back to the genesis - # block. - # - # Consequently, version 0 witness outputs are never spendable without - # witness, and so can't be spent before segwit activation (the point at which - # blocks are permitted to contain witnesses). + self.test_node.announce_block_and_wait_for_getdata(block2, use_header=True) + assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) + test_witness_block(self.nodes[0].rpc, self.test_node, block2, True) + + block3 = self.build_next_block(version=(VB_TOP_BITS | (1 << 15))) + block3.solve() + self.test_node.announce_block_and_wait_for_getdata(block3, use_header=True) + assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) + test_witness_block(self.nodes[0].rpc, self.test_node, block3, True) + + # Check that we can getdata for witness blocks or regular blocks, + # and the right thing happens. + if self.segwit_status != 'active': + # Before activation, we should be able to request old blocks with + # or without witness, and they should be the same. + chain_height = self.nodes[0].getblockcount() + # Pick 10 random blocks on main chain, and verify that getdata's + # for MSG_BLOCK, MSG_WITNESS_BLOCK, and rpc getblock() are equal. + all_heights = list(range(chain_height + 1)) + random.shuffle(all_heights) + all_heights = all_heights[0:10] + for height in all_heights: + block_hash = self.nodes[0].getblockhash(height) + rpc_block = self.nodes[0].getblock(block_hash, False) + block_hash = int(block_hash, 16) + block = self.test_node.request_block(block_hash, 2) + wit_block = self.test_node.request_block(block_hash, 2 | MSG_WITNESS_FLAG) + assert_equal(block.serialize(True), wit_block.serialize(True)) + assert_equal(block.serialize(), hex_str_to_bytes(rpc_block)) + else: + # After activation, witness blocks and non-witness blocks should + # be different. Verify rpc getblock() returns witness blocks, while + # getdata respects the requested type. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, []) + # This gives us a witness commitment. + assert(len(block.vtx[0].wit.vtxinwit) == 1) + assert(len(block.vtx[0].wit.vtxinwit[0].scriptWitness.stack) == 1) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + # Now try to retrieve it... + rpc_block = self.nodes[0].getblock(block.hash, False) + non_wit_block = self.test_node.request_block(block.sha256, 2) + wit_block = self.test_node.request_block(block.sha256, 2 | MSG_WITNESS_FLAG) + assert_equal(wit_block.serialize(True), hex_str_to_bytes(rpc_block)) + assert_equal(wit_block.serialize(False), non_wit_block.serialize()) + assert_equal(wit_block.serialize(True), block.serialize(True)) + + # Test size, vsize, weight + rpc_details = self.nodes[0].getblock(block.hash, True) + assert_equal(rpc_details["size"], len(block.serialize(True))) + assert_equal(rpc_details["strippedsize"], len(block.serialize(False))) + weight = 3 * len(block.serialize(False)) + len(block.serialize(True)) + assert_equal(rpc_details["weight"], weight) + + # Upgraded node should not ask for blocks from unupgraded + block4 = self.build_next_block(version=4) + block4.solve() + self.old_node.getdataset = set() + + # Blocks can be requested via direct-fetch (immediately upon processing the announcement) + # or via parallel download (with an indeterminate delay from processing the announcement) + # so to test that a block is NOT requested, we could guess a time period to sleep for, + # and then check. We can avoid the sleep() by taking advantage of transaction getdata's + # being processed after block getdata's, and announce a transaction as well, + # and then check to see if that particular getdata has been received. + # Since 0.14, inv's will only be responded to with a getheaders, so send a header + # to announce this block. + msg = msg_headers() + msg.headers = [CBlockHeader(block4)] + self.old_node.send_message(msg) + self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0]) + assert(block4.sha256 not in self.old_node.getdataset) + + @subtest def test_v0_outputs_arent_spendable(self): - self.log.info("Testing that v0 witness program outputs aren't spendable before activation") + """Test that v0 outputs aren't spendable before segwit activation. + + ~6 months after segwit activation, the SCRIPT_VERIFY_WITNESS flag was + backdated so that it applies to all blocks, going back to the genesis + block. - assert len(self.utxo), "self.utxo is empty" + Consequently, version 0 witness outputs are never spendable without + witness, and so can't be spent before segwit activation (the point at which + blocks are permitted to contain witnesses).""" + + # node2 doesn't need to be connected for this test. + # (If it's connected, node0 may propogate an invalid block to it over + # compact blocks and the nodes would have inconsistent tips.) + disconnect_nodes(self.nodes[0], 2) # Create two outputs, a p2wsh and p2sh-p2wsh witness_program = CScript([OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) - p2sh_pubkey = hash160(scriptPubKey) - p2sh_scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + p2sh_pubkey = hash160(script_pubkey) + p2sh_script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) value = self.utxo[0].nValue // 3 tx = CTransaction() tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b'')] - tx.vout = [CTxOut(value, scriptPubKey), CTxOut(value, p2sh_scriptPubKey)] + tx.vout = [CTxOut(value, script_pubkey), CTxOut(value, p2sh_script_pubkey)] tx.vout.append(CTxOut(value, CScript([OP_TRUE]))) tx.rehash() txid = tx.sha256 @@ -310,7 +500,7 @@ class SegWitTest(BitcoinTestFramework): self.update_witness_block_with_transactions(block, [tx]) # Verify that segwit isn't activated. A block serialized with witness # should be rejected prior to activation. - test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=True, reason = b'unexpected-witness') + test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=True, reason=b'unexpected-witness') # Now send the block without witness. It should be accepted test_witness_block(self.nodes[0], self.test_node, block, accepted=True, with_witness=False) @@ -323,7 +513,7 @@ class SegWitTest(BitcoinTestFramework): p2wsh_tx.rehash() p2sh_p2wsh_tx = CTransaction() - p2sh_p2wsh_tx.vin = [CTxIn(COutPoint(txid, 1), CScript([scriptPubKey]))] + p2sh_p2wsh_tx.vin = [CTxIn(COutPoint(txid, 1), CScript([script_pubkey]))] p2sh_p2wsh_tx.vout = [CTxOut(value, CScript([OP_TRUE]))] p2sh_p2wsh_tx.wit.vtxinwit.append(CTxInWitness()) p2sh_p2wsh_tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] @@ -348,50 +538,282 @@ class SegWitTest(BitcoinTestFramework): # TODO: support multiple acceptable reject reasons. test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=False) + connect_nodes(self.nodes[0], 2) + self.utxo.pop(0) self.utxo.append(UTXO(txid, 2, value)) - # Mine enough blocks for segwit's vb state to be 'started'. + @subtest def advance_to_segwit_started(self): + """Mine enough blocks for segwit's vb state to be 'started'.""" height = self.nodes[0].getblockcount() # Will need to rewrite the tests here if we are past the first period assert(height < VB_PERIOD - 1) - # Genesis block is 'defined'. - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'defined') # Advance to end of period, status should now be 'started' - self.nodes[0].generate(VB_PERIOD-height-1) + self.nodes[0].generate(VB_PERIOD - height - 1) assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started') + self.segwit_status = 'started' + + @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 + # commitment, nor a version bit signalling segwit. + assert_equal(block_version & (1 << VB_WITNESS_BIT), 0) + assert('default_witness_commitment' not in gbt_results) + else: + # For segwit-aware nodes, check the version bit and the witness + # commitment are correct. + assert(block_version & (1 << VB_WITNESS_BIT) != 0) + assert('default_witness_commitment' in gbt_results) + witness_commitment = gbt_results['default_witness_commitment'] - # Mine enough blocks to lock in segwit, but don't activate. - # TODO: we could verify that lockin only happens at the right threshold of - # signalling blocks, rather than just at the right period boundary. + # Check that default_witness_commitment is present. + witness_root = CBlock.get_merkle_root([ser_uint256(0), + ser_uint256(txid)]) + 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.""" height = self.nodes[0].getblockcount() - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started') # Advance to end of period, and verify lock-in happens at the end - self.nodes[0].generate(VB_PERIOD-1) + self.nodes[0].generate(VB_PERIOD - 1) height = self.nodes[0].getblockcount() assert((height % VB_PERIOD) == VB_PERIOD - 2) assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started') self.nodes[0].generate(1) assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in') + self.segwit_status = 'locked_in' + + @subtest + def test_witness_tx_relay_before_segwit_activation(self): + + # Generate a transaction that doesn't require a witness, but send it + # with a witness. Should be rejected for premature-witness, but should + # not be added to recently rejected list. + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) + tx.wit.vtxinwit.append(CTxInWitness()) + tx.wit.vtxinwit[0].scriptWitness.stack = [b'a'] + tx.rehash() + + tx_hash = tx.sha256 + tx_value = tx.vout[0].nValue + + # Verify that if a peer doesn't set nServices to include NODE_WITNESS, + # the getdata is just for the non-witness portion. + self.old_node.announce_tx_and_wait_for_getdata(tx) + assert(self.old_node.last_message["getdata"].inv[0].type == 1) + + # Since we haven't delivered the tx yet, inv'ing the same tx from + # a witness transaction ought not result in a getdata. + self.test_node.announce_tx_and_wait_for_getdata(tx, timeout=2, success=False) + + # Delivering this transaction with witness should fail (no matter who + # its from) + assert_equal(len(self.nodes[0].getrawmempool()), 0) + assert_equal(len(self.nodes[1].getrawmempool()), 0) + test_transaction_acceptance(self.nodes[0].rpc, self.old_node, tx, with_witness=True, accepted=False) + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=True, accepted=False) + + # But eliminating the witness should fix it + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=False, accepted=True) + + # Cleanup: mine the first transaction and update utxo + self.nodes[0].generate(1) + assert_equal(len(self.nodes[0].getrawmempool()), 0) + + self.utxo.pop(0) + self.utxo.append(UTXO(tx_hash, 0, tx_value)) + + @subtest + def test_standardness_v0(self): + """Test V0 txout standardness. + + V0 segwit outputs and inputs are always standard. + V0 segwit inputs may only be mined after activation, but not before.""" + + witness_program = CScript([OP_TRUE]) + witness_hash = sha256(witness_program) + script_pubkey = CScript([OP_0, witness_hash]) + + p2sh_pubkey = hash160(witness_program) + p2sh_script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + + # First prepare a p2sh output (so that spending it will pass standardness) + p2sh_tx = CTransaction() + p2sh_tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")] + p2sh_tx.vout = [CTxOut(self.utxo[0].nValue - 1000, p2sh_script_pubkey)] + p2sh_tx.rehash() + + # Mine it on test_node to create the confirmed output. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, p2sh_tx, with_witness=True, accepted=True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + # Now test standardness of v0 P2WSH outputs. + # Start by creating a transaction with two outputs. + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] + tx.vout = [CTxOut(p2sh_tx.vout[0].nValue - 10000, script_pubkey)] + tx.vout.append(CTxOut(8000, script_pubkey)) # Might burn this later + tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER # Just to have the option to bump this tx from the mempool + tx.rehash() + + # This is always accepted, since the mempool policy is to consider segwit as always active + # and thus allow segwit outputs + test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx, with_witness=True, accepted=True) + + # Now create something that looks like a P2PKH output. This won't be spendable. + script_pubkey = CScript([OP_0, hash160(witness_hash)]) + tx2 = CTransaction() + # tx was accepted, so we spend the second output. + tx2.vin = [CTxIn(COutPoint(tx.sha256, 1), b"")] + tx2.vout = [CTxOut(7000, script_pubkey)] + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] + tx2.rehash() + + test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx2, with_witness=True, accepted=True) + + # Now update self.utxo for later tests. + tx3 = CTransaction() + # tx and tx2 were both accepted. Don't bother trying to reclaim the + # P2PKH output; just send tx's first output back to an anyone-can-spend. + sync_mempools([self.nodes[0], self.nodes[1]]) + tx3.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] + tx3.vout = [CTxOut(tx.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))] + tx3.wit.vtxinwit.append(CTxInWitness()) + tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] + tx3.rehash() + if self.segwit_status != 'active': + # Just check mempool acceptance, but don't add the transaction to the mempool, since witness is disallowed + # in blocks and the tx is impossible to mine right now. + assert_equal(self.nodes[0].testmempoolaccept([bytes_to_hex_str(tx3.serialize_with_witness())]), [{'txid': tx3.hash, 'allowed': True}]) + # Create the same output as tx3, but by replacing tx + tx3_out = tx3.vout[0] + tx3 = tx + tx3.vout = [tx3_out] + tx3.rehash() + assert_equal(self.nodes[0].testmempoolaccept([bytes_to_hex_str(tx3.serialize_with_witness())]), [{'txid': tx3.hash, 'allowed': True}]) + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, with_witness=True, accepted=True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + self.utxo.pop(0) + self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) + assert_equal(len(self.nodes[1].getrawmempool()), 0) - # Mine enough blocks to activate segwit. - # TODO: we could verify that activation only happens at the right threshold - # of signalling blocks, rather than just at the right period boundary. + @subtest def advance_to_segwit_active(self): - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in') + """Mine enough blocks to activate segwit.""" height = self.nodes[0].getblockcount() - self.nodes[0].generate(VB_PERIOD - (height%VB_PERIOD) - 2) + self.nodes[0].generate(VB_PERIOD - (height % VB_PERIOD) - 2) assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in') self.nodes[0].generate(1) assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'active') + self.segwit_status = 'active' + + @subtest + def test_p2sh_witness(self): + """Test P2SH wrapped witness programs.""" + # Prepare the p2sh-wrapped witness output + witness_program = CScript([OP_DROP, OP_TRUE]) + witness_hash = sha256(witness_program) + p2wsh_pubkey = CScript([OP_0, witness_hash]) + p2sh_witness_hash = hash160(p2wsh_pubkey) + script_pubkey = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) + script_sig = CScript([p2wsh_pubkey]) # a push of the redeem script - # This test can only be run after segwit has activated + # Fund the P2SH output + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) + tx.rehash() + + # Verify mempool acceptance and block validity + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=False, accepted=True) + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True, with_witness=True) + sync_blocks(self.nodes) + + # Now test attempts to spend the output. + spend_tx = CTransaction() + spend_tx.vin.append(CTxIn(COutPoint(tx.sha256, 0), script_sig)) + spend_tx.vout.append(CTxOut(tx.vout[0].nValue - 1000, CScript([OP_TRUE]))) + spend_tx.rehash() + + # This transaction should not be accepted into the mempool pre- or + # post-segwit. Mempool acceptance will use SCRIPT_VERIFY_WITNESS which + # will require a witness to spend a witness program regardless of + # segwit activation. Note that older bitcoind's that are not + # segwit-aware would also reject this for failing CLEANSTACK. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=False, accepted=False) + + # Try to put the witness script in the script_sig, should also fail. + spend_tx.vin[0].script_sig = CScript([p2wsh_pubkey, b'a']) + spend_tx.rehash() + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=False, accepted=False) + + # Now put the witness script in the witness, should succeed after + # segwit activates. + spend_tx.vin[0].scriptSig = script_sig + spend_tx.rehash() + spend_tx.wit.vtxinwit.append(CTxInWitness()) + spend_tx.wit.vtxinwit[0].scriptWitness.stack = [b'a', witness_program] + + # Verify mempool acceptance + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=True, accepted=True) + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [spend_tx]) + + # If we're after activation, then sending this with witnesses should be valid. + # This no longer works before activation, because SCRIPT_VERIFY_WITNESS + # is always set. + # TODO: rewrite this test to make clear that it only works after activation. + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + + # Update self.utxo + self.utxo.pop(0) + self.utxo.append(UTXO(spend_tx.sha256, 0, spend_tx.vout[0].nValue)) + + @subtest def test_witness_commitments(self): - self.log.info("Testing witness commitments") + """Test witness commitments. + + This test can only be run after segwit has activated.""" # First try a correct witness commitment. block = self.build_next_block() @@ -416,21 +838,20 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0].rpc, self.test_node, block_2, accepted=True) # Now test commitments with actual transactions - assert (len(self.utxo) > 0) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) # Let's construct a witness program witness_program = CScript([OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey)) + script_pubkey = CScript([OP_0, witness_hash]) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) tx.rehash() # tx2 will spend tx1, and send back to a regular anyone-can-spend address tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, witness_program)) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, witness_program)) tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] tx2.rehash() @@ -458,7 +879,7 @@ class SegWitTest(BitcoinTestFramework): block_3.vtx[0].rehash() block_3.hashMerkleRoot = block_3.calc_merkle_root() block_3.rehash() - assert(len(block_3.vtx[0].vout) == 4) # 3 OP_returns + assert(len(block_3.vtx[0].vout) == 4) # 3 OP_returns block_3.solve() test_witness_block(self.nodes[0].rpc, self.test_node, block_3, accepted=True) @@ -467,7 +888,7 @@ class SegWitTest(BitcoinTestFramework): block_4 = self.build_next_block() tx3 = CTransaction() tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b"")) - tx3.vout.append(CTxOut(tx.vout[0].nValue-1000, witness_program)) + tx3.vout.append(CTxOut(tx.vout[0].nValue - 1000, witness_program)) tx3.rehash() block_4.vtx.append(tx3) block_4.hashMerkleRoot = block_4.calc_merkle_root() @@ -478,9 +899,8 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - + @subtest def test_block_malleability(self): - self.log.info("Testing witness block malleability") # Make sure that a block that has too big a virtual size # because of a too-large coinbase witness is not permanently @@ -489,7 +909,7 @@ class SegWitTest(BitcoinTestFramework): add_witness_commitment(block) block.solve() - block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.append(b'a'*5000000) + block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.append(b'a' * 5000000) assert(get_virtual_size(block) > MAX_BLOCK_BASE_SIZE) # We can't send over the p2p network, because this is too big to relay @@ -512,16 +932,15 @@ class SegWitTest(BitcoinTestFramework): # Change the nonce -- should not cause the block to be permanently # failed - block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ ser_uint256(1) ] + block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(1)] test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Changing the witness reserved value doesn't change the block hash - block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ ser_uint256(0) ] + block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(0)] test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - + @subtest def test_witness_block_size(self): - self.log.info("Testing witness block size limit") # TODO: Test that non-witness carrying blocks can't exceed 1MB # Skipping this test for now; this is covered in p2p-fullblocktest.py @@ -534,21 +953,21 @@ class SegWitTest(BitcoinTestFramework): # The witness program will be a bunch of OP_2DROP's, followed by OP_TRUE. # This should give us plenty of room to tweak the spending tx's # virtual size. - NUM_DROPS = 200 # 201 max ops per script! + NUM_DROPS = 200 # 201 max ops per script! NUM_OUTPUTS = 50 - witness_program = CScript([OP_2DROP]*NUM_DROPS + [OP_TRUE]) + witness_program = CScript([OP_2DROP] * NUM_DROPS + [OP_TRUE]) witness_hash = uint256_from_str(sha256(witness_program)) - scriptPubKey = CScript([OP_0, ser_uint256(witness_hash)]) + script_pubkey = CScript([OP_0, ser_uint256(witness_hash)]) prevout = COutPoint(self.utxo[0].sha256, self.utxo[0].n) value = self.utxo[0].nValue parent_tx = CTransaction() parent_tx.vin.append(CTxIn(prevout, b"")) - child_value = int(value/NUM_OUTPUTS) + child_value = int(value / NUM_OUTPUTS) for i in range(NUM_OUTPUTS): - parent_tx.vout.append(CTxOut(child_value, scriptPubKey)) + parent_tx.vout.append(CTxOut(child_value, script_pubkey)) parent_tx.vout[0].nValue -= 50000 assert(parent_tx.vout[0].nValue > 0) parent_tx.rehash() @@ -559,17 +978,17 @@ class SegWitTest(BitcoinTestFramework): child_tx.vout = [CTxOut(value - 100000, CScript([OP_TRUE]))] for i in range(NUM_OUTPUTS): child_tx.wit.vtxinwit.append(CTxInWitness()) - child_tx.wit.vtxinwit[-1].scriptWitness.stack = [b'a'*195]*(2*NUM_DROPS) + [witness_program] + child_tx.wit.vtxinwit[-1].scriptWitness.stack = [b'a' * 195] * (2 * NUM_DROPS) + [witness_program] child_tx.rehash() self.update_witness_block_with_transactions(block, [parent_tx, child_tx]) vsize = get_virtual_size(block) - additional_bytes = (MAX_BLOCK_BASE_SIZE - vsize)*4 + additional_bytes = (MAX_BLOCK_BASE_SIZE - vsize) * 4 i = 0 while additional_bytes > 0: # Add some more bytes to each input until we hit MAX_BLOCK_BASE_SIZE+1 - extra_bytes = min(additional_bytes+1, 55) - block.vtx[-1].wit.vtxinwit[int(i/(2*NUM_DROPS))].scriptWitness.stack[i%(2*NUM_DROPS)] = b'a'*(195+extra_bytes) + extra_bytes = min(additional_bytes + 1, 55) + block.vtx[-1].wit.vtxinwit[int(i / (2 * NUM_DROPS))].scriptWitness.stack[i % (2 * NUM_DROPS)] = b'a' * (195 + extra_bytes) additional_bytes -= extra_bytes i += 1 @@ -580,13 +999,13 @@ class SegWitTest(BitcoinTestFramework): assert_equal(vsize, MAX_BLOCK_BASE_SIZE + 1) # Make sure that our test case would exceed the old max-network-message # limit - assert(len(block.serialize(True)) > 2*1024*1024) + assert(len(block.serialize(True)) > 2 * 1024 * 1024) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Now resize the second transaction to make the block fit. cur_length = len(block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0]) - block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0] = b'a'*(cur_length-1) + block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0] = b'a' * (cur_length - 1) block.vtx[0].vout.pop() add_witness_commitment(block) block.solve() @@ -598,16 +1017,15 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue)) - - # submitblock will try to add the nonce automatically, so that mining - # software doesn't need to worry about doing so itself. + @subtest def test_submit_block(self): + """Test that submitblock adds the nonce automatically when possible.""" block = self.build_next_block() # Try using a custom nonce and then don't supply it. # This shouldn't possibly work. add_witness_commitment(block, nonce=1) - block.vtx[0].wit = CTxWitness() # drop the nonce + block.vtx[0].wit = CTxWitness() # drop the nonce block.solve() self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True))) assert(self.nodes[0].getbestblockhash() != block.hash) @@ -635,24 +1053,21 @@ class SegWitTest(BitcoinTestFramework): # Tip should not advance! assert(self.nodes[0].getbestblockhash() != block_2.hash) - - # Consensus tests of extra witness data in a transaction. + @subtest def test_extra_witness_data(self): - self.log.info("Testing extra witness data in tx") - - assert(len(self.utxo) > 0) + """Test extra witness data in a transaction.""" block = self.build_next_block() witness_program = CScript([OP_DROP, OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) # First try extra witness data on a tx that doesn't require a witness tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-2000, scriptPubKey)) - tx.vout.append(CTxOut(1000, CScript([OP_TRUE]))) # non-witness output + tx.vout.append(CTxOut(self.utxo[0].nValue - 2000, script_pubkey)) + tx.vout.append(CTxOut(1000, CScript([OP_TRUE]))) # non-witness output tx.wit.vtxinwit.append(CTxInWitness()) tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([])] tx.rehash() @@ -673,12 +1088,12 @@ class SegWitTest(BitcoinTestFramework): # Now try extra witness/signature data on an input that DOES require a # witness tx2 = CTransaction() - tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) # witness output - tx2.vin.append(CTxIn(COutPoint(tx.sha256, 1), b"")) # non-witness + tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) # witness output + tx2.vin.append(CTxIn(COutPoint(tx.sha256, 1), b"")) # non-witness tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE]))) tx2.wit.vtxinwit.extend([CTxInWitness(), CTxInWitness()]) - tx2.wit.vtxinwit[0].scriptWitness.stack = [ CScript([CScriptNum(1)]), CScript([CScriptNum(1)]), witness_program ] - tx2.wit.vtxinwit[1].scriptWitness.stack = [ CScript([OP_TRUE]) ] + tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), CScript([CScriptNum(1)]), witness_program] + tx2.wit.vtxinwit[1].scriptWitness.stack = [CScript([OP_TRUE])] block = self.build_next_block() self.update_witness_block_with_transactions(block, [tx2]) @@ -711,37 +1126,34 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - + @subtest def test_max_witness_push_length(self): - ''' Should only allow up to 520 byte pushes in witness stack ''' - self.log.info("Testing maximum witness push size") - MAX_SCRIPT_ELEMENT_SIZE = 520 - assert(len(self.utxo)) + """Test that witness stack can only allow up to 520 byte pushes.""" block = self.build_next_block() witness_program = CScript([OP_DROP, OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey)) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) tx.rehash() tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, CScript([OP_TRUE]))) tx2.wit.vtxinwit.append(CTxInWitness()) # First try a 521-byte stack element - tx2.wit.vtxinwit[0].scriptWitness.stack = [ b'a'*(MAX_SCRIPT_ELEMENT_SIZE+1), witness_program ] + tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a' * (MAX_SCRIPT_ELEMENT_SIZE + 1), witness_program] tx2.rehash() self.update_witness_block_with_transactions(block, [tx, tx2]) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Now reduce the length of the stack element - tx2.wit.vtxinwit[0].scriptWitness.stack[0] = b'a'*(MAX_SCRIPT_ELEMENT_SIZE) + tx2.wit.vtxinwit[0].scriptWitness.stack[0] = b'a' * (MAX_SCRIPT_ELEMENT_SIZE) add_witness_commitment(block) block.solve() @@ -751,31 +1163,30 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) + @subtest def test_max_witness_program_length(self): - # Can create witness outputs that are long, but can't be greater than - # 10k bytes to successfully spend - self.log.info("Testing maximum witness program length") - assert(len(self.utxo)) + """Test that witness outputs greater than 10kB can't be spent.""" + MAX_PROGRAM_LENGTH = 10000 # This program is 19 max pushes (9937 bytes), then 64 more opcode-bytes. - long_witness_program = CScript([b'a'*520]*19 + [OP_DROP]*63 + [OP_TRUE]) - assert(len(long_witness_program) == MAX_PROGRAM_LENGTH+1) + long_witness_program = CScript([b'a' * 520] * 19 + [OP_DROP] * 63 + [OP_TRUE]) + assert(len(long_witness_program) == MAX_PROGRAM_LENGTH + 1) long_witness_hash = sha256(long_witness_program) - long_scriptPubKey = CScript([OP_0, long_witness_hash]) + long_script_pubkey = CScript([OP_0, long_witness_hash]) block = self.build_next_block() tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, long_scriptPubKey)) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, long_script_pubkey)) tx.rehash() tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, CScript([OP_TRUE]))) tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a']*44 + [long_witness_program] + tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a'] * 44 + [long_witness_program] tx2.rehash() self.update_witness_block_with_transactions(block, [tx, tx2]) @@ -783,15 +1194,15 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Try again with one less byte in the witness program - witness_program = CScript([b'a'*520]*19 + [OP_DROP]*62 + [OP_TRUE]) + witness_program = CScript([b'a' * 520] * 19 + [OP_DROP] * 62 + [OP_TRUE]) assert(len(witness_program) == MAX_PROGRAM_LENGTH) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) - tx.vout[0] = CTxOut(tx.vout[0].nValue, scriptPubKey) + tx.vout[0] = CTxOut(tx.vout[0].nValue, script_pubkey) tx.rehash() tx2.vin[0].prevout.hash = tx.sha256 - tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a']*43 + [witness_program] + tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a'] * 43 + [witness_program] tx2.rehash() block.vtx = [block.vtx[0]] self.update_witness_block_with_transactions(block, [tx, tx2]) @@ -800,22 +1211,20 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - + @subtest def test_witness_input_length(self): - ''' Ensure that vin length must match vtxinwit length ''' - self.log.info("Testing witness input length") - assert(len(self.utxo)) + """Test that vin length must match vtxinwit length.""" witness_program = CScript([OP_DROP, OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) # Create a transaction that splits our utxo into many outputs tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - nValue = self.utxo[0].nValue + value = self.utxo[0].nValue for i in range(10): - tx.vout.append(CTxOut(int(nValue/10), scriptPubKey)) + tx.vout.append(CTxOut(int(value / 10), script_pubkey)) tx.vout[0].nValue -= 1000 assert(tx.vout[0].nValue >= 0) @@ -847,7 +1256,7 @@ class SegWitTest(BitcoinTestFramework): tx2 = BrokenCTransaction() for i in range(10): tx2.vin.append(CTxIn(COutPoint(tx.sha256, i), b"")) - tx2.vout.append(CTxOut(nValue-3000, CScript([OP_TRUE]))) + tx2.vout.append(CTxOut(value - 3000, CScript([OP_TRUE]))) # First try using a too long vtxinwit for i in range(11): @@ -869,7 +1278,7 @@ class SegWitTest(BitcoinTestFramework): # Now make one of the intermediate witnesses be incorrect tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[-1].scriptWitness.stack = [b'a', witness_program] - tx2.wit.vtxinwit[5].scriptWitness.stack = [ witness_program ] + tx2.wit.vtxinwit[5].scriptWitness.stack = [witness_program] block.vtx = [block.vtx[0]] self.update_witness_block_with_transactions(block, [tx2]) @@ -884,70 +1293,23 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) + @subtest + def test_tx_relay_after_segwit_activation(self): + """Test transaction relay after segwit activation. - def test_witness_tx_relay_before_segwit_activation(self): - self.log.info("Testing relay of witness transactions") - # Generate a transaction that doesn't require a witness, but send it - # with a witness. Should be rejected for premature-witness, but should - # not be added to recently rejected list. - assert(len(self.utxo)) - tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE]))) - tx.wit.vtxinwit.append(CTxInWitness()) - tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a' ] - tx.rehash() - - tx_hash = tx.sha256 - tx_value = tx.vout[0].nValue - - # Verify that if a peer doesn't set nServices to include NODE_WITNESS, - # the getdata is just for the non-witness portion. - self.old_node.announce_tx_and_wait_for_getdata(tx) - assert(self.old_node.last_message["getdata"].inv[0].type == 1) - - # Since we haven't delivered the tx yet, inv'ing the same tx from - # a witness transaction ought not result in a getdata. - try: - self.test_node.announce_tx_and_wait_for_getdata(tx, timeout=2) - self.log.error("Error: duplicate tx getdata!") - assert(False) - except AssertionError: - pass - - # Delivering this transaction with witness should fail (no matter who - # its from) - assert_equal(len(self.nodes[0].getrawmempool()), 0) - assert_equal(len(self.nodes[1].getrawmempool()), 0) - test_transaction_acceptance(self.nodes[0].rpc, self.old_node, tx, with_witness=True, accepted=False) - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=True, accepted=False) - - # But eliminating the witness should fix it - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=False, accepted=True) - - # Cleanup: mine the first transaction and update utxo - self.nodes[0].generate(1) - assert_equal(len(self.nodes[0].getrawmempool()), 0) - - self.utxo.pop(0) - self.utxo.append(UTXO(tx_hash, 0, tx_value)) - + After segwit activates, verify that mempool: + - rejects transactions with unnecessary/extra witnesses + - accepts transactions with valid witnesses + and that witness transactions are relayed to non-upgraded peers.""" - # After segwit activates, verify that mempool: - # - rejects transactions with unnecessary/extra witnesses - # - accepts transactions with valid witnesses - # and that witness transactions are relayed to non-upgraded peers. - def test_tx_relay_after_segwit_activation(self): - self.log.info("Testing relay of witness transactions") # Generate a transaction that doesn't require a witness, but send it # with a witness. Should be rejected because we can't use a witness # when spending a non-witness output. - assert(len(self.utxo)) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE]))) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx.wit.vtxinwit.append(CTxInWitness()) - tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a' ] + tx.wit.vtxinwit[0].scriptWitness.stack = [b'a'] tx.rehash() tx_hash = tx.sha256 @@ -964,10 +1326,10 @@ class SegWitTest(BitcoinTestFramework): # Now try to add extra witness data to a valid witness tx. witness_program = CScript([OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx_hash, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey)) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_pubkey)) tx2.rehash() tx3 = CTransaction() @@ -977,8 +1339,8 @@ class SegWitTest(BitcoinTestFramework): # Add too-large for IsStandard witness and check that it does not enter reject filter p2sh_program = CScript([OP_TRUE]) p2sh_pubkey = hash160(p2sh_program) - witness_program2 = CScript([b'a'*400000]) - tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]))) + witness_program2 = CScript([b'a' * 400000]) + tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]))) tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program2] tx3.rehash() @@ -989,18 +1351,18 @@ class SegWitTest(BitcoinTestFramework): test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx3, True, False, b'tx-size') # Remove witness stuffing, instead add extra witness push on stack - tx3.vout[0] = CTxOut(tx2.vout[0].nValue-1000, CScript([OP_TRUE])) - tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), witness_program ] + tx3.vout[0] = CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])) + tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), witness_program] tx3.rehash() test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, with_witness=True, accepted=True) test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, with_witness=True, accepted=False) # Get rid of the extra witness, and verify acceptance. - tx3.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ] + tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] # Also check that old_node gets a tx announcement, even though this is # a witness transaction. - self.old_node.wait_for_inv([CInv(1, tx2.sha256)]) # wait until tx2 was inv'ed + self.old_node.wait_for_inv([CInv(1, tx2.sha256)]) # wait until tx2 was inv'ed test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, with_witness=True, accepted=True) self.old_node.wait_for_inv([CInv(1, tx3.sha256)]) @@ -1009,7 +1371,7 @@ class SegWitTest(BitcoinTestFramework): raw_tx = self.nodes[0].getrawtransaction(tx3.hash, 1) assert_equal(int(raw_tx["hash"], 16), tx3.calc_sha256(True)) assert_equal(raw_tx["size"], len(tx3.serialize_with_witness())) - weight = len(tx3.serialize_with_witness()) + 3*len(tx3.serialize_without_witness()) + weight = len(tx3.serialize_with_witness()) + 3 * len(tx3.serialize_without_witness()) vsize = math.ceil(weight / 4) assert_equal(raw_tx["vsize"], vsize) assert_equal(raw_tx["weight"], weight) @@ -1019,240 +1381,68 @@ class SegWitTest(BitcoinTestFramework): # Cleanup: mine the transactions and update utxo for next test self.nodes[0].generate(1) - assert_equal(len(self.nodes[0].getrawmempool()), 0) + assert_equal(len(self.nodes[0].getrawmempool()), 0) self.utxo.pop(0) self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) + @subtest + def test_segwit_versions(self): + """Test validity of future segwit version transactions. - # Test that block requests to NODE_WITNESS peer are with MSG_WITNESS_FLAG - # This is true regardless of segwit activation. - # Also test that we don't ask for blocks from unupgraded peers - def test_block_relay(self, segwit_activated): - self.log.info("Testing block relay") - - blocktype = 2|MSG_WITNESS_FLAG - - # test_node has set NODE_WITNESS, so all getdata requests should be for - # witness blocks. - # Test announcing a block via inv results in a getdata, and that - # announcing a version 4 or random VB block with a header results in a getdata - block1 = self.build_next_block() - block1.solve() - - self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False) - assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) - test_witness_block(self.nodes[0].rpc, self.test_node, block1, True) - - block2 = self.build_next_block(nVersion=4) - block2.solve() - - self.test_node.announce_block_and_wait_for_getdata(block2, use_header=True) - assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) - test_witness_block(self.nodes[0].rpc, self.test_node, block2, True) - - block3 = self.build_next_block(nVersion=(VB_TOP_BITS | (1<<15))) - block3.solve() - self.test_node.announce_block_and_wait_for_getdata(block3, use_header=True) - assert(self.test_node.last_message["getdata"].inv[0].type == blocktype) - test_witness_block(self.nodes[0].rpc, self.test_node, block3, True) - - # Check that we can getdata for witness blocks or regular blocks, - # and the right thing happens. - if segwit_activated == False: - # Before activation, we should be able to request old blocks with - # or without witness, and they should be the same. - chain_height = self.nodes[0].getblockcount() - # Pick 10 random blocks on main chain, and verify that getdata's - # for MSG_BLOCK, MSG_WITNESS_BLOCK, and rpc getblock() are equal. - all_heights = list(range(chain_height+1)) - random.shuffle(all_heights) - all_heights = all_heights[0:10] - for height in all_heights: - block_hash = self.nodes[0].getblockhash(height) - rpc_block = self.nodes[0].getblock(block_hash, False) - block_hash = int(block_hash, 16) - block = self.test_node.request_block(block_hash, 2) - wit_block = self.test_node.request_block(block_hash, 2|MSG_WITNESS_FLAG) - assert_equal(block.serialize(True), wit_block.serialize(True)) - assert_equal(block.serialize(), hex_str_to_bytes(rpc_block)) - else: - # After activation, witness blocks and non-witness blocks should - # be different. Verify rpc getblock() returns witness blocks, while - # getdata respects the requested type. - block = self.build_next_block() - self.update_witness_block_with_transactions(block, []) - # This gives us a witness commitment. - assert(len(block.vtx[0].wit.vtxinwit) == 1) - assert(len(block.vtx[0].wit.vtxinwit[0].scriptWitness.stack) == 1) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - # Now try to retrieve it... - rpc_block = self.nodes[0].getblock(block.hash, False) - non_wit_block = self.test_node.request_block(block.sha256, 2) - wit_block = self.test_node.request_block(block.sha256, 2|MSG_WITNESS_FLAG) - assert_equal(wit_block.serialize(True), hex_str_to_bytes(rpc_block)) - assert_equal(wit_block.serialize(False), non_wit_block.serialize()) - assert_equal(wit_block.serialize(True), block.serialize(True)) - - # Test size, vsize, weight - rpc_details = self.nodes[0].getblock(block.hash, True) - assert_equal(rpc_details["size"], len(block.serialize(True))) - assert_equal(rpc_details["strippedsize"], len(block.serialize(False))) - weight = 3*len(block.serialize(False)) + len(block.serialize(True)) - assert_equal(rpc_details["weight"], weight) - - # Upgraded node should not ask for blocks from unupgraded - block4 = self.build_next_block(nVersion=4) - block4.solve() - self.old_node.getdataset = set() - - # Blocks can be requested via direct-fetch (immediately upon processing the announcement) - # or via parallel download (with an indeterminate delay from processing the announcement) - # so to test that a block is NOT requested, we could guess a time period to sleep for, - # and then check. We can avoid the sleep() by taking advantage of transaction getdata's - # being processed after block getdata's, and announce a transaction as well, - # and then check to see if that particular getdata has been received. - # Since 0.14, inv's will only be responded to with a getheaders, so send a header - # to announce this block. - msg = msg_headers() - msg.headers = [ CBlockHeader(block4) ] - self.old_node.send_message(msg) - self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0]) - assert(block4.sha256 not in self.old_node.getdataset) - - # V0 segwit outputs should be standard after activation, but not before. - def test_standardness_v0(self, segwit_activated): - self.log.info("Testing standardness of v0 outputs (%s activation)" % ("after" if segwit_activated else "before")) - assert(len(self.utxo)) - - witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) - - p2sh_pubkey = hash160(witness_program) - p2sh_scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) - - # First prepare a p2sh output (so that spending it will pass standardness) - p2sh_tx = CTransaction() - p2sh_tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")] - p2sh_tx.vout = [CTxOut(self.utxo[0].nValue-1000, p2sh_scriptPubKey)] - p2sh_tx.rehash() - - # Mine it on test_node to create the confirmed output. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, p2sh_tx, with_witness=True, accepted=True) - self.nodes[0].generate(1) - sync_blocks(self.nodes) - - # Now test standardness of v0 P2WSH outputs. - # Start by creating a transaction with two outputs. - tx = CTransaction() - tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] - tx.vout = [CTxOut(p2sh_tx.vout[0].nValue-10000, scriptPubKey)] - tx.vout.append(CTxOut(8000, scriptPubKey)) # Might burn this later - tx.rehash() - - test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx, with_witness=True, accepted=segwit_activated) - - # Now create something that looks like a P2PKH output. This won't be spendable. - scriptPubKey = CScript([OP_0, hash160(witness_hash)]) - tx2 = CTransaction() - if segwit_activated: - # if tx was accepted, then we spend the second output. - tx2.vin = [CTxIn(COutPoint(tx.sha256, 1), b"")] - tx2.vout = [CTxOut(7000, scriptPubKey)] - tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] - else: - # if tx wasn't accepted, we just re-spend the p2sh output we started with. - tx2.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] - tx2.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, scriptPubKey)] - tx2.rehash() - - test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx2, with_witness=True, accepted=segwit_activated) - - # Now update self.utxo for later tests. - tx3 = CTransaction() - if segwit_activated: - # tx and tx2 were both accepted. Don't bother trying to reclaim the - # P2PKH output; just send tx's first output back to an anyone-can-spend. - sync_mempools([self.nodes[0], self.nodes[1]]) - tx3.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] - tx3.vout = [CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))] - tx3.wit.vtxinwit.append(CTxInWitness()) - tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] - tx3.rehash() - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, with_witness=True, accepted=True) - else: - # tx and tx2 didn't go anywhere; just clean up the p2sh_tx output. - tx3.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] - tx3.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, witness_program)] - tx3.rehash() - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, with_witness=True, accepted=True) - - self.nodes[0].generate(1) - sync_blocks(self.nodes) - self.utxo.pop(0) - self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - assert_equal(len(self.nodes[1].getrawmempool()), 0) - + Future segwit version transactions are non-standard, but valid in blocks. + Can run this before and after segwit activation.""" - # Verify that future segwit upgraded transactions are non-standard, - # but valid in blocks. Can run this before and after segwit activation. - def test_segwit_versions(self): - self.log.info("Testing standardness/consensus for segwit versions (0-16)") - assert(len(self.utxo)) - NUM_TESTS = 17 # will test OP_0, OP1, ..., OP_16 - if (len(self.utxo) < NUM_TESTS): + NUM_SEGWIT_VERSIONS = 17 # will test OP_0, OP1, ..., OP_16 + if len(self.utxo) < NUM_SEGWIT_VERSIONS: tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - split_value = (self.utxo[0].nValue - 4000) // NUM_TESTS - for i in range(NUM_TESTS): + split_value = (self.utxo[0].nValue - 4000) // NUM_SEGWIT_VERSIONS + for i in range(NUM_SEGWIT_VERSIONS): tx.vout.append(CTxOut(split_value, CScript([OP_TRUE]))) tx.rehash() block = self.build_next_block() self.update_witness_block_with_transactions(block, [tx]) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) self.utxo.pop(0) - for i in range(NUM_TESTS): + for i in range(NUM_SEGWIT_VERSIONS): self.utxo.append(UTXO(tx.sha256, i, split_value)) sync_blocks(self.nodes) temp_utxo = [] tx = CTransaction() - count = 0 witness_program = CScript([OP_TRUE]) witness_hash = sha256(witness_program) assert_equal(len(self.nodes[1].getrawmempool()), 0) - for version in list(range(OP_1, OP_16+1)) + [OP_0]: - count += 1 - # First try to spend to a future version segwit scriptPubKey. - scriptPubKey = CScript([CScriptOp(version), witness_hash]) + for version in list(range(OP_1, OP_16 + 1)) + [OP_0]: + # First try to spend to a future version segwit script_pubkey. + script_pubkey = CScript([CScriptOp(version), witness_hash]) tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")] - tx.vout = [CTxOut(self.utxo[0].nValue-1000, scriptPubKey)] + tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)] tx.rehash() test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx, with_witness=True, accepted=False) test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=True, accepted=True) self.utxo.pop(0) temp_utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue)) - self.nodes[0].generate(1) # Mine all the transactions + self.nodes[0].generate(1) # Mine all the transactions sync_blocks(self.nodes) assert(len(self.nodes[0].getrawmempool()) == 0) # Finally, verify that version 0 -> version 1 transactions # are non-standard - scriptPubKey = CScript([CScriptOp(OP_1), witness_hash]) + script_pubkey = CScript([CScriptOp(OP_1), witness_hash]) tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] - tx2.vout = [CTxOut(tx.vout[0].nValue-1000, scriptPubKey)] + tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)] tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ] + tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] tx2.rehash() # Gets accepted to test_node, because standardness of outputs isn't # checked with fRequireStandard test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, with_witness=True, accepted=True) test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx2, with_witness=True, accepted=False) - temp_utxo.pop() # last entry in temp_utxo was the output we just spent + temp_utxo.pop() # last entry in temp_utxo was the output we just spent temp_utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) # Spend everything in temp_utxo back to an OP_TRUE output. @@ -1281,15 +1471,15 @@ class SegWitTest(BitcoinTestFramework): # Add utxo to our list self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - + @subtest def test_premature_coinbase_witness_spend(self): - self.log.info("Testing premature coinbase witness spend") + block = self.build_next_block() # Change the output of the block to be a witness output. witness_program = CScript([OP_TRUE]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) - block.vtx[0].vout[0].scriptPubKey = scriptPubKey + script_pubkey = CScript([OP_0, witness_hash]) + block.vtx[0].vout[0].scriptPubKey = script_pubkey # This next line will rehash the coinbase and update the merkle # root, and solve. self.update_witness_block_with_transactions(block, []) @@ -1299,7 +1489,7 @@ class SegWitTest(BitcoinTestFramework): spend_tx.vin = [CTxIn(COutPoint(block.vtx[0].sha256, 0), b"")] spend_tx.vout = [CTxOut(block.vtx[0].vout[0].nValue, witness_program)] spend_tx.wit.vtxinwit.append(CTxInWitness()) - spend_tx.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ] + spend_tx.wit.vtxinwit[0].scriptWitness.stack = [witness_program] spend_tx.rehash() # Now test a premature spend. @@ -1316,22 +1506,127 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0].rpc, self.test_node, block2, accepted=True) sync_blocks(self.nodes) + @subtest + def test_uncompressed_pubkey(self): + """Test uncompressed pubkey validity in segwit transactions. + + Uncompressed pubkeys are no longer supported in default relay policy, + but (for now) are still valid in blocks.""" + + # Segwit transactions using uncompressed pubkeys are not accepted + # under default policy, but should still pass consensus. + key = CECKey() + key.set_secretbytes(b"9") + key.set_compressed(False) + pubkey = CPubKey(key.get_pubkey()) + assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey + + utxo = self.utxo.pop(0) + + # Test 1: P2WPKH + # First create a P2WPKH output that uses an uncompressed pubkey + pubkeyhash = hash160(pubkey) + script_pkh = CScript([OP_0, pubkeyhash]) + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(utxo.sha256, utxo.n), b"")) + tx.vout.append(CTxOut(utxo.nValue - 1000, script_pkh)) + tx.rehash() + + # Confirm it in a block. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + + # Now try to spend it. Send it to a P2WSH output, which we'll + # use in the next test. + witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) + witness_hash = sha256(witness_program) + script_wsh = CScript([OP_0, witness_hash]) + tx2 = CTransaction() + tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_wsh)) + script = get_p2pkh_script(pubkeyhash) + sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) + signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey] + tx2.rehash() + + # Should fail policy test. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + # But passes consensus. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx2]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + + # Test 2: P2WSH + # Try to spend the P2WSH output created in last test. + # Send it to a P2SH(P2WSH) output, which we'll use in the next test. + p2sh_witness_hash = hash160(script_wsh) + script_p2sh = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) + script_sig = CScript([script_wsh]) + + tx3 = CTransaction() + tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b"")) + tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, script_p2sh)) + tx3.wit.vtxinwit.append(CTxInWitness()) + sign_p2pk_witness_input(witness_program, tx3, 0, SIGHASH_ALL, tx2.vout[0].nValue, key) + + # Should fail policy test. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + # But passes consensus. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx3]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + + # Test 3: P2SH(P2WSH) + # Try to spend the P2SH output created in the last test. + # Send it to a P2PKH output, which we'll use in the next test. + script_pubkey = get_p2pkh_script(pubkeyhash) + tx4 = CTransaction() + tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), script_sig)) + tx4.vout.append(CTxOut(tx3.vout[0].nValue - 1000, script_pubkey)) + tx4.wit.vtxinwit.append(CTxInWitness()) + sign_p2pk_witness_input(witness_program, tx4, 0, SIGHASH_ALL, tx3.vout[0].nValue, key) + + # Should fail policy test. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx4, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx4]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + + # Test 4: Uncompressed pubkeys should still be valid in non-segwit + # transactions. + tx5 = CTransaction() + tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) + tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE]))) + (sig_hash, err) = SignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) + signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + tx5.vin[0].scriptSig = CScript([signature, pubkey]) + tx5.rehash() + # Should pass policy and consensus. + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx5, True, True) + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx5]) + test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + self.utxo.append(UTXO(tx5.sha256, 0, tx5.vout[0].nValue)) + + @subtest def test_signature_version_1(self): - self.log.info("Testing segwit signature hash version 1") + key = CECKey() key.set_secretbytes(b"9") pubkey = CPubKey(key.get_pubkey()) witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) + script_pubkey = CScript([OP_0, witness_hash]) # First create a witness output for use in the tests. - assert(len(self.utxo)) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey)) + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) tx.rehash() test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=True, accepted=True) @@ -1344,27 +1639,27 @@ class SegWitTest(BitcoinTestFramework): # Test each hashtype prev_utxo = UTXO(tx.sha256, 0, tx.vout[0].nValue) - for sigflag in [ 0, SIGHASH_ANYONECANPAY ]: + for sigflag in [0, SIGHASH_ANYONECANPAY]: for hashtype in [SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE]: hashtype |= sigflag block = self.build_next_block() tx = CTransaction() tx.vin.append(CTxIn(COutPoint(prev_utxo.sha256, prev_utxo.n), b"")) - tx.vout.append(CTxOut(prev_utxo.nValue - 1000, scriptPubKey)) + tx.vout.append(CTxOut(prev_utxo.nValue - 1000, script_pubkey)) tx.wit.vtxinwit.append(CTxInWitness()) # Too-large input value - sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue+1, key) + sign_p2pk_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue + 1, key) self.update_witness_block_with_transactions(block, [tx]) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Too-small input value - sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue-1, key) - block.vtx.pop() # remove last tx + sign_p2pk_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue - 1, key) + block.vtx.pop() # remove last tx self.update_witness_block_with_transactions(block, [tx]) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=False) # Now try correct value - sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue, key) + sign_p2pk_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue, key) block.vtx.pop() self.update_witness_block_with_transactions(block, [tx]) test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) @@ -1374,19 +1669,19 @@ class SegWitTest(BitcoinTestFramework): # Test combinations of signature hashes. # Split the utxo into a lot of outputs. # Randomly choose up to 10 to spend, sign with different hashtypes, and - # output to a random number of outputs. Repeat NUM_TESTS times. + # output to a random number of outputs. Repeat NUM_SIGHASH_TESTS times. # Ensure that we've tested a situation where we use SIGHASH_SINGLE with # an input index > number of outputs. - NUM_TESTS = 500 + NUM_SIGHASH_TESTS = 500 temp_utxos = [] tx = CTransaction() tx.vin.append(CTxIn(COutPoint(prev_utxo.sha256, prev_utxo.n), b"")) - split_value = prev_utxo.nValue // NUM_TESTS - for i in range(NUM_TESTS): - tx.vout.append(CTxOut(split_value, scriptPubKey)) + split_value = prev_utxo.nValue // NUM_SIGHASH_TESTS + for i in range(NUM_SIGHASH_TESTS): + tx.vout.append(CTxOut(split_value, script_pubkey)) tx.wit.vtxinwit.append(CTxInWitness()) - sign_P2PK_witness_input(witness_program, tx, 0, SIGHASH_ALL, prev_utxo.nValue, key) - for i in range(NUM_TESTS): + sign_p2pk_witness_input(witness_program, tx, 0, SIGHASH_ALL, prev_utxo.nValue, key) + for i in range(NUM_SIGHASH_TESTS): temp_utxos.append(UTXO(tx.sha256, i, split_value)) block = self.build_next_block() @@ -1395,7 +1690,7 @@ class SegWitTest(BitcoinTestFramework): block = self.build_next_block() used_sighash_single_out_of_bounds = False - for i in range(NUM_TESTS): + for i in range(NUM_SIGHASH_TESTS): # Ping regularly to keep the connection alive if (not i % 100): self.test_node.sync_with_ping() @@ -1413,14 +1708,14 @@ class SegWitTest(BitcoinTestFramework): total_value += temp_utxos[i].nValue split_value = total_value // num_outputs for i in range(num_outputs): - tx.vout.append(CTxOut(split_value, scriptPubKey)) + tx.vout.append(CTxOut(split_value, script_pubkey)) for i in range(num_inputs): # Now try to sign each input, using a random hashtype. anyonecanpay = 0 if random.randint(0, 1): anyonecanpay = SIGHASH_ANYONECANPAY hashtype = random.randint(1, 3) | anyonecanpay - sign_P2PK_witness_input(witness_program, tx, i, hashtype, temp_utxos[i].nValue, key) + sign_p2pk_witness_input(witness_program, tx, i, hashtype, temp_utxos[i].nValue, key) if (hashtype == SIGHASH_SINGLE and i >= num_outputs): used_sighash_single_out_of_bounds = True tx.rehash() @@ -1445,19 +1740,19 @@ class SegWitTest(BitcoinTestFramework): # Now test witness version 0 P2PKH transactions pubkeyhash = hash160(pubkey) - scriptPKH = CScript([OP_0, pubkeyhash]) + script_pkh = CScript([OP_0, pubkeyhash]) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(temp_utxos[0].sha256, temp_utxos[0].n), b"")) - tx.vout.append(CTxOut(temp_utxos[0].nValue, scriptPKH)) + tx.vout.append(CTxOut(temp_utxos[0].nValue, script_pkh)) tx.wit.vtxinwit.append(CTxInWitness()) - sign_P2PK_witness_input(witness_program, tx, 0, SIGHASH_ALL, temp_utxos[0].nValue, key) + sign_p2pk_witness_input(witness_program, tx, 0, SIGHASH_ALL, temp_utxos[0].nValue, key) tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE]))) - script = GetP2PKHScript(pubkeyhash) + script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL # Check that we can't have a scriptSig tx2.vin[0].scriptSig = CScript([signature, pubkey]) @@ -1490,7 +1785,7 @@ class SegWitTest(BitcoinTestFramework): # the signatures as we go. tx.vin.append(CTxIn(COutPoint(i.sha256, i.n), b"")) tx.wit.vtxinwit.append(CTxInWitness()) - sign_P2PK_witness_input(witness_program, tx, index, SIGHASH_ALL|SIGHASH_ANYONECANPAY, i.nValue, key) + sign_p2pk_witness_input(witness_program, tx, index, SIGHASH_ALL | SIGHASH_ANYONECANPAY, i.nValue, key) index += 1 block = self.build_next_block() self.update_witness_block_with_transactions(block, [tx]) @@ -1499,365 +1794,63 @@ class SegWitTest(BitcoinTestFramework): for i in range(len(tx.vout)): self.utxo.append(UTXO(tx.sha256, i, tx.vout[i].nValue)) + @subtest + def test_non_standard_witness_blinding(self): + """Test behavior of unnecessary witnesses in transactions does not blind the node for the transaction""" - # Test P2SH wrapped witness programs. - def test_p2sh_witness(self, segwit_activated): - self.log.info("Testing P2SH witness transactions") - - assert(len(self.utxo)) - - # Prepare the p2sh-wrapped witness output - witness_program = CScript([OP_DROP, OP_TRUE]) - witness_hash = sha256(witness_program) - p2wsh_pubkey = CScript([OP_0, witness_hash]) - p2sh_witness_hash = hash160(p2wsh_pubkey) - scriptPubKey = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) - scriptSig = CScript([p2wsh_pubkey]) # a push of the redeem script - - # Fund the P2SH output - tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey)) - tx.rehash() - - # Verify mempool acceptance and block validity - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, with_witness=False, accepted=True) - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True, with_witness=segwit_activated) - sync_blocks(self.nodes) - - # Now test attempts to spend the output. - spend_tx = CTransaction() - spend_tx.vin.append(CTxIn(COutPoint(tx.sha256, 0), scriptSig)) - spend_tx.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))) - spend_tx.rehash() - - # This transaction should not be accepted into the mempool pre- or - # post-segwit. Mempool acceptance will use SCRIPT_VERIFY_WITNESS which - # will require a witness to spend a witness program regardless of - # segwit activation. Note that older bitcoind's that are not - # segwit-aware would also reject this for failing CLEANSTACK. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=False, accepted=False) - - # Try to put the witness script in the scriptSig, should also fail. - spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a']) - spend_tx.rehash() - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=False, accepted=False) - - # Now put the witness script in the witness, should succeed after - # segwit activates. - spend_tx.vin[0].scriptSig = scriptSig - spend_tx.rehash() - spend_tx.wit.vtxinwit.append(CTxInWitness()) - spend_tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a', witness_program ] - - # Verify mempool acceptance - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, spend_tx, with_witness=True, accepted=segwit_activated) - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [spend_tx]) - - # If we're after activation, then sending this with witnesses should be valid. - # This no longer works before activation, because SCRIPT_VERIFY_WITNESS - # is always set. - # TODO: rewrite this test to make clear that it only works after activation. - if segwit_activated: - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - else: - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True, with_witness=False) - - # Update self.utxo - self.utxo.pop(0) - self.utxo.append(UTXO(spend_tx.sha256, 0, spend_tx.vout[0].nValue)) - - # Test the behavior of starting up a segwit-aware node after the softfork - # has activated. As segwit requires different block data than pre-segwit - # nodes would have stored, this requires special handling. - # To enable this test, pass --oldbinary=<path-to-pre-segwit-bitcoind> to - # the test. - def test_upgrade_after_activation(self, node_id): - self.log.info("Testing software upgrade after softfork activation") - - assert(node_id != 0) # node0 is assumed to be a segwit-active bitcoind - - # Make sure the nodes are all up - sync_blocks(self.nodes) - - # Restart with the new binary - self.stop_node(node_id) - self.start_node(node_id, extra_args=["-vbparams=segwit:0:999999999999"]) - connect_nodes(self.nodes[0], node_id) - - sync_blocks(self.nodes) - - # Make sure that this peer thinks segwit has activated. - assert(get_bip9_status(self.nodes[node_id], 'segwit')['status'] == "active") - - # Make sure this peer's blocks match those of node0. - height = self.nodes[node_id].getblockcount() - while height >= 0: - block_hash = self.nodes[node_id].getblockhash(height) - assert_equal(block_hash, self.nodes[0].getblockhash(height)) - assert_equal(self.nodes[0].getblock(block_hash), self.nodes[node_id].getblock(block_hash)) - height -= 1 - - - def test_witness_sigops(self): - '''Ensure sigop counting is correct inside witnesses.''' - self.log.info("Testing sigops limit") - - assert(len(self.utxo)) - - # Keep this under MAX_OPS_PER_SCRIPT (201) - witness_program = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKMULTISIG]*5 + [OP_CHECKSIG]*193 + [OP_ENDIF]) - witness_hash = sha256(witness_program) - scriptPubKey = CScript([OP_0, witness_hash]) - - sigops_per_script = 20*5 + 193*1 - # We'll produce 2 extra outputs, one with a program that would take us - # over max sig ops, and one with a program that would exactly reach max - # sig ops - outputs = (MAX_SIGOP_COST // sigops_per_script) + 2 - extra_sigops_available = MAX_SIGOP_COST % sigops_per_script - - # We chose the number of checkmultisigs/checksigs to make this work: - assert(extra_sigops_available < 100) # steer clear of MAX_OPS_PER_SCRIPT - - # This script, when spent with the first - # N(=MAX_SIGOP_COST//sigops_per_script) outputs of our transaction, - # would push us just over the block sigop limit. - witness_program_toomany = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG]*(extra_sigops_available + 1) + [OP_ENDIF]) - witness_hash_toomany = sha256(witness_program_toomany) - scriptPubKey_toomany = CScript([OP_0, witness_hash_toomany]) - - # If we spend this script instead, we would exactly reach our sigop - # limit (for witness sigops). - witness_program_justright = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG]*(extra_sigops_available) + [OP_ENDIF]) - witness_hash_justright = sha256(witness_program_justright) - scriptPubKey_justright = CScript([OP_0, witness_hash_justright]) + # Create a p2sh output -- this is so we can pass the standardness + # rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped + # in P2SH). + p2sh_program = CScript([OP_TRUE]) + p2sh_pubkey = hash160(p2sh_program) + script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) - # First split our available utxo into a bunch of outputs - split_value = self.utxo[0].nValue // outputs + # Now check that unnecessary witnesses can't be used to blind a node + # to a transaction, eg by violating standardness checks. tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) - for i in range(outputs): - tx.vout.append(CTxOut(split_value, scriptPubKey)) - tx.vout[-2].scriptPubKey = scriptPubKey_toomany - tx.vout[-1].scriptPubKey = scriptPubKey_justright + tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) tx.rehash() - - block_1 = self.build_next_block() - self.update_witness_block_with_transactions(block_1, [tx]) - test_witness_block(self.nodes[0].rpc, self.test_node, block_1, accepted=True) - - tx2 = CTransaction() - # If we try to spend the first n-1 outputs from tx, that should be - # too many sigops. - total_value = 0 - for i in range(outputs-1): - tx2.vin.append(CTxIn(COutPoint(tx.sha256, i), b"")) - tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program ] - total_value += tx.vout[i].nValue - tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program_toomany ] - tx2.vout.append(CTxOut(total_value, CScript([OP_TRUE]))) - tx2.rehash() - - block_2 = self.build_next_block() - self.update_witness_block_with_transactions(block_2, [tx2]) - test_witness_block(self.nodes[0].rpc, self.test_node, block_2, accepted=False) - - # Try dropping the last input in tx2, and add an output that has - # too many sigops (contributing to legacy sigop count). - checksig_count = (extra_sigops_available // 4) + 1 - scriptPubKey_checksigs = CScript([OP_CHECKSIG]*checksig_count) - tx2.vout.append(CTxOut(0, scriptPubKey_checksigs)) - tx2.vin.pop() - tx2.wit.vtxinwit.pop() - tx2.vout[0].nValue -= tx.vout[-2].nValue - tx2.rehash() - block_3 = self.build_next_block() - self.update_witness_block_with_transactions(block_3, [tx2]) - test_witness_block(self.nodes[0].rpc, self.test_node, block_3, accepted=False) - - # If we drop the last checksig in this output, the tx should succeed. - block_4 = self.build_next_block() - tx2.vout[-1].scriptPubKey = CScript([OP_CHECKSIG]*(checksig_count-1)) - tx2.rehash() - self.update_witness_block_with_transactions(block_4, [tx2]) - test_witness_block(self.nodes[0].rpc, self.test_node, block_4, accepted=True) - - # Reset the tip back down for the next test + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx, False, True) + self.nodes[0].generate(1) sync_blocks(self.nodes) - for x in self.nodes: - x.invalidateblock(block_4.hash) - - # Try replacing the last input of tx2 to be spending the last - # output of tx - block_5 = self.build_next_block() - tx2.vout.pop() - tx2.vin.append(CTxIn(COutPoint(tx.sha256, outputs-1), b"")) - tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program_justright ] - tx2.rehash() - self.update_witness_block_with_transactions(block_5, [tx2]) - test_witness_block(self.nodes[0].rpc, self.test_node, block_5, accepted=True) - - # TODO: test p2sh sigop counting - - def test_getblocktemplate_before_lockin(self): - self.log.info("Testing getblocktemplate setting of segwit versionbit (before lockin)") - # 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 - # commitment, nor a version bit signalling segwit. - assert_equal(block_version & (1 << VB_WITNESS_BIT), 0) - assert('default_witness_commitment' not in gbt_results) - else: - # For segwit-aware nodes, check the version bit and the witness - # commitment are correct. - assert(block_version & (1 << VB_WITNESS_BIT) != 0) - assert('default_witness_commitment' in gbt_results) - witness_commitment = gbt_results['default_witness_commitment'] - - # Check that default_witness_commitment is present. - witness_root = CBlock.get_merkle_root([ser_uint256(0), - ser_uint256(txid)]) - 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) - - # Uncompressed pubkeys are no longer supported in default relay policy, - # but (for now) are still valid in blocks. - def test_uncompressed_pubkey(self): - self.log.info("Testing uncompressed pubkeys") - # Segwit transactions using uncompressed pubkeys are not accepted - # under default policy, but should still pass consensus. - key = CECKey() - key.set_secretbytes(b"9") - key.set_compressed(False) - pubkey = CPubKey(key.get_pubkey()) - assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey - - assert(len(self.utxo) > 0) - utxo = self.utxo.pop(0) - - # Test 1: P2WPKH - # First create a P2WPKH output that uses an uncompressed pubkey - pubkeyhash = hash160(pubkey) - scriptPKH = CScript([OP_0, pubkeyhash]) - tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(utxo.sha256, utxo.n), b"")) - tx.vout.append(CTxOut(utxo.nValue-1000, scriptPKH)) - tx.rehash() - - # Confirm it in a block. - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - - # Now try to spend it. Send it to a P2WSH output, which we'll - # use in the next test. - witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) - witness_hash = sha256(witness_program) - scriptWSH = CScript([OP_0, witness_hash]) + # We'll add an unnecessary witness to this transaction that would cause + # it to be non-standard, to test that violating policy with a witness + # doesn't blind a node to a transaction. Transactions + # rejected for having a witness shouldn't be added + # to the rejection cache. tx2 = CTransaction() - tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) - tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptWSH)) - script = GetP2PKHScript(pubkeyhash) - sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), CScript([p2sh_program]))) + tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_pubkey)) tx2.wit.vtxinwit.append(CTxInWitness()) - tx2.wit.vtxinwit[0].scriptWitness.stack = [ signature, pubkey ] + tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a' * 400] tx2.rehash() + # This will be rejected due to a policy check: + # No witness is allowed, since it is not a witness program but a p2sh program + test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx2, True, False, b'bad-witness-nonstandard') - # Should fail policy test. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') - # But passes consensus. - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx2]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - - # Test 2: P2WSH - # Try to spend the P2WSH output created in last test. - # Send it to a P2SH(P2WSH) output, which we'll use in the next test. - p2sh_witness_hash = hash160(scriptWSH) - scriptP2SH = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) - scriptSig = CScript([scriptWSH]) + # If we send without witness, it should be accepted. + test_transaction_acceptance(self.nodes[1].rpc, self.std_node, tx2, False, True) + # Now create a new anyone-can-spend utxo for the next test. tx3 = CTransaction() - tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b"")) - tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptP2SH)) - tx3.wit.vtxinwit.append(CTxInWitness()) - sign_P2PK_witness_input(witness_program, tx3, 0, SIGHASH_ALL, tx2.vout[0].nValue, key) - - # Should fail policy test. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') - # But passes consensus. - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx3]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - - # Test 3: P2SH(P2WSH) - # Try to spend the P2SH output created in the last test. - # Send it to a P2PKH output, which we'll use in the next test. - scriptPubKey = GetP2PKHScript(pubkeyhash) - tx4 = CTransaction() - tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), scriptSig)) - tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, scriptPubKey)) - tx4.wit.vtxinwit.append(CTxInWitness()) - sign_P2PK_witness_input(witness_program, tx4, 0, SIGHASH_ALL, tx3.vout[0].nValue, key) + tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), CScript([p2sh_program]))) + tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) + tx3.rehash() + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx2, False, True) + test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx3, False, True) - # Should fail policy test. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx4, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx4]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) - # Test 4: Uncompressed pubkeys should still be valid in non-segwit - # transactions. - tx5 = CTransaction() - tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) - tx5.vout.append(CTxOut(tx4.vout[0].nValue-1000, CScript([OP_TRUE]))) - (sig_hash, err) = SignatureHash(scriptPubKey, tx5, 0, SIGHASH_ALL) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL - tx5.vin[0].scriptSig = CScript([signature, pubkey]) - tx5.rehash() - # Should pass policy and consensus. - test_transaction_acceptance(self.nodes[0].rpc, self.test_node, tx5, True, True) - block = self.build_next_block() - self.update_witness_block_with_transactions(block, [tx5]) - test_witness_block(self.nodes[0].rpc, self.test_node, block, accepted=True) - self.utxo.append(UTXO(tx5.sha256, 0, tx5.vout[0].nValue)) + # Update our utxo list; we spent the first entry. + self.utxo.pop(0) + self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) + @subtest def test_non_standard_witness(self): - self.log.info("Testing detection of non-standard P2WSH witness") + """Test detection of non-standard P2WSH witness""" pad = chr(1).encode('latin-1') # Create scripts for tests @@ -1869,7 +1862,6 @@ class SegWitTest(BitcoinTestFramework): p2wsh_scripts = [] - assert(len(self.utxo)) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) @@ -1893,13 +1885,13 @@ class SegWitTest(BitcoinTestFramework): p2sh_txs = [] for i in range(len(scripts)): p2wsh_tx = CTransaction() - p2wsh_tx.vin.append(CTxIn(COutPoint(txid,i*2))) + p2wsh_tx.vin.append(CTxIn(COutPoint(txid, i * 2))) p2wsh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) p2wsh_tx.wit.vtxinwit.append(CTxInWitness()) p2wsh_tx.rehash() p2wsh_txs.append(p2wsh_tx) p2sh_tx = CTransaction() - p2sh_tx.vin.append(CTxIn(COutPoint(txid,i*2+1), CScript([p2wsh_scripts[i]]))) + p2sh_tx.vin.append(CTxIn(COutPoint(txid, i * 2 + 1), CScript([p2wsh_scripts[i]]))) p2sh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) p2sh_tx.wit.vtxinwit.append(CTxInWitness()) p2sh_tx.rehash() @@ -1956,79 +1948,128 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) + @subtest + def test_upgrade_after_activation(self): + """Test the behavior of starting up a segwit-aware node after the softfork has activated.""" - def run_test(self): - # Setup the p2p connections and start up the network thread. - # self.test_node sets NODE_WITNESS|NODE_NETWORK - self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK|NODE_WITNESS) - # self.old_node sets only NODE_NETWORK - self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK) - # self.std_node is for testing node1 (fRequireStandard=true) - self.std_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK|NODE_WITNESS) + # Restart with the new binary + self.stop_node(2) + self.start_node(2, extra_args=["-vbparams=segwit:0:999999999999"]) + connect_nodes(self.nodes[0], 2) - network_thread_start() + sync_blocks(self.nodes) - # Keep a place to store utxo's that can be used in later tests - self.utxo = [] + # Make sure that this peer thinks segwit has activated. + assert(get_bip9_status(self.nodes[2], 'segwit')['status'] == "active") - # Test logic begins here - self.test_node.wait_for_verack() + # Make sure this peer's blocks match those of node0. + height = self.nodes[2].getblockcount() + while height >= 0: + block_hash = self.nodes[2].getblockhash(height) + assert_equal(block_hash, self.nodes[0].getblockhash(height)) + assert_equal(self.nodes[0].getblock(block_hash), self.nodes[2].getblock(block_hash)) + height -= 1 - self.log.info("Starting tests before segwit lock in:") + @subtest + def test_witness_sigops(self): + """Test sigop counting is correct inside witnesses.""" - self.test_witness_services() # Verifies NODE_WITNESS - self.test_non_witness_transaction() # non-witness tx's are accepted - self.test_unnecessary_witness_before_segwit_activation() - self.test_v0_outputs_arent_spendable() - self.test_block_relay(segwit_activated=False) + # Keep this under MAX_OPS_PER_SCRIPT (201) + witness_program = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKMULTISIG] * 5 + [OP_CHECKSIG] * 193 + [OP_ENDIF]) + witness_hash = sha256(witness_program) + script_pubkey = CScript([OP_0, witness_hash]) - # Advance to segwit being 'started' - self.advance_to_segwit_started() - sync_blocks(self.nodes) - self.test_getblocktemplate_before_lockin() + sigops_per_script = 20 * 5 + 193 * 1 + # We'll produce 2 extra outputs, one with a program that would take us + # over max sig ops, and one with a program that would exactly reach max + # sig ops + outputs = (MAX_SIGOP_COST // sigops_per_script) + 2 + extra_sigops_available = MAX_SIGOP_COST % sigops_per_script - sync_blocks(self.nodes) + # We chose the number of checkmultisigs/checksigs to make this work: + assert(extra_sigops_available < 100) # steer clear of MAX_OPS_PER_SCRIPT - # At lockin, nothing should change. - self.log.info("Testing behavior post lockin, pre-activation") - self.advance_to_segwit_lockin() + # This script, when spent with the first + # N(=MAX_SIGOP_COST//sigops_per_script) outputs of our transaction, + # would push us just over the block sigop limit. + witness_program_toomany = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG] * (extra_sigops_available + 1) + [OP_ENDIF]) + witness_hash_toomany = sha256(witness_program_toomany) + script_pubkey_toomany = CScript([OP_0, witness_hash_toomany]) - # Retest unnecessary witnesses - self.test_unnecessary_witness_before_segwit_activation() - self.test_witness_tx_relay_before_segwit_activation() - self.test_block_relay(segwit_activated=False) - self.test_standardness_v0(segwit_activated=False) + # If we spend this script instead, we would exactly reach our sigop + # limit (for witness sigops). + witness_program_justright = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG] * (extra_sigops_available) + [OP_ENDIF]) + witness_hash_justright = sha256(witness_program_justright) + script_pubkey_justright = CScript([OP_0, witness_hash_justright]) - sync_blocks(self.nodes) + # First split our available utxo into a bunch of outputs + split_value = self.utxo[0].nValue // outputs + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) + for i in range(outputs): + tx.vout.append(CTxOut(split_value, script_pubkey)) + tx.vout[-2].scriptPubKey = script_pubkey_toomany + tx.vout[-1].scriptPubKey = script_pubkey_justright + tx.rehash() - # Now activate segwit - self.log.info("Testing behavior after segwit activation") - self.advance_to_segwit_active() + block_1 = self.build_next_block() + self.update_witness_block_with_transactions(block_1, [tx]) + test_witness_block(self.nodes[0].rpc, self.test_node, block_1, accepted=True) - sync_blocks(self.nodes) + tx2 = CTransaction() + # If we try to spend the first n-1 outputs from tx, that should be + # too many sigops. + total_value = 0 + for i in range(outputs - 1): + tx2.vin.append(CTxIn(COutPoint(tx.sha256, i), b"")) + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[-1].scriptWitness.stack = [witness_program] + total_value += tx.vout[i].nValue + tx2.wit.vtxinwit[-1].scriptWitness.stack = [witness_program_toomany] + tx2.vout.append(CTxOut(total_value, CScript([OP_TRUE]))) + tx2.rehash() - # Test P2SH witness handling again - self.test_p2sh_witness(segwit_activated=True) - self.test_witness_commitments() - self.test_block_malleability() - self.test_witness_block_size() - self.test_submit_block() - self.test_extra_witness_data() - self.test_max_witness_push_length() - self.test_max_witness_program_length() - self.test_witness_input_length() - self.test_block_relay(segwit_activated=True) - self.test_tx_relay_after_segwit_activation() - self.test_standardness_v0(segwit_activated=True) - self.test_segwit_versions() - self.test_premature_coinbase_witness_spend() - self.test_uncompressed_pubkey() - self.test_signature_version_1() - self.test_non_standard_witness() + block_2 = self.build_next_block() + self.update_witness_block_with_transactions(block_2, [tx2]) + test_witness_block(self.nodes[0].rpc, self.test_node, block_2, accepted=False) + + # Try dropping the last input in tx2, and add an output that has + # too many sigops (contributing to legacy sigop count). + checksig_count = (extra_sigops_available // 4) + 1 + script_pubkey_checksigs = CScript([OP_CHECKSIG] * checksig_count) + tx2.vout.append(CTxOut(0, script_pubkey_checksigs)) + tx2.vin.pop() + tx2.wit.vtxinwit.pop() + tx2.vout[0].nValue -= tx.vout[-2].nValue + tx2.rehash() + block_3 = self.build_next_block() + self.update_witness_block_with_transactions(block_3, [tx2]) + test_witness_block(self.nodes[0].rpc, self.test_node, block_3, accepted=False) + + # If we drop the last checksig in this output, the tx should succeed. + block_4 = self.build_next_block() + tx2.vout[-1].scriptPubKey = CScript([OP_CHECKSIG] * (checksig_count - 1)) + tx2.rehash() + self.update_witness_block_with_transactions(block_4, [tx2]) + test_witness_block(self.nodes[0].rpc, self.test_node, block_4, accepted=True) + + # Reset the tip back down for the next test sync_blocks(self.nodes) - self.test_upgrade_after_activation(node_id=2) - self.test_witness_sigops() + for x in self.nodes: + x.invalidateblock(block_4.hash) + # Try replacing the last input of tx2 to be spending the last + # output of tx + block_5 = self.build_next_block() + tx2.vout.pop() + tx2.vin.append(CTxIn(COutPoint(tx.sha256, outputs - 1), b"")) + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[-1].scriptWitness.stack = [witness_program_justright] + tx2.rehash() + self.update_witness_block_with_transactions(block_5, [tx2]) + test_witness_block(self.nodes[0].rpc, self.test_node, block_5, accepted=True) + + # TODO: test p2sh sigop counting if __name__ == '__main__': SegWitTest().main() diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py index 4c7d5e65c5..2788d8995e 100755 --- a/test/functional/p2p_sendheaders.py +++ b/test/functional/p2p_sendheaders.py @@ -90,7 +90,6 @@ from test_framework.mininode import ( CBlockHeader, CInv, NODE_WITNESS, - network_thread_start, P2PInterface, mininode_lock, msg_block, @@ -116,6 +115,7 @@ class BaseNode(P2PInterface): self.block_announced = False self.last_blockhash_announced = None + self.recent_headers_announced = [] def send_get_data(self, block_hashes): """Request data for a list of block hashes.""" @@ -163,40 +163,45 @@ class BaseNode(P2PInterface): def on_headers(self, message): if len(message.headers): self.block_announced = True - message.headers[-1].calc_sha256() + for x in message.headers: + x.calc_sha256() + # append because headers may be announced over multiple messages. + self.recent_headers_announced.append(x.sha256) self.last_blockhash_announced = message.headers[-1].sha256 - def clear_last_announcement(self): + def clear_block_announcements(self): with mininode_lock: self.block_announced = False self.last_message.pop("inv", None) self.last_message.pop("headers", None) + self.recent_headers_announced = [] - def check_last_announcement(self, headers=None, inv=None): - """Test whether the last announcement received had the right header or the right inv. - inv and headers should be lists of block hashes.""" + def check_last_headers_announcement(self, headers): + """Test whether the last headers announcements received are right. + Headers may be announced across more than one message.""" + test_function = lambda: (len(self.recent_headers_announced) >= len(headers)) + wait_until(test_function, timeout=60, lock=mininode_lock) + with mininode_lock: + assert_equal(self.recent_headers_announced, headers) + self.block_announced = False + self.last_message.pop("headers", None) + self.recent_headers_announced = [] + + def check_last_inv_announcement(self, inv): + """Test whether the last announcement received had the right inv. + inv should be a list of block hashes.""" test_function = lambda: self.block_announced wait_until(test_function, timeout=60, lock=mininode_lock) with mininode_lock: - self.block_announced = False - compare_inv = [] if "inv" in self.last_message: compare_inv = [x.hash for x in self.last_message["inv"].inv] - if inv is not None: - assert_equal(compare_inv, inv) - - compare_headers = [] - if "headers" in self.last_message: - compare_headers = [x.sha256 for x in self.last_message["headers"].headers] - if headers is not None: - assert_equal(compare_headers, headers) - + assert_equal(compare_inv, inv) + self.block_announced = False self.last_message.pop("inv", None) - self.last_message.pop("headers", None) class SendHeadersTest(BitcoinTestFramework): def set_test_params(self): @@ -206,8 +211,8 @@ class SendHeadersTest(BitcoinTestFramework): def mine_blocks(self, count): """Mine count blocks and return the new tip.""" - # Clear out last block announcement from each p2p listener - [x.clear_last_announcement() for x in self.nodes[0].p2ps] + # Clear out block announcements from each p2p listener + [x.clear_block_announcements() for x in self.nodes[0].p2ps] self.nodes[0].generate(count) return int(self.nodes[0].getbestblockhash(), 16) @@ -222,7 +227,7 @@ class SendHeadersTest(BitcoinTestFramework): sync_blocks(self.nodes, wait=0.1) for x in self.nodes[0].p2ps: x.wait_for_block_announcement(int(self.nodes[0].getbestblockhash(), 16)) - x.clear_last_announcement() + x.clear_block_announcements() tip_height = self.nodes[1].getblockcount() hash_to_invalidate = self.nodes[1].getblockhash(tip_height - (length - 1)) @@ -232,15 +237,11 @@ class SendHeadersTest(BitcoinTestFramework): return [int(x, 16) for x in all_hashes] def run_test(self): - # Setup the p2p connections and start up the network thread. + # Setup the p2p connections inv_node = self.nodes[0].add_p2p_connection(BaseNode()) # Make sure NODE_NETWORK is not set for test_node, so no block download # will occur outside of direct fetching test_node = self.nodes[0].add_p2p_connection(BaseNode(), services=NODE_WITNESS) - - network_thread_start() - - # Test logic begins here inv_node.wait_for_verack() test_node.wait_for_verack() @@ -255,25 +256,25 @@ class SendHeadersTest(BitcoinTestFramework): tip = self.nodes[0].getblockheader(self.nodes[0].generate(1)[0]) tip_hash = int(tip["hash"], 16) - inv_node.check_last_announcement(inv=[tip_hash], headers=[]) - test_node.check_last_announcement(inv=[tip_hash], headers=[]) + inv_node.check_last_inv_announcement(inv=[tip_hash]) + test_node.check_last_inv_announcement(inv=[tip_hash]) self.log.info("Verify getheaders with null locator and valid hashstop returns headers.") - test_node.clear_last_announcement() + test_node.clear_block_announcements() test_node.send_get_headers(locator=[], hashstop=tip_hash) - test_node.check_last_announcement(headers=[tip_hash]) + test_node.check_last_headers_announcement(headers=[tip_hash]) self.log.info("Verify getheaders with null locator and invalid hashstop does not return headers.") block = create_block(int(tip["hash"], 16), create_coinbase(tip["height"] + 1), tip["mediantime"] + 1) block.solve() test_node.send_header_for_blocks([block]) - test_node.clear_last_announcement() + test_node.clear_block_announcements() test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16)) test_node.sync_with_ping() assert_equal(test_node.block_announced, False) - inv_node.clear_last_announcement() + inv_node.clear_block_announcements() test_node.send_message(msg_block(block)) - inv_node.check_last_announcement(inv=[int(block.hash, 16)], headers=[]) + inv_node.check_last_inv_announcement(inv=[int(block.hash, 16)]) def test_nonnull_locators(self, test_node, inv_node): tip = int(self.nodes[0].getbestblockhash(), 16) @@ -282,10 +283,11 @@ class SendHeadersTest(BitcoinTestFramework): # 1. Mine a block; expect inv announcements each time self.log.info("Part 1: headers don't start before sendheaders message...") for i in range(4): + self.log.debug("Part 1.{}: starting...".format(i)) old_tip = tip tip = self.mine_blocks(1) - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(inv=[tip], headers=[]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_inv_announcement(inv=[tip]) # Try a few different responses; none should affect next announcement if i == 0: # first request the block @@ -296,9 +298,10 @@ class SendHeadersTest(BitcoinTestFramework): test_node.send_get_headers(locator=[old_tip], hashstop=tip) test_node.send_get_data([tip]) test_node.wait_for_block(tip) - test_node.clear_last_announcement() # since we requested headers... + test_node.clear_block_announcements() # since we requested headers... elif i == 2: # this time announce own block via headers + inv_node.clear_block_announcements() height = self.nodes[0].getblockcount() last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 @@ -308,8 +311,9 @@ class SendHeadersTest(BitcoinTestFramework): test_node.wait_for_getdata([new_block.sha256]) test_node.send_message(msg_block(new_block)) test_node.sync_with_ping() # make sure this block is processed - inv_node.clear_last_announcement() - test_node.clear_last_announcement() + wait_until(lambda: inv_node.block_announced, timeout=60, lock=mininode_lock) + inv_node.clear_block_announcements() + test_node.clear_block_announcements() self.log.info("Part 1: success!") self.log.info("Part 2: announce blocks with headers after sendheaders message...") @@ -323,17 +327,19 @@ class SendHeadersTest(BitcoinTestFramework): # Now that we've synced headers, headers announcements should work tip = self.mine_blocks(1) - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(headers=[tip]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_headers_announcement(headers=[tip]) height = self.nodes[0].getblockcount() + 1 block_time += 10 # Advance far enough ahead for i in range(10): + self.log.debug("Part 2.{}: starting...".format(i)) # Mine i blocks, and alternate announcing either via # inv (of tip) or via headers. After each, new blocks # mined by the node should successfully be announced # with block header, even though the blocks are never requested for j in range(2): + self.log.debug("Part 2.{}.{}: starting...".format(i, j)) blocks = [] for b in range(i + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) @@ -368,8 +374,8 @@ class SendHeadersTest(BitcoinTestFramework): assert "inv" not in inv_node.last_message assert "headers" not in inv_node.last_message tip = self.mine_blocks(1) - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(headers=[tip]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_headers_announcement(headers=[tip]) height += 1 block_time += 1 @@ -380,19 +386,20 @@ class SendHeadersTest(BitcoinTestFramework): # PART 3. Headers announcements can stop after large reorg, and resume after # getheaders or inv from peer. for j in range(2): + self.log.debug("Part 3.{}: starting...".format(j)) # First try mining a reorg that can propagate with header announcement new_block_hashes = self.mine_reorg(length=7) tip = new_block_hashes[-1] - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(headers=new_block_hashes) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_headers_announcement(headers=new_block_hashes) block_time += 8 # Mine a too-large reorg, which should be announced with a single inv new_block_hashes = self.mine_reorg(length=8) tip = new_block_hashes[-1] - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(inv=[tip], headers=[]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_inv_announcement(inv=[tip]) block_time += 9 @@ -401,28 +408,33 @@ class SendHeadersTest(BitcoinTestFramework): # Use getblocks/getdata test_node.send_getblocks(locator=[fork_point]) - test_node.check_last_announcement(inv=new_block_hashes, headers=[]) + test_node.check_last_inv_announcement(inv=new_block_hashes) test_node.send_get_data(new_block_hashes) test_node.wait_for_block(new_block_hashes[-1]) for i in range(3): + self.log.debug("Part 3.{}.{}: starting...".format(j, i)) + # Mine another block, still should get only an inv tip = self.mine_blocks(1) - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(inv=[tip], headers=[]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_inv_announcement(inv=[tip]) if i == 0: - self.log.debug("Just get the data -- shouldn't cause headers announcements to resume") + # Just get the data -- shouldn't cause headers announcements to resume test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: - self.log.debug("Send a getheaders message that shouldn't trigger headers announcements to resume (best header sent will be too old)") + # Send a getheaders message that shouldn't trigger headers announcements + # to resume (best header sent will be too old) test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 2: + # This time, try sending either a getheaders to trigger resumption + # of headers announcements, or mine a new block and inv it, also + # triggering resumption of headers announcements. test_node.send_get_data([tip]) test_node.wait_for_block(tip) - self.log.debug("This time, try sending either a getheaders to trigger resumption of headers announcements, or mine a new block and inv it, also triggering resumption of headers announcements.") if j == 0: test_node.send_get_headers(locator=[tip], hashstop=0) test_node.sync_with_ping() @@ -431,8 +443,8 @@ class SendHeadersTest(BitcoinTestFramework): test_node.sync_with_ping() # New blocks should now be announced with header tip = self.mine_blocks(1) - inv_node.check_last_announcement(inv=[tip], headers=[]) - test_node.check_last_announcement(headers=[tip]) + inv_node.check_last_inv_announcement(inv=[tip]) + test_node.check_last_headers_announcement(headers=[tip]) self.log.info("Part 3: success!") @@ -526,6 +538,7 @@ class SendHeadersTest(BitcoinTestFramework): # First we test that receipt of an unconnecting header doesn't prevent # chain sync. for i in range(10): + self.log.debug("Part 5.{}: starting...".format(i)) test_node.last_message.pop("getdata", None) blocks = [] # Create two more blocks. diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index 6a21b693b4..7a4ef1c05c 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -38,18 +38,16 @@ class TimeoutsTest(BitcoinTestFramework): self.num_nodes = 1 def run_test(self): - # Setup the p2p connections and start up the network thread. + # Setup the p2p connections no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn()) no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False) no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False) - network_thread_start() - sleep(1) - assert no_verack_node.connected - assert no_version_node.connected - assert no_send_node.connected + assert no_verack_node.is_connected + assert no_version_node.is_connected + assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) @@ -58,18 +56,18 @@ class TimeoutsTest(BitcoinTestFramework): assert "version" in no_verack_node.last_message - assert no_verack_node.connected - assert no_version_node.connected - assert no_send_node.connected + assert no_verack_node.is_connected + assert no_version_node.is_connected + assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) sleep(31) - assert not no_verack_node.connected - assert not no_version_node.connected - assert not no_send_node.connected + 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/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 53b2856eb5..5f2d65c3f5 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -57,12 +57,8 @@ from test_framework.util import * import time from test_framework.blocktools import create_block, create_coinbase, create_transaction -class AcceptBlockTest(BitcoinTestFramework): - def add_options(self, parser): - parser.add_option("--testbinary", dest="testbinary", - default=os.getenv("BITCOIND", "bitcoind"), - help="bitcoind binary to test") +class AcceptBlockTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 @@ -77,15 +73,11 @@ class AcceptBlockTest(BitcoinTestFramework): self.setup_nodes() def run_test(self): - # Setup the p2p connections and start up the network thread. + # Setup the p2p connections # test_node connects to node0 (not whitelisted) test_node = self.nodes[0].add_p2p_connection(P2PInterface()) # min_work_node connects to node1 (whitelisted) min_work_node = self.nodes[1].add_p2p_connection(P2PInterface()) - - network_thread_start() - - # Test logic begins here test_node.wait_for_verack() min_work_node.wait_for_verack() @@ -208,10 +200,8 @@ class AcceptBlockTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() self.nodes[1].disconnect_p2ps() - network_thread_join() test_node = self.nodes[0].add_p2p_connection(P2PInterface()) - network_thread_start() test_node.wait_for_verack() test_node.send_message(msg_block(block_h1f)) @@ -297,8 +287,6 @@ class AcceptBlockTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() test_node = self.nodes[0].add_p2p_connection(P2PInterface()) - - network_thread_start() test_node.wait_for_verack() # We should have failed reorg and switched back to 290 (but have block 291) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 17e24453e5..155d30317a 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -41,7 +41,6 @@ from test_framework.messages import ( ) from test_framework.mininode import ( P2PInterface, - network_thread_start, ) @@ -217,6 +216,7 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(header['confirmations'], 1) assert_equal(header['previousblockhash'], secondbesthash) assert_is_hex_string(header['chainwork']) + assert_equal(header['nTx'], 1) assert_is_hash_string(header['hash']) assert_is_hash_string(header['previousblockhash']) assert_is_hash_string(header['merkleroot']) @@ -261,7 +261,6 @@ class BlockchainTest(BitcoinTestFramework): # Start a P2P connection since we'll need to create some blocks. node.add_p2p_connection(P2PInterface()) - network_thread_start() node.p2p.wait_for_verack() current_height = node.getblock(node.getbestblockhash())['height'] diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py new file mode 100755 index 0000000000..97e614c888 --- /dev/null +++ b/test/functional/rpc_createmultisig.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-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. +"""Test transaction signing using the signrawtransaction* RPCs.""" + +from test_framework.test_framework import BitcoinTestFramework +import decimal + +class RpcCreateMultiSigTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + + def get_keys(self): + node0,node1,node2 = self.nodes + self.add = [node1.getnewaddress() for _ in range(self.nkeys)] + self.pub = [node1.getaddressinfo(a)["pubkey"] for a in self.add] + self.priv = [node1.dumpprivkey(a) for a in self.add] + self.final = node2.getnewaddress() + + def run_test(self): + node0,node1,node2 = self.nodes + + # 50 BTC each, rest will be 25 BTC each + node0.generate(149) + self.sync_all() + + self.moved = 0 + for self.nkeys in [3,5]: + for self.nsigs in [2,3]: + for self.output_type in ["bech32", "p2sh-segwit", "legacy"]: + self.get_keys() + self.do_multisig() + + self.checkbalances() + + def checkbalances(self): + node0,node1,node2 = self.nodes + node0.generate(100) + self.sync_all() + + bal0 = node0.getbalance() + bal1 = node1.getbalance() + bal2 = node2.getbalance() + + height = node0.getblockchaininfo()["blocks"] + assert 150 < height < 350 + total = 149*50 + (height-149-100)*25 + assert bal1 == 0 + assert bal2 == self.moved + assert bal0+bal1+bal2 == total + + def do_multisig(self): + node0,node1,node2 = self.nodes + + msig = node2.createmultisig(self.nsigs, self.pub, self.output_type) + madd = msig["address"] + mredeem = msig["redeemScript"] + if self.output_type == 'bech32': + assert madd[0:4] == "bcrt" # actually a bech32 address + + # compare against addmultisigaddress + msigw = node1.addmultisigaddress(self.nsigs, self.pub, None, self.output_type) + maddw = msigw["address"] + mredeemw = msigw["redeemScript"] + # addmultisigiaddress and createmultisig work the same + assert maddw == madd + assert mredeemw == mredeem + + txid = node0.sendtoaddress(madd, 40) + + tx = node0.getrawtransaction(txid, True) + vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses",[])] + assert len(vout) == 1 + vout = vout[0] + scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"] + value = tx["vout"][vout]["value"] + prevtxs = [{"txid": txid, "vout": vout, "scriptPubKey": scriptPubKey, "redeemScript": mredeem, "amount": value}] + + node0.generate(1) + + outval = value - decimal.Decimal("0.00001000") + rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}]) + + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], prevtxs) + rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) + + self.moved += outval + tx = node0.sendrawtransaction(rawtx3["hex"], True) + blk = node0.generate(1)[0] + assert tx in node0.getblock(blk)["tx"] + + txinfo = node0.getrawtransaction(tx, True, blk) + self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"])) + +if __name__ == '__main__': + RpcCreateMultiSigTest().main() diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 1ffc570437..d588151768 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -50,8 +50,11 @@ class DecodeScriptTest(BitcoinTestFramework): def decodescript_script_pub_key(self): public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2' push_public_key = '21' + public_key - public_key_hash = '11695b6cd891484c2d49ec5aa738ec2b2f897777' + public_key_hash = '5dd1d3a048119c27b28293056724d9522f26d945' push_public_key_hash = '14' + public_key_hash + uncompressed_public_key = '04b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb25e01fc8fde47c96c98a4f3a8123e33a38a50cf9025cc8c4494a518f991792bb7' + push_uncompressed_public_key = '41' + uncompressed_public_key + p2wsh_p2pk_script_hash = 'd8590cf8ea0674cf3d49fd7ca249b85ef7485dea62c138468bddeb20cd6519f7' # below are test cases for all of the standard transaction types @@ -59,18 +62,26 @@ class DecodeScriptTest(BitcoinTestFramework): # <pubkey> OP_CHECKSIG rpc_result = self.nodes[0].decodescript(push_public_key + 'ac') assert_equal(public_key + ' OP_CHECKSIG', rpc_result['asm']) + # P2PK is translated to P2WPKH + assert_equal('0 ' + public_key_hash, rpc_result['segwit']['asm']) # 2) P2PKH scriptPubKey # OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG rpc_result = self.nodes[0].decodescript('76a9' + push_public_key_hash + '88ac') assert_equal('OP_DUP OP_HASH160 ' + public_key_hash + ' OP_EQUALVERIFY OP_CHECKSIG', rpc_result['asm']) + # P2PKH is translated to P2WPKH + assert_equal('0 ' + public_key_hash, rpc_result['segwit']['asm']) # 3) multisig scriptPubKey # <m> <A pubkey> <B pubkey> <C pubkey> <n> OP_CHECKMULTISIG # just imagine that the pub keys used below are different. # for our purposes here it does not matter that they are the same even though it is unrealistic. - rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_public_key + push_public_key + '53ae') + multisig_script = '52' + push_public_key + push_public_key + push_public_key + '53ae' + rpc_result = self.nodes[0].decodescript(multisig_script) assert_equal('2 ' + public_key + ' ' + public_key + ' ' + public_key + ' 3 OP_CHECKMULTISIG', rpc_result['asm']) + # multisig in P2WSH + multisig_script_hash = bytes_to_hex_str(sha256(hex_str_to_bytes(multisig_script))) + assert_equal('0 ' + multisig_script_hash, rpc_result['segwit']['asm']) # 4) P2SH scriptPubKey # OP_HASH160 <Hash160(redeemScript)> OP_EQUAL. @@ -78,6 +89,8 @@ class DecodeScriptTest(BitcoinTestFramework): # but this works the same for purposes of this test. rpc_result = self.nodes[0].decodescript('a9' + push_public_key_hash + '87') assert_equal('OP_HASH160 ' + public_key_hash + ' OP_EQUAL', rpc_result['asm']) + # P2SH does not work in segwit secripts. decodescript should not return a result for it. + assert 'segwit' not in rpc_result # 5) null data scriptPubKey # use a signature look-alike here to make sure that we do not decode random data as a signature. @@ -101,8 +114,49 @@ class DecodeScriptTest(BitcoinTestFramework): # <sender-pubkey> OP_CHECKSIG # # lock until block 500,000 - rpc_result = self.nodes[0].decodescript('63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac') + cltv_script = '63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac' + rpc_result = self.nodes[0].decodescript(cltv_script) assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) + # CLTV script in P2WSH + cltv_script_hash = bytes_to_hex_str(sha256(hex_str_to_bytes(cltv_script))) + assert_equal('0 ' + cltv_script_hash, rpc_result['segwit']['asm']) + + # 7) P2PK scriptPubKey + # <pubkey> OP_CHECKSIG + rpc_result = self.nodes[0].decodescript(push_uncompressed_public_key + 'ac') + assert_equal(uncompressed_public_key + ' OP_CHECKSIG', rpc_result['asm']) + # uncompressed pubkeys are invalid for checksigs in segwit scripts. + # decodescript should not return a P2WPKH equivalent. + assert 'segwit' not in rpc_result + + # 8) multisig scriptPubKey with an uncompressed pubkey + # <m> <A pubkey> <B pubkey> <n> OP_CHECKMULTISIG + # just imagine that the pub keys used below are different. + # the purpose of this test is to check that a segwit script is not returned for bare multisig scripts + # with an uncompressed pubkey in them. + rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_uncompressed_public_key +'52ae') + assert_equal('2 ' + public_key + ' ' + uncompressed_public_key + ' 2 OP_CHECKMULTISIG', rpc_result['asm']) + # uncompressed pubkeys are invalid for checksigs in segwit scripts. + # decodescript should not return a P2WPKH equivalent. + assert 'segwit' not in rpc_result + + # 9) P2WPKH scriptpubkey + # 0 <PubKeyHash> + rpc_result = self.nodes[0].decodescript('00' + push_public_key_hash) + assert_equal('0 ' + public_key_hash, rpc_result['asm']) + # segwit scripts do not work nested into each other. + # a nested segwit script should not be returned in the results. + assert 'segwit' not in rpc_result + + # 10) P2WSH scriptpubkey + # 0 <ScriptHash> + # even though this hash is of a P2PK script which is better used as bare P2WPKH, it should not matter + # for the purpose of this test. + rpc_result = self.nodes[0].decodescript('0020' + p2wsh_p2pk_script_hash) + assert_equal('0 ' + p2wsh_p2pk_script_hash, rpc_result['asm']) + # segwit scripts do not work nested into each other. + # a nested segwit script should not be returned in the results. + assert 'segwit' not in rpc_result def decoderawtransaction_asm_sighashtype(self): """Test decoding scripts via RPC command "decoderawtransaction". diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py index 7b7c596506..bc27c183b1 100755 --- a/test/functional/rpc_deprecated.py +++ b/test/functional/rpc_deprecated.py @@ -40,7 +40,6 @@ class DeprecatedRpcTest(BitcoinTestFramework): # # The following 'label' RPC methods are usable both with and without the # -deprecatedrpc=accounts switch enabled. - # - getlabeladdress # - getaddressesbylabel # - getreceivedbylabel # - listlabels @@ -49,6 +48,7 @@ class DeprecatedRpcTest(BitcoinTestFramework): # address0 = self.nodes[0].getnewaddress() self.nodes[0].generatetoaddress(101, address0) + self.sync_all() address1 = self.nodes[1].getnewaddress() self.nodes[1].generatetoaddress(101, address1) @@ -68,10 +68,6 @@ class DeprecatedRpcTest(BitcoinTestFramework): assert_raises_rpc_error(-32, "getaccountaddress is deprecated", self.nodes[0].getaccountaddress, "label0") self.nodes[1].getaccountaddress("label1") - self.log.info("- getlabeladdress") - self.nodes[0].getlabeladdress("label0") - self.nodes[1].getlabeladdress("label1") - self.log.info("- getaddressesbyaccount") assert_raises_rpc_error(-32, "getaddressesbyaccount is deprecated", self.nodes[0].getaddressesbyaccount, "label0") self.nodes[1].getaddressesbyaccount("label1") diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 5fb9a361d9..1e91cf598c 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -4,8 +4,18 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the fundrawtransaction RPC.""" +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import ( + assert_equal, + assert_fee_amount, + assert_greater_than, + assert_greater_than_or_equal, + assert_raises_rpc_error, + connect_nodes_bi, + count_bytes, + find_vout_for_address, +) def get_unspent(listunspent, amount): @@ -57,6 +67,11 @@ class RawTransactionsTest(BitcoinTestFramework): watchonly_amount = Decimal(200) self.nodes[3].importpubkey(watchonly_pubkey, "", True) watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount) + + # Lock UTXO so nodes[0] doesn't accidentally spend it + watchonly_vout = find_vout_for_address(self.nodes[0], watchonly_txid, watchonly_address) + self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}]) + self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5) @@ -224,7 +239,7 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None}) - assert_raises_rpc_error(-5, "Unknown change type", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''}) + assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''}) rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'}) dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex']) assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type']) @@ -475,6 +490,9 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,3) + # Again lock the watchonly UTXO or nodes[0] may spend it, because + # lockunspent is memory-only and thus lost on restart + self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}]) self.sync_all() # drain the keypool diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py new file mode 100755 index 0000000000..f573faaf17 --- /dev/null +++ b/test/functional/rpc_getblockstats.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-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. + +# +# Test getblockstats rpc call +# +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) +import json +import os +import time + +TESTSDIR = os.path.dirname(os.path.realpath(__file__)) + +class GetblockstatsTest(BitcoinTestFramework): + + start_height = 101 + max_stat_pos = 2 + STATS_NEED_TXINDEX = [ + 'avgfee', + 'avgfeerate', + 'maxfee', + 'maxfeerate', + 'medianfee', + 'medianfeerate', + 'minfee', + 'minfeerate', + 'totalfee', + 'utxo_size_inc', + ] + + def add_options(self, parser): + parser.add_option('--gen-test-data', dest='gen_test_data', + default=False, action='store_true', + help='Generate test data') + parser.add_option('--test-data', dest='test_data', + default='data/rpc_getblockstats.json', + action='store', metavar='FILE', + help='Test data file') + + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [['-txindex'], ['-paytxfee=0.003']] + self.setup_clean_chain = True + + def get_stats(self): + return [self.nodes[0].getblockstats(hash_or_height=self.start_height + i) for i in range(self.max_stat_pos+1)] + + def generate_test_data(self, filename): + mocktime = time.time() + self.nodes[0].generate(101) + + self.nodes[0].sendtoaddress(address=self.nodes[1].getnewaddress(), amount=10, subtractfeefromamount=True) + self.nodes[0].generate(1) + self.sync_all() + + self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=10, subtractfeefromamount=True) + self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=10, subtractfeefromamount=False) + self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1, subtractfeefromamount=True) + self.sync_all() + self.nodes[0].generate(1) + + self.expected_stats = self.get_stats() + + blocks = [] + tip = self.nodes[0].getbestblockhash() + blockhash = None + height = 0 + while tip != blockhash: + blockhash = self.nodes[0].getblockhash(height) + blocks.append(self.nodes[0].getblock(blockhash, 0)) + height += 1 + + to_dump = { + 'blocks': blocks, + 'mocktime': int(mocktime), + 'stats': self.expected_stats, + } + with open(filename, 'w', encoding="utf8") as f: + json.dump(to_dump, f, sort_keys=True, indent=2) + + def load_test_data(self, filename): + with open(filename, 'r', encoding="utf8") as f: + d = json.load(f) + blocks = d['blocks'] + mocktime = d['mocktime'] + self.expected_stats = d['stats'] + + # Set the timestamps from the file so that the nodes can get out of Initial Block Download + self.nodes[0].setmocktime(mocktime) + self.nodes[1].setmocktime(mocktime) + + for b in blocks: + self.nodes[0].submitblock(b) + + def run_test(self): + test_data = os.path.join(TESTSDIR, self.options.test_data) + if self.options.gen_test_data: + self.generate_test_data(test_data) + else: + self.load_test_data(test_data) + + self.sync_all() + stats = self.get_stats() + expected_stats_noindex = [] + for stat_row in stats: + expected_stats_noindex.append({k: v for k, v in stat_row.items() if k not in self.STATS_NEED_TXINDEX}) + + # Make sure all valid statistics are included but nothing else is + expected_keys = self.expected_stats[0].keys() + assert_equal(set(stats[0].keys()), set(expected_keys)) + + assert_equal(stats[0]['height'], self.start_height) + assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos) + + for i in range(self.max_stat_pos+1): + self.log.info('Checking block %d\n' % (i)) + assert_equal(stats[i], self.expected_stats[i]) + + # Check selecting block by hash too + blockhash = self.expected_stats[i]['blockhash'] + stats_by_hash = self.nodes[0].getblockstats(hash_or_height=blockhash) + assert_equal(stats_by_hash, self.expected_stats[i]) + + # Check with the node that has no txindex + stats_no_txindex = self.nodes[1].getblockstats(hash_or_height=blockhash, stats=list(expected_stats_noindex[i].keys())) + assert_equal(stats_no_txindex, expected_stats_noindex[i]) + + # Make sure each stat can be queried on its own + for stat in expected_keys: + for i in range(self.max_stat_pos+1): + result = self.nodes[0].getblockstats(hash_or_height=self.start_height + i, stats=[stat]) + assert_equal(list(result.keys()), [stat]) + if result[stat] != self.expected_stats[i][stat]: + self.log.info('result[%s] (%d) failed, %r != %r' % ( + stat, i, result[stat], self.expected_stats[i][stat])) + assert_equal(result[stat], self.expected_stats[i][stat]) + + # Make sure only the selected statistics are included (more than one) + some_stats = {'minfee', 'maxfee'} + stats = self.nodes[0].getblockstats(hash_or_height=1, stats=list(some_stats)) + assert_equal(set(stats.keys()), some_stats) + + # Test invalid parameters raise the proper json exceptions + tip = self.start_height + self.max_stat_pos + assert_raises_rpc_error(-8, 'Target block height %d after current tip %d' % (tip+1, tip), + self.nodes[0].getblockstats, hash_or_height=tip+1) + assert_raises_rpc_error(-8, 'Target block height %d is negative' % (-1), + self.nodes[0].getblockstats, hash_or_height=-1) + + # Make sure not valid stats aren't allowed + inv_sel_stat = 'asdfghjkl' + inv_stats = [ + [inv_sel_stat], + ['minfee' , inv_sel_stat], + [inv_sel_stat, 'minfee'], + ['minfee', inv_sel_stat, 'maxfee'], + ] + for inv_stat in inv_stats: + assert_raises_rpc_error(-8, 'Invalid selected statistic %s' % inv_sel_stat, + self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat) + + # Make sure we aren't always returning inv_sel_stat as the culprit stat + assert_raises_rpc_error(-8, 'Invalid selected statistic aaa%s' % inv_sel_stat, + 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=self.start_height + self.max_stat_pos) + + # Mainchain's genesis block shouldn't be found on regtest + assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, + hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') + +if __name__ == '__main__': + GetblockstatsTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py new file mode 100755 index 0000000000..a68327496f --- /dev/null +++ b/test/functional/rpc_psbt.py @@ -0,0 +1,190 @@ +#!/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 Partially Signed Transaction RPCs. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +# Create one-input, one-output, no-fee transaction: +class PSBTTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 3 + + 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'] + + # Node 1 should not be able to add anything to it but still return the psbtx same as before + psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt'] + assert_equal(psbtx1, psbtx) + + # Sign the transaction and send + signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt'] + final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] + self.nodes[0].sendrawtransaction(final_tx) + + # Create p2sh, p2wpkh, and p2wsh addresses + pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey'] + pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey'] + pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey'] + p2sh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address'] + p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address'] + p2sh_p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address'] + p2wpkh = self.nodes[1].getnewaddress("", "bech32") + p2pkh = self.nodes[1].getnewaddress("", "legacy") + p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit") + + # fund those addresses + rawtx = self.nodes[0].createrawtransaction([], {p2sh:10, p2wsh:10, p2wpkh:10, p2sh_p2wsh:10, p2sh_p2wpkh:10, p2pkh:10}) + rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition":3}) + signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])['hex'] + txid = self.nodes[0].sendrawtransaction(signed_tx) + self.nodes[0].generate(6) + self.sync_all() + + # Find the output pos + p2sh_pos = -1 + p2wsh_pos = -1 + p2wpkh_pos = -1 + p2pkh_pos = -1 + p2sh_p2wsh_pos = -1 + p2sh_p2wpkh_pos = -1 + decoded = self.nodes[0].decoderawtransaction(signed_tx) + for out in decoded['vout']: + if out['scriptPubKey']['addresses'][0] == p2sh: + p2sh_pos = out['n'] + elif out['scriptPubKey']['addresses'][0] == p2wsh: + p2wsh_pos = out['n'] + elif out['scriptPubKey']['addresses'][0] == p2wpkh: + p2wpkh_pos = out['n'] + elif out['scriptPubKey']['addresses'][0] == p2sh_p2wsh: + p2sh_p2wsh_pos = out['n'] + elif out['scriptPubKey']['addresses'][0] == p2sh_p2wpkh: + p2sh_p2wpkh_pos = out['n'] + elif out['scriptPubKey']['addresses'][0] == p2pkh: + p2pkh_pos = out['n'] + + # spend single key from node 1 + rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt'] + walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx) + assert_equal(walletprocesspsbt_out['complete'], True) + self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex']) + + # partially sign multisig things with node 1 + psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt'] + walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx) + psbtx = walletprocesspsbt_out['psbt'] + assert_equal(walletprocesspsbt_out['complete'], False) + + # partially sign with node 2. This should be complete and sendable + walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx) + assert_equal(walletprocesspsbt_out['complete'], True) + self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex']) + + # check that walletprocesspsbt fails to decode a non-psbt + rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2wpkh_pos}], {self.nodes[1].getnewaddress():9.99}) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[1].walletprocesspsbt, rawtx) + + # Convert a non-psbt to psbt and make sure we can decode it + rawtx = self.nodes[0].createrawtransaction([], {self.nodes[1].getnewaddress():10}) + rawtx = self.nodes[0].fundrawtransaction(rawtx) + new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) + self.nodes[0].decodepsbt(new_psbt) + + # 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']) + + # Explicilty allow converting non-empty txs + new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) + self.nodes[0].decodepsbt(new_psbt) + + # Create outputs to nodes 1 and 2 + node1_addr = self.nodes[1].getnewaddress() + node2_addr = self.nodes[2].getnewaddress() + txid1 = self.nodes[0].sendtoaddress(node1_addr, 13) + txid2 =self.nodes[0].sendtoaddress(node2_addr, 13) + self.nodes[0].generate(6) + self.sync_all() + vout1 = find_output(self.nodes[1], txid1, 13) + vout2 = find_output(self.nodes[2], txid2, 13) + + # Create a psbt spending outputs from nodes 1 and 2 + psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], {self.nodes[0].getnewaddress():25.999}) + + # Update psbts, should only have data for one input and not the other + psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig)['psbt'] + psbt1_decoded = self.nodes[0].decodepsbt(psbt1) + assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1] + psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig)['psbt'] + psbt2_decoded = self.nodes[0].decodepsbt(psbt2) + assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1] + + # Combine, finalize, and send the psbts + combined = self.nodes[0].combinepsbt([psbt1, psbt2]) + finalized = self.nodes[0].finalizepsbt(combined)['hex'] + self.nodes[0].sendrawtransaction(finalized) + self.nodes[0].generate(6) + self.sync_all() + + # BIP 174 Test Vectors + + # Check that unknown values are just passed through + unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=" + unknown_out = self.nodes[0].walletprocesspsbt(unknown_psbt)['psbt'] + assert_equal(unknown_psbt, unknown_out) + + # Open the data file + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_psbt.json'), encoding='utf-8') as f: + d = json.load(f) + invalids = d['invalid'] + valids = d['valid'] + creators = d['creator'] + signers = d['signer'] + combiners = d['combiner'] + finalizers = d['finalizer'] + extractors = d['extractor'] + + # Invalid PSBTs + for invalid in invalids: + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decodepsbt, invalid) + + # Valid PSBTs + for valid in valids: + self.nodes[0].decodepsbt(valid) + + # Creator Tests + for creator in creators: + created_tx = self.nodes[0].createpsbt(creator['inputs'], creator['outputs']) + assert_equal(created_tx, creator['result']) + + # Signer tests + for i, signer in enumerate(signers): + for key in signer['privkeys']: + self.nodes[i].importprivkey(key) + signed_tx = self.nodes[i].walletprocesspsbt(signer['psbt'])['psbt'] + assert_equal(signed_tx, signer['result']) + + # Combiner test + for combiner in combiners: + combined = self.nodes[2].combinepsbt(combiner['combine']) + assert_equal(combined, combiner['result']) + + # Finalizer test + for finalizer in finalizers: + finalized = self.nodes[2].finalizepsbt(finalizer['finalize'], False)['psbt'] + assert_equal(finalized, finalizer['result']) + + # Extractor test + for extractor in extractors: + extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex'] + assert_equal(extracted, extractor['result']) + + +if __name__ == '__main__': + PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 48b4a4a9db..2485dcf6ec 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -137,6 +137,61 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {'data': '99'}, {'data': '99'}]), ) + for type in ["bech32", "p2sh-segwit", "legacy"]: + addr = self.nodes[0].getnewaddress("", type) + addrinfo = self.nodes[0].getaddressinfo(addr) + pubkey = addrinfo["scriptPubKey"] + + self.log.info('sendrawtransaction with missing prevtx info (%s)' %(type)) + + # Test `signrawtransactionwithwallet` invalid `prevtxs` + inputs = [ {'txid' : txid, 'vout' : 3, 'sequence' : 1000}] + outputs = { self.nodes[0].getnewaddress() : 1 } + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + + prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1) + succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) + assert succ["complete"] + if type == "legacy": + del prevtx["amount"] + succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) + assert succ["complete"] + + if type != "legacy": + assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "scriptPubKey": pubkey, + "vout": 3, + } + ]) + + assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "scriptPubKey": pubkey, + "amount": 1, + } + ]) + assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "scriptPubKey": pubkey, + "vout": 3, + "amount": 1, + } + ]) + assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "vout": 3, + "amount": 1 + } + ]) + + ######################################### + # sendrawtransaction with missing input # + ######################################### + self.log.info('sendrawtransaction with missing input') inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1}] #won't exists outputs = { self.nodes[0].getnewaddress() : 4.998 } diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py new file mode 100755 index 0000000000..ce5d4da9e7 --- /dev/null +++ b/test/functional/rpc_scantxoutset.py @@ -0,0 +1,48 @@ +#!/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 scantxoutset rpc call.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +import shutil +import os + +class ScantxoutsetTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + def run_test(self): + self.log.info("Mining blocks...") + self.nodes[0].generate(110) + + addr_P2SH_SEGWIT = self.nodes[0].getnewaddress("", "p2sh-segwit") + pubk1 = self.nodes[0].getaddressinfo(addr_P2SH_SEGWIT)['pubkey'] + addr_LEGACY = self.nodes[0].getnewaddress("", "legacy") + pubk2 = self.nodes[0].getaddressinfo(addr_LEGACY)['pubkey'] + addr_BECH32 = self.nodes[0].getnewaddress("", "bech32") + pubk3 = self.nodes[0].getaddressinfo(addr_BECH32)['pubkey'] + self.nodes[0].sendtoaddress(addr_P2SH_SEGWIT, 1) + self.nodes[0].sendtoaddress(addr_LEGACY, 2) + self.nodes[0].sendtoaddress(addr_BECH32, 3) + self.nodes[0].generate(1) + + self.log.info("Stop node, remove wallet, mine again some blocks...") + self.stop_node(0) + shutil.rmtree(os.path.join(self.nodes[0].datadir, "regtest", 'wallets')) + self.start_node(0) + self.nodes[0].generate(110) + + self.restart_node(0, ['-nowallet']) + self.log.info("Test if we have found the non HD unspent outputs.") + assert_equal(self.nodes[0].scantxoutset("start", [ {"pubkey": {"pubkey": pubk1}}, {"pubkey": {"pubkey": pubk2}}, {"pubkey": {"pubkey": pubk3}}])['total_amount'], 6) + assert_equal(self.nodes[0].scantxoutset("start", [ {"address": addr_P2SH_SEGWIT}, {"address": addr_LEGACY}, {"address": addr_BECH32}])['total_amount'], 6) + assert_equal(self.nodes[0].scantxoutset("start", [ {"address": addr_P2SH_SEGWIT}, {"address": addr_LEGACY}, {"pubkey": {"pubkey": pubk3}} ])['total_amount'], 6) + + self.log.info("Test invalid parameters.") + assert_raises_rpc_error(-8, 'Scanobject "pubkey" must contain an object as value', self.nodes[0].scantxoutset, "start", [ {"pubkey": pubk1}]) #missing pubkey object + assert_raises_rpc_error(-8, 'Scanobject "address" must contain a single string as value', self.nodes[0].scantxoutset, "start", [ {"address": {"address": addr_P2SH_SEGWIT}}]) #invalid object for address object + +if __name__ == '__main__': + ScantxoutsetTest().main() diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py index c52a7397dc..e5a63f0c46 100755 --- a/test/functional/rpc_txoutproof.py +++ b/test/functional/rpc_txoutproof.py @@ -6,6 +6,8 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * +from test_framework.mininode import FromHex, ToHex +from test_framework.messages import CMerkleBlock class MerkleBlockTest(BitcoinTestFramework): def set_test_params(self): @@ -78,6 +80,27 @@ class MerkleBlockTest(BitcoinTestFramework): # We can't get a proof if we specify transactions from different blocks assert_raises_rpc_error(-5, "Not all transactions found in specified or retrieved block", self.nodes[2].gettxoutproof, [txid1, txid3]) + # Now we'll try tweaking a proof. + proof = self.nodes[3].gettxoutproof([txid1, txid2]) + assert txid1 in self.nodes[0].verifytxoutproof(proof) + assert txid2 in self.nodes[1].verifytxoutproof(proof) + + tweaked_proof = FromHex(CMerkleBlock(), proof) + + # Make sure that our serialization/deserialization is working + assert txid1 in self.nodes[2].verifytxoutproof(ToHex(tweaked_proof)) + + # Check to see if we can go up the merkle tree and pass this off as a + # single-transaction block + tweaked_proof.txn.nTransactions = 1 + tweaked_proof.txn.vHash = [tweaked_proof.header.hashMerkleRoot] + tweaked_proof.txn.vBits = [True] + [False]*7 + + for n in self.nodes: + assert not n.verifytxoutproof(ToHex(tweaked_proof)) + + # TODO: try more variants, eg transactions at different depths, and + # verify that the proofs are invalid if __name__ == '__main__': MerkleBlockTest().main() diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 1ef59da5ad..62d86467fc 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -59,7 +59,7 @@ class HTTPBasicsTest(BitcoinTestFramework): #Old authpair authpair = url.username + ':' + url.password - #New authpair generated via share/rpcuser tool + #New authpair generated via share/rpcauth tool password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" #Second authpair with different username diff --git a/test/functional/rpc_zmq.py b/test/functional/rpc_zmq.py new file mode 100755 index 0000000000..6dbc726d5e --- /dev/null +++ b/test/functional/rpc_zmq.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. +"""Test for the ZMQ RPC methods.""" + +from test_framework.test_framework import ( + BitcoinTestFramework, skip_if_no_py3_zmq, skip_if_no_bitcoind_zmq) +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 run_test(self): + skip_if_no_py3_zmq() + skip_if_no_bitcoind_zmq(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/messages.py b/test/functional/test_framework/messages.py index ca2e425bd6..df8d424d01 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -841,6 +841,52 @@ class BlockTransactions(): def __repr__(self): return "BlockTransactions(hash=%064x transactions=%s)" % (self.blockhash, repr(self.transactions)) +class CPartialMerkleTree(): + 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] + self.vHash = deser_uint256_vector(f) + vBytes = deser_string(f) + self.vBits = [] + for i in range(len(vBytes) * 8): + self.vBits.append(vBytes[i//8] & (1 << (i % 8)) != 0) + + def serialize(self): + r = b"" + r += struct.pack("<i", self.nTransactions) + r += ser_uint256_vector(self.vHash) + vBytesArray = bytearray([0x00] * ((len(self.vBits) + 7)//8)) + for i in range(len(self.vBits)): + vBytesArray[i // 8] |= self.vBits[i] << (i % 8) + r += ser_string(bytes(vBytesArray)) + return r + + def __repr__(self): + return "CPartialMerkleTree(nTransactions=%d, vHash=%s, vBits=%s)" % (self.nTransactions, repr(self.vHash), repr(self.vBits)) + +class CMerkleBlock(): + def __init__(self): + self.header = CBlockHeader() + self.txn = CPartialMerkleTree() + + def deserialize(self, f): + self.header.deserialize(f) + self.txn.deserialize(f) + + def serialize(self): + r = b"" + r += self.header.serialize() + r += self.txn.serialize() + return r + + def __repr__(self): + return "CMerkleBlock(header=%s, txn=%s)" % (repr(self.header), repr(self.txn)) + # Objects that correspond to messages on the wire class msg_version(): diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index aba2841682..d5b1a90687 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -13,11 +13,10 @@ P2PConnection: A low-level connection object to a node's P2P interface P2PInterface: A high-level interface object for communicating to a node over P2P P2PDataStore: A p2p interface class that keeps a store of transactions and blocks and can respond correctly to getdata and getheaders messages""" -import asyncore +import asyncio from collections import defaultdict from io import BytesIO import logging -import socket import struct import sys import threading @@ -57,7 +56,8 @@ MAGIC_BYTES = { "regtest": b"\xfa\xbf\xb5\xda", # regtest } -class P2PConnection(asyncore.dispatcher): + +class P2PConnection(asyncio.Protocol): """A low-level connection object to a node's P2P interface. This class is responsible for: @@ -71,68 +71,59 @@ class P2PConnection(asyncore.dispatcher): sub-classed and the on_message() callback overridden.""" def __init__(self): - # All P2PConnections must be created before starting the NetworkThread. - # assert that the network thread is not running. - assert not network_thread_running() + # The underlying transport of the connection. + # Should only call methods on this from the NetworkThread, c.f. call_soon_threadsafe + self._transport = None - super().__init__(map=mininode_socket_map) + @property + def is_connected(self): + return self._transport is not None def peer_connect(self, dstaddr, dstport, net="regtest"): + assert not self.is_connected self.dstaddr = dstaddr self.dstport = dstport - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - self.sendbuf = b"" + # The initial message to send after the connection was made: + self.on_connection_send_msg = None self.recvbuf = b"" - self.state = "connecting" self.network = net - self.disconnect = False - logger.debug('Connecting to Bitcoin Node: %s:%d' % (self.dstaddr, self.dstport)) - try: - self.connect((dstaddr, dstport)) - except: - self.handle_close() + loop = NetworkThread.network_event_loop + conn_gen_unsafe = loop.create_connection(lambda: self, host=self.dstaddr, port=self.dstport) + conn_gen = lambda: loop.call_soon_threadsafe(loop.create_task, conn_gen_unsafe) + return conn_gen def peer_disconnect(self): # Connection could have already been closed by other end. - if self.state == "connected": - self.disconnect_node() + NetworkThread.network_event_loop.call_soon_threadsafe(lambda: self._transport and self._transport.abort()) # Connection and disconnection methods - def handle_connect(self): - """asyncore callback when a connection is opened.""" - if self.state != "connected": - logger.debug("Connected & Listening: %s:%d" % (self.dstaddr, self.dstport)) - self.state = "connected" - self.on_open() - - def handle_close(self): - """asyncore callback when a connection is closed.""" - logger.debug("Closing connection to: %s:%d" % (self.dstaddr, self.dstport)) - self.state = "closed" + def connection_made(self, transport): + """asyncio callback when a connection is opened.""" + assert not self._transport + logger.debug("Connected & Listening: %s:%d" % (self.dstaddr, self.dstport)) + self._transport = transport + if self.on_connection_send_msg: + self.send_message(self.on_connection_send_msg) + self.on_connection_send_msg = None # Never used again + self.on_open() + + def connection_lost(self, exc): + """asyncio callback when a connection is closed.""" + if exc: + logger.warning("Connection lost to {}:{} due to {}".format(self.dstaddr, self.dstport, exc)) + else: + logger.debug("Closed connection to: %s:%d" % (self.dstaddr, self.dstport)) + self._transport = None self.recvbuf = b"" - self.sendbuf = b"" - try: - self.close() - except: - pass self.on_close() - def disconnect_node(self): - """Disconnect the p2p connection. - - Called by the test logic thread. Causes the p2p connection - to be disconnected on the next iteration of the asyncore loop.""" - self.disconnect = True - # Socket read methods - def handle_read(self): - """asyncore callback when data is read from the socket.""" - t = self.recv(8192) + def data_received(self, t): + """asyncio callback when data is read from the socket.""" if len(t) > 0: self.recvbuf += t self._on_data() @@ -179,39 +170,21 @@ class P2PConnection(asyncore.dispatcher): # Socket write methods - def writable(self): - """asyncore method to determine whether the handle_write() callback should be called on the next loop.""" - with mininode_lock: - pre_connection = self.state == "connecting" - length = len(self.sendbuf) - return (length > 0 or pre_connection) - - def handle_write(self): - """asyncore callback when data should be written to the socket.""" - with mininode_lock: - # asyncore does not expose socket connection, only the first read/write - # event, thus we must check connection manually here to know when we - # actually connect - if self.state == "connecting": - self.handle_connect() - if not self.writable(): - return - - try: - sent = self.send(self.sendbuf) - except: - self.handle_close() - return - self.sendbuf = self.sendbuf[sent:] - - def send_message(self, message, pushbuf=False): + def send_message(self, message): """Send a P2P message over the socket. This method takes a P2P payload, builds the P2P header and adds the message to the send buffer to be sent over the socket.""" - if self.state != "connected" and not pushbuf: - raise IOError('Not connected, no pushbuf') + if not self.is_connected: + raise IOError('Not connected') self._log_message("send", message) + tmsg = self._build_message(message) + NetworkThread.network_event_loop.call_soon_threadsafe(lambda: self._transport and not self._transport.is_closing() and self._transport.write(tmsg)) + + # Class utility methods + + def _build_message(self, message): + """Build a serialized P2P message""" command = message.command data = message.serialize() tmsg = MAGIC_BYTES[self.network] @@ -222,17 +195,7 @@ class P2PConnection(asyncore.dispatcher): h = sha256(th) tmsg += h[:4] tmsg += data - with mininode_lock: - if (len(self.sendbuf) == 0 and not pushbuf): - try: - sent = self.send(tmsg) - self.sendbuf = tmsg[sent:] - except BlockingIOError: - self.sendbuf = tmsg - else: - self.sendbuf += tmsg - - # Class utility methods + return tmsg def _log_message(self, direction, msg): """Logs a message being sent or received over the connection.""" @@ -270,7 +233,7 @@ class P2PInterface(P2PConnection): self.nServices = 0 def peer_connect(self, *args, services=NODE_NETWORK|NODE_WITNESS, send_version=True, **kwargs): - super().peer_connect(*args, **kwargs) + create_conn = super().peer_connect(*args, **kwargs) if send_version: # Send a version msg @@ -280,7 +243,9 @@ class P2PInterface(P2PConnection): vt.addrTo.port = self.dstport vt.addrFrom.ip = "0.0.0.0" vt.addrFrom.port = 0 - self.send_message(vt, True) + self.on_connection_send_msg = vt # Will be sent soon after connection_made + + return create_conn # Message receiving methods @@ -348,7 +313,7 @@ class P2PInterface(P2PConnection): # Connection helper methods def wait_for_disconnect(self, timeout=60): - test_function = lambda: self.state != "connected" + test_function = lambda: not self.is_connected wait_until(test_function, timeout=timeout, lock=mininode_lock) # Message receiving helper methods @@ -404,56 +369,35 @@ class P2PInterface(P2PConnection): self.ping_counter += 1 -# Keep our own socket map for asyncore, so that we can track disconnects -# ourselves (to work around an issue with closing an asyncore socket when -# using select) -mininode_socket_map = dict() - -# One lock for synchronizing all data access between the networking thread (see +# One lock for synchronizing all data access between the network event loop (see # NetworkThread below) and the thread running the test logic. For simplicity, -# P2PConnection acquires this lock whenever delivering a message to a P2PInterface, -# and whenever adding anything to the send buffer (in send_message()). This -# lock should be acquired in the thread running the test logic to synchronize +# P2PConnection acquires this lock whenever delivering a message to a P2PInterface. +# This lock should be acquired in the thread running the test logic to synchronize # access to any data shared with the P2PInterface or P2PConnection. mininode_lock = threading.RLock() + class NetworkThread(threading.Thread): + network_event_loop = None + def __init__(self): super().__init__(name="NetworkThread") + # There is only one event loop and no more than one thread must be created + assert not self.network_event_loop + + NetworkThread.network_event_loop = asyncio.new_event_loop() def run(self): - while mininode_socket_map: - # We check for whether to disconnect outside of the asyncore - # loop to work around the behavior of asyncore when using - # select - disconnected = [] - for fd, obj in mininode_socket_map.items(): - if obj.disconnect: - disconnected.append(obj) - [obj.handle_close() for obj in disconnected] - asyncore.loop(0.1, use_poll=True, map=mininode_socket_map, count=1) - logger.debug("Network thread closing") - -def network_thread_start(): - """Start the network thread.""" - # Only one network thread may run at a time - assert not network_thread_running() - - NetworkThread().start() - -def network_thread_running(): - """Return whether the network thread is running.""" - return any([thread.name == "NetworkThread" for thread in threading.enumerate()]) - -def network_thread_join(timeout=10): - """Wait timeout seconds for the network thread to terminate. - - Throw if the network thread doesn't terminate in timeout seconds.""" - network_threads = [thread for thread in threading.enumerate() if thread.name == "NetworkThread"] - assert len(network_threads) <= 1 - for thread in network_threads: - thread.join(timeout) - assert not thread.is_alive() + """Start the network thread.""" + self.network_event_loop.run_forever() + + def close(self, timeout=10): + """Close the connections and network event loop.""" + self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop) + wait_until(lambda: not self.network_event_loop.is_running(), timeout=timeout) + self.network_event_loop.close() + self.join(timeout) + class P2PDataStore(P2PInterface): """A P2P data store class. @@ -554,13 +498,13 @@ class P2PDataStore(P2PInterface): if reject_reason is not None: wait_until(lambda: self.reject_reason_received == reject_reason, lock=mininode_lock) - def send_txs_and_test(self, txs, rpc, success=True, reject_code=None, reject_reason=None): + def send_txs_and_test(self, txs, rpc, success=True, expect_disconnect=False, reject_code=None, reject_reason=None): """Send txs to test node and test whether they're accepted to the mempool. - add all txs to our tx_store - send tx messages for all txs - - if success is True: assert that the tx is accepted to the mempool - - if success is False: assert that the tx is not accepted to the mempool + - if success is True/False: assert that the txs are/are not accepted to the mempool + - if expect_disconnect is True: Skip the sync with ping - if reject_code and reject_reason are set: assert that the correct reject message is received.""" with mininode_lock: @@ -573,7 +517,10 @@ class P2PDataStore(P2PInterface): for tx in txs: self.send_message(msg_tx(tx)) - self.sync_with_ping() + if expect_disconnect: + self.wait_for_disconnect() + else: + self.sync_with_ping() raw_mempool = rpc.getrawmempool() if success: diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 54ff9eb038..c2fb2077ac 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Base class for RPC testing.""" +import configparser from enum import Enum import logging import optparse @@ -17,6 +18,7 @@ import time from .authproxy import JSONRPCException from . import coverage from .test_node import TestNode +from .mininode import NetworkThread from .util import ( MAX_NODES, PortSeed, @@ -82,6 +84,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.setup_clean_chain = False self.nodes = [] + self.network_thread = None self.mocktime = 0 self.supports_cli = False self.bind_to_localhost_only = True @@ -97,10 +100,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="Leave bitcoinds and test.* datadir on exit or error") parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", help="Don't stop bitcoinds after the test execution") - parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"), - help="Source directory containing bitcoind/bitcoin-cli (default: %default)") - parser.add_option("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), - help="Directory for caching pregenerated datadirs") + parser.add_option("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), + help="Directory for caching pregenerated datadirs (default: %default)") parser.add_option("--tmpdir", dest="tmpdir", help="Root directory for datadirs") parser.add_option("-l", "--loglevel", dest="loglevel", default="INFO", help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.") @@ -111,7 +112,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): parser.add_option("--coveragedir", dest="coveragedir", help="Write tested RPC commands into this directory") parser.add_option("--configfile", dest="configfile", - help="Location of the test framework config file") + default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../config.ini"), + help="Location of the test framework config file (default: %default)") parser.add_option("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true", help="Attach a python debugger if test fails") parser.add_option("--usecli", dest="usecli", default=False, action="store_true", @@ -121,14 +123,21 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): PortSeed.n = self.options.port_seed - os.environ['PATH'] = self.options.srcdir + os.pathsep + \ - self.options.srcdir + os.path.sep + "qt" + os.pathsep + \ - os.environ['PATH'] - check_json_precision() self.options.cachedir = os.path.abspath(self.options.cachedir) + config = configparser.ConfigParser() + config.read_file(open(self.options.configfile)) + self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"]) + self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"]) + + os.environ['PATH'] = os.pathsep.join([ + os.path.join(config['environment']['BUILDDIR'], 'src'), + os.path.join(config['environment']['BUILDDIR'], 'src', 'qt'), + os.environ['PATH'] + ]) + # Set up temp directory and start logging if self.options.tmpdir: self.options.tmpdir = os.path.abspath(self.options.tmpdir) @@ -137,6 +146,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.options.tmpdir = tempfile.mkdtemp(prefix="test") self._start_logging() + self.log.debug('Setting up network thread') + self.network_thread = NetworkThread() + self.network_thread.start() + success = TestStatus.FAILED try: @@ -164,6 +177,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): print("Testcase failed. Attaching python debugger. Enter ? for help") pdb.set_trace() + self.log.debug('Closing down network thread') + self.network_thread.close() if not self.options.noshutdown: self.log.info("Stopping nodes") if self.nodes: @@ -246,12 +261,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if extra_args is None: extra_args = [[]] * num_nodes if binary is None: - binary = [None] * num_nodes + binary = [self.options.bitcoind] * num_nodes assert_equal(len(extra_confs), num_nodes) 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=timewait, binary=binary[i], stderr=None, 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=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)) def start_node(self, i, *args, **kwargs): """Start a bitcoind""" @@ -284,9 +299,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): for node in self.nodes: coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc) - def stop_node(self, i): + def stop_node(self, i, expected_stderr=''): """Stop a bitcoind test node""" - self.nodes[i].stop_node() + self.nodes[i].stop_node(expected_stderr) self.nodes[i].wait_until_stopped() def stop_nodes(self): @@ -352,7 +367,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.log = logging.getLogger('TestFramework') self.log.setLevel(logging.DEBUG) # Create file handler to log all messages - fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log') + fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log', encoding='utf-8') fh.setLevel(logging.DEBUG) # Create console handler to log messages to stderr. By default this logs only error messages, but can be configured with --loglevel. ch = logging.StreamHandler(sys.stdout) @@ -399,10 +414,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # Create cache directories, run bitcoinds: for i in range(MAX_NODES): datadir = initialize_datadir(self.options.cachedir, i) - args = [os.getenv("BITCOIND", "bitcoind"), "-datadir=" + datadir] + args = [self.options.bitcoind, "-datadir=" + datadir] 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=None, binary=None, stderr=None, 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=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=None)) self.nodes[i].args = args self.start_node(i) @@ -460,3 +475,20 @@ class SkipTest(Exception): """This exception is raised to skip a test""" def __init__(self, message): self.message = message + + +def skip_if_no_py3_zmq(): + """Attempt to import the zmq package and skip the test if the import fails.""" + try: + import zmq # noqa + except ImportError: + raise SkipTest("python3-zmq module not available.") + + +def skip_if_no_bitcoind_zmq(test_instance): + """Skip the running test if bitcoind has not been compiled with zmq support.""" + config = configparser.ConfigParser() + config.read_file(open(test_instance.options.configfile)) + + if not config["components"].getboolean("ENABLE_ZMQ"): + raise SkipTest("bitcoind has not been built with zmq enabled.") diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 54e533d6f6..287dc0e53e 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -56,20 +56,18 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, rpchost, timewait, binary, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False): + def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False): self.index = i self.datadir = datadir + self.stdout_dir = os.path.join(self.datadir, "stdout") + self.stderr_dir = os.path.join(self.datadir, "stderr") self.rpchost = rpchost if timewait: self.rpc_timeout = timewait else: # Wait for up to 60 seconds for the RPC server to respond self.rpc_timeout = 60 - if binary is None: - self.binary = os.getenv("BITCOIND", "bitcoind") - else: - self.binary = binary - self.stderr = stderr + self.binary = bitcoind self.coverage_dir = coverage_dir if extra_conf != None: append_config(datadir, extra_conf) @@ -85,11 +83,10 @@ class TestNode(): "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), - "-uacomment=testnode%d" % i, - "-noprinttoconsole" + "-uacomment=testnode%d" % i ] - self.cli = TestNodeCLI(os.getenv("BITCOINCLI", "bitcoin-cli"), self.datadir) + self.cli = TestNodeCLI(bitcoin_cli, self.datadir) self.use_cli = use_cli self.running = False @@ -128,17 +125,29 @@ class TestNode(): assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") return getattr(self.rpc, name) - def start(self, extra_args=None, stderr=None, *args, **kwargs): + def start(self, extra_args=None, stdout=None, stderr=None, *args, **kwargs): """Start the node.""" if extra_args is None: extra_args = self.extra_args + + # Add a new stdout and stderr file each time bitcoind is started if stderr is None: - stderr = self.stderr + stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) + if stdout is None: + stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) + self.stderr = stderr + self.stdout = stdout + # Delete any existing cookie file -- if such a file exists (eg due to # unclean shutdown), it will get overwritten anyway by bitcoind, and # potentially interfere with our attempt to authenticate delete_cookie_file(self.datadir) - self.process = subprocess.Popen(self.args + extra_args, stderr=stderr, *args, **kwargs) + + # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal + subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") + + self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, *args, **kwargs) + self.running = True self.log.debug("bitcoind started, waiting for RPC to come up") @@ -178,7 +187,7 @@ class TestNode(): wallet_path = "wallet/%s" % wallet_name return self.rpc / wallet_path - def stop_node(self): + def stop_node(self, expected_stderr=''): """Stop the node.""" if not self.running: return @@ -187,6 +196,13 @@ class TestNode(): self.stop() except http.client.CannotSendRequest: self.log.exception("Unable to stop node.") + + # Check that stderr is as expected + self.stderr.seek(0) + stderr = self.stderr.read().decode('utf-8').strip() + if stderr != expected_stderr: + raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) + del self.p2ps[:] def is_node_stopped(self): @@ -221,9 +237,10 @@ class TestNode(): Will throw if bitcoind starts without an error. Will throw if an expected_msg is provided and it does not match bitcoind's stdout.""" - with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: + with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \ + tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout: try: - self.start(extra_args, stderr=log_stderr, *args, **kwargs) + self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs) self.wait_for_rpc_connection() self.stop_node() self.wait_until_stopped() @@ -272,7 +289,7 @@ class TestNode(): if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' - p2p_conn.peer_connect(*args, **kwargs) + p2p_conn.peer_connect(*args, **kwargs)() self.p2ps.append(p2p_conn) return p2p_conn @@ -326,10 +343,10 @@ class TestNodeCLI(): def batch(self, requests): results = [] for request in requests: - try: - results.append(dict(result=request())) - except JSONRPCException as e: - results.append(dict(error=e)) + try: + results.append(dict(result=request())) + except JSONRPCException as e: + results.append(dict(error=e)) return results def send_cli(self, command=None, *args, **kwargs): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 4ec3175cd6..5e0b61b5e7 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -301,6 +301,9 @@ def initialize_datadir(dirname, n): f.write("keypool=1\n") f.write("discover=0\n") f.write("listenonion=0\n") + f.write("printtoconsole=0\n") + os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) + os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) return datadir def get_datadir_path(dirname, n): @@ -324,7 +327,7 @@ def get_auth_cookie(datadir): 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")): - with open(os.path.join(datadir, "regtest", ".cookie"), 'r') as f: + with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f: userpass = f.read() split_userpass = userpass.split(':') user = split_userpass[0] @@ -349,7 +352,14 @@ def set_node_times(nodes, t): def disconnect_nodes(from_connection, node_num): for peer_id in [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']]: - from_connection.disconnectnode(nodeid=peer_id) + try: + from_connection.disconnectnode(nodeid=peer_id) + except JSONRPCException as e: + # If this node is disconnected between calculating the peer id + # and issuing the disconnect, don't worry about it. + # This avoids a race condition if we're mass-disconnecting peers. + if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED + raise # wait to disconnect wait_until(lambda: [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5) @@ -556,3 +566,14 @@ def mine_large_block(node, utxos=None): fee = 100 * node.getnetworkinfo()["relayfee"] create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee) node.generate(1) + +def find_vout_for_address(node, txid, addr): + """ + Locate the vout index of the given transaction sending to the + given address. Raises runtime error exception if not found. + """ + tx = node.getrawtransaction(txid, True) + for i in range(len(tx["vout"])): + if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]): + return i + raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr)) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 5e86e7ce64..d8a09c22e5 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -99,6 +99,7 @@ BASE_SCRIPTS = [ 'wallet_multiwallet.py', 'wallet_multiwallet.py --usecli', 'interface_http.py', + 'rpc_psbt.py', 'rpc_users.py', 'feature_proxy.py', 'rpc_signrawtransaction.py', @@ -113,9 +114,11 @@ BASE_SCRIPTS = [ 'mining_prioritisetransaction.py', 'p2p_invalid_block.py', 'p2p_invalid_tx.py', + 'rpc_createmultisig.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', + 'rpc_zmq.py', 'rpc_signmessage.py', 'feature_nulldummy.py', 'mempool_accept.py', @@ -135,9 +138,12 @@ BASE_SCRIPTS = [ 'wallet_resendwallettransactions.py', 'wallet_fallbackfee.py', 'feature_minchainwork.py', + 'rpc_getblockstats.py', 'p2p_fingerprint.py', 'feature_uacomment.py', 'p2p_unrequested_blocks.py', + 'feature_includeconf.py', + 'rpc_scantxoutset.py', 'feature_logging.py', 'p2p_node_network_limited.py', 'feature_blocksdir.py', @@ -201,6 +207,7 @@ def main(): 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('--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() # args to be passed on always start with two dashes; tests are the remaining unknown args @@ -210,7 +217,7 @@ def main(): # Read config generated by configure. config = configparser.ConfigParser() configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini" - config.read_file(open(configfile)) + config.read_file(open(configfile, encoding="utf8")) passon_args.append("--configfile=%s" % configfile) @@ -283,9 +290,21 @@ def main(): 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"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args, args.combinedlogslen) + run_tests( + test_list, + config["environment"]["SRCDIR"], + config["environment"]["BUILDDIR"], + tmpdir, + jobs=args.jobs, + enable_coverage=args.coverage, + args=passon_args, + combined_logs_len=args.combinedlogslen, + failfast=args.failfast, + ) + +def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False): + args = args or [] -def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[], combined_logs_len=0): # Warn if bitcoind is already running (unix only) try: if subprocess.check_output(["pidof", "bitcoind"]) is not None: @@ -298,15 +317,9 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove if os.path.isdir(cache_dir): print("%sWARNING!%s There is a cache directory here: %s. If tests fail unexpectedly, try deleting the cache directory." % (BOLD[1], BOLD[0], cache_dir)) - #Set env vars - if "BITCOIND" not in os.environ: - os.environ["BITCOIND"] = build_dir + '/src/bitcoind' + exeext - os.environ["BITCOINCLI"] = build_dir + '/src/bitcoin-cli' + exeext - tests_dir = src_dir + '/test/functional/' - flags = ["--srcdir={}/src".format(build_dir)] + args - flags.append("--cachedir=%s" % cache_dir) + flags = ['--cachedir={}'.format(cache_dir)] + args if enable_coverage: coverage = RPCCoverage() @@ -351,6 +364,10 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove combined_logs, _ = subprocess.Popen([sys.executable, os.path.join(tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate() print("\n".join(deque(combined_logs.splitlines(), combined_logs_len))) + if failfast: + logging.debug("Early exiting after test failure") + break + print_results(test_results, max_len_name, (int(time.time() - start_time))) if coverage: @@ -365,6 +382,10 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove all_passed = all(map(lambda test_result: test_result.was_successful, test_results)) + # This will be a no-op unless failfast is True in which case there may be dangling + # processes which need to be killed. + job_queue.kill_and_join() + sys.exit(not all_passed) def print_results(test_results, max_len_name, runtime): @@ -402,10 +423,6 @@ class TestHandler: self.test_list = test_list self.flags = flags self.num_running = 0 - # In case there is a graveyard of zombie bitcoinds, we can apply a - # pseudorandom offset to hopefully jump over them. - # (625 is PORT_RANGE/MAX_NODES) - self.portseed_offset = int(time.time() * 1000) % 625 self.jobs = [] def get_next(self): @@ -413,7 +430,7 @@ class TestHandler: # Add tests self.num_running += 1 test = self.test_list.pop(0) - portseed = len(self.test_list) + self.portseed_offset + portseed = len(self.test_list) portseed_arg = ["--portseed={}".format(portseed)] log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) @@ -455,6 +472,17 @@ class TestHandler: return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr print('.', end='', flush=True) + def kill_and_join(self): + """Send SIGKILL to all jobs and block until all have ended.""" + procs = [i[2] for i in self.jobs] + + for proc in procs: + proc.kill() + + for proc in procs: + proc.wait() + + class TestResult(): def __init__(self, name, status, time): self.name = name @@ -566,7 +594,7 @@ class RPCCoverage(): if not os.path.isfile(coverage_ref_filename): raise RuntimeError("No coverage reference found") - with open(coverage_ref_filename, 'r') as coverage_ref_file: + 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): @@ -575,7 +603,7 @@ class RPCCoverage(): coverage_filenames.add(os.path.join(root, filename)) for filename in coverage_filenames: - with open(filename, 'r') as coverage_file: + with open(filename, 'r', encoding="utf8") as coverage_file: covered_cmds.update([line.strip() for line in coverage_file.readlines()]) return all_cmds - covered_cmds diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index d5ef08d782..cf2120d4eb 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -70,9 +70,17 @@ class AbandonConflictTest(BitcoinTestFramework): signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) + # Create a child tx spending ABC2 + signed3_change = Decimal("24.999") + inputs = [ {"txid":txABC2, "vout":0} ] + outputs = { self.nodes[0].getnewaddress(): signed3_change } + signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + # note tx is never directly referenced, only abandoned as a child of the above + self.nodes[0].sendrawtransaction(signed3["hex"]) + # In mempool txs from self should increase balance from change newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("30") + Decimal("24.9996")) + assert_equal(newbalance, balance - Decimal("30") + signed3_change) balance = newbalance # Restart the node with a higher min relay fee so the parent tx is no longer in mempool @@ -87,7 +95,7 @@ class AbandonConflictTest(BitcoinTestFramework): # Not in mempool txs from self should only reduce balance # inputs are still spent, but change not received newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("24.9996")) + assert_equal(newbalance, balance - signed3_change) # Unconfirmed received funds that are not in mempool, also shouldn't show # up in unconfirmed balance unconfbalance = self.nodes[0].getunconfirmedbalance() + self.nodes[0].getbalance() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index 5d2428e6ef..7658d78383 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -280,7 +280,10 @@ class AddressTypeTest(BitcoinTestFramework): self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent') self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32') - self.log.info('getrawchangeaddress fails with invalid changetype argument') + self.log.info('test invalid address type arguments') + assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], None, '') + assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '') + assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '') assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23') self.log.info("Nodes with changetype=p2sh-segwit never use a P2WPKH change output") diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index bfe90957a7..431fec3738 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -3,23 +3,34 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet.""" +from decimal import Decimal +import time + from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import ( + assert_array_result, + assert_equal, + assert_fee_amount, + assert_raises_rpc_error, + connect_nodes_bi, + sync_blocks, + sync_mempools, + wait_until, +) class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True - self.extra_args = [['-deprecatedrpc=accounts']] * 4 def setup_network(self): - self.add_nodes(4, self.extra_args) + self.add_nodes(4) self.start_node(0) self.start_node(1) self.start_node(2) - connect_nodes_bi(self.nodes,0,1) - connect_nodes_bi(self.nodes,1,2) - connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + connect_nodes_bi(self.nodes, 0, 2) self.sync_all([self.nodes[0:3]]) def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size): @@ -53,6 +64,15 @@ 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) @@ -112,11 +132,20 @@ class WalletTest(BitcoinTestFramework): self.nodes[2].lockunspent(True, [unspent_0]) assert_equal(len(self.nodes[2].listlockunspent()), 0) assert_raises_rpc_error(-8, "Invalid parameter, unknown transaction", - self.nodes[2].lockunspent, False, - [{"txid": "0000000000000000000000000000000000", "vout": 0}]) + self.nodes[2].lockunspent, False, + [{"txid": "0000000000000000000000000000000000", "vout": 0}]) assert_raises_rpc_error(-8, "Invalid parameter, vout index out of bounds", - self.nodes[2].lockunspent, False, - [{"txid": unspent_0["txid"], "vout": 999}]) + self.nodes[2].lockunspent, False, + [{"txid": unspent_0["txid"], "vout": 999}]) + + # An output should be unlocked when spent + unspent_0 = self.nodes[1].listunspent()[0] + self.nodes[1].lockunspent(False, [unspent_0]) + tx = self.nodes[1].createrawtransaction([unspent_0], { self.nodes[1].getnewaddress() : 1 }) + tx = self.nodes[1].fundrawtransaction(tx)['hex'] + tx = self.nodes[1].signrawtransactionwithwallet(tx)["hex"] + self.nodes[1].sendrawtransaction(tx) + assert_equal(len(self.nodes[1].listlockunspent()), 0) # Have node1 generate 100 blocks (so node0 can recover the fee) self.nodes[1].generate(100) @@ -124,7 +153,7 @@ class WalletTest(BitcoinTestFramework): # node0 should end up with 100 btc in block rewards plus fees, but # minus the 21 plus fees sent to node2 - assert_equal(self.nodes[0].getbalance(), 100-21) + assert_equal(self.nodes[0].getbalance(), 100 - 21) assert_equal(self.nodes[2].getbalance(), 21) # Node0 should have two unspent outputs. @@ -138,8 +167,8 @@ class WalletTest(BitcoinTestFramework): for utxo in node0utxos: inputs = [] outputs = {} - inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]}) - outputs[self.nodes[2].getnewaddress("from1")] = utxo["amount"] - 3 + inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) + outputs[self.nodes[2].getnewaddress()] = utxo["amount"] - 3 raw_tx = self.nodes[0].createrawtransaction(inputs, outputs) txns_to_send.append(self.nodes[0].signrawtransactionwithwallet(raw_tx)) @@ -153,7 +182,6 @@ class WalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 94) - assert_equal(self.nodes[2].getbalance("from1"), 94-21) # Verify that a spent output cannot be locked anymore spent_0 = {"txid": node0utxos[0]["txid"], "vout": node0utxos[0]["vout"]} @@ -178,7 +206,7 @@ class WalletTest(BitcoinTestFramework): node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) # Sendmany 10 BTC - txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", []) + txid = self.nodes[2].sendmany('', {address: 10}, 0, "", []) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) node_0_bal += Decimal('10') @@ -186,7 +214,7 @@ class WalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].getbalance(), node_0_bal) # Sendmany 10 BTC with subtract fee from amount - txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address]) + txid = self.nodes[2].sendmany('', {address: 10}, 0, "", [address]) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) node_2_bal -= Decimal('10') @@ -215,91 +243,90 @@ class WalletTest(BitcoinTestFramework): 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 rawtx - #2. hex-changed one output to 0.0 - #3. sign and send - #4. check if recipient (node0) can list the zero value tx - usp = self.nodes[1].listunspent() - inputs = [{"txid":usp[0]['txid'], "vout":usp[0]['vout']}] + # check if we can list zero value tx as available coins + # 1. create raw_tx + # 2. hex-changed one output to 0.0 + # 3. sign and send + # 4. check if recipient (node0) can list the zero value tx + usp = self.nodes[1].listunspent(query_options={'minimumAmount': '49.998'})[0] + inputs = [{"txid": usp['txid'], "vout": usp['vout']}] outputs = {self.nodes[1].getnewaddress(): 49.998, self.nodes[0].getnewaddress(): 11.11} - rawTx = self.nodes[1].createrawtransaction(inputs, outputs).replace("c0833842", "00000000") #replace 11.11 with 0.0 (int32) - decRawTx = self.nodes[1].decoderawtransaction(rawTx) - signedRawTx = self.nodes[1].signrawtransactionwithwallet(rawTx) - decRawTx = self.nodes[1].decoderawtransaction(signedRawTx['hex']) - zeroValueTxid= decRawTx['txid'] - self.nodes[1].sendrawtransaction(signedRawTx['hex']) + raw_tx = self.nodes[1].createrawtransaction(inputs, outputs).replace("c0833842", "00000000") # replace 11.11 with 0.0 (int32) + signed_raw_tx = self.nodes[1].signrawtransactionwithwallet(raw_tx) + decoded_raw_tx = self.nodes[1].decoderawtransaction(signed_raw_tx['hex']) + zero_value_txid = decoded_raw_tx['txid'] + self.nodes[1].sendrawtransaction(signed_raw_tx['hex']) self.sync_all() - self.nodes[1].generate(1) #mine a block + self.nodes[1].generate(1) # mine a block self.sync_all() - unspentTxs = self.nodes[0].listunspent() #zero value tx must be in listunspents output + unspent_txs = self.nodes[0].listunspent() # zero value tx must be in listunspents output found = False - for uTx in unspentTxs: - if uTx['txid'] == zeroValueTxid: + for uTx in unspent_txs: + if uTx['txid'] == zero_value_txid: found = True assert_equal(uTx['amount'], Decimal('0')) assert(found) - #do some -walletbroadcast tests + # do some -walletbroadcast tests self.stop_nodes() self.start_node(0, ["-walletbroadcast=0"]) self.start_node(1, ["-walletbroadcast=0"]) self.start_node(2, ["-walletbroadcast=0"]) - connect_nodes_bi(self.nodes,0,1) - connect_nodes_bi(self.nodes,1,2) - connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + connect_nodes_bi(self.nodes, 0, 2) self.sync_all([self.nodes[0:3]]) - txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) - txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted) - self.nodes[1].generate(1) #mine a block, tx should not be in there + txid_not_broadcast = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) + tx_obj_not_broadcast = self.nodes[0].gettransaction(txid_not_broadcast) + self.nodes[1].generate(1) # mine a block, tx should not be in there self.sync_all([self.nodes[0:3]]) - assert_equal(self.nodes[2].getbalance(), node_2_bal) #should not be changed because tx was not broadcasted + assert_equal(self.nodes[2].getbalance(), node_2_bal) # should not be changed because tx was not broadcasted - #now broadcast from another node, mine a block, sync, and check the balance - self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex']) + # now broadcast from another node, mine a block, sync, and check the balance + self.nodes[1].sendrawtransaction(tx_obj_not_broadcast['hex']) self.nodes[1].generate(1) self.sync_all([self.nodes[0:3]]) node_2_bal += 2 - txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted) + tx_obj_not_broadcast = self.nodes[0].gettransaction(txid_not_broadcast) assert_equal(self.nodes[2].getbalance(), node_2_bal) - #create another tx - txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) + # create another tx + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) - #restart the nodes with -walletbroadcast=1 + # restart the nodes with -walletbroadcast=1 self.stop_nodes() self.start_node(0) self.start_node(1) self.start_node(2) - connect_nodes_bi(self.nodes,0,1) - connect_nodes_bi(self.nodes,1,2) - connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + connect_nodes_bi(self.nodes, 0, 2) sync_blocks(self.nodes[0:3]) self.nodes[0].generate(1) sync_blocks(self.nodes[0:3]) node_2_bal += 2 - #tx should be added to balance because after restarting the nodes tx should be broadcast + # tx should be added to balance because after restarting the nodes tx should be broadcast assert_equal(self.nodes[2].getbalance(), node_2_bal) - #send a tx with value in a string (PR#6380 +) - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2") - txObj = self.nodes[0].gettransaction(txId) - assert_equal(txObj['amount'], Decimal('-2')) + # send a tx with value in a string (PR#6380 +) + txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2") + tx_obj = self.nodes[0].gettransaction(txid) + assert_equal(tx_obj['amount'], Decimal('-2')) - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "0.0001") - txObj = self.nodes[0].gettransaction(txId) - assert_equal(txObj['amount'], Decimal('-0.0001')) + txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "0.0001") + tx_obj = self.nodes[0].gettransaction(txid) + assert_equal(tx_obj['amount'], Decimal('-0.0001')) - #check if JSON parser can handle scientific notation in strings - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1e-4") - txObj = self.nodes[0].gettransaction(txId) - assert_equal(txObj['amount'], Decimal('-0.0001')) + # check if JSON parser can handle scientific notation in strings + txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1e-4") + tx_obj = self.nodes[0].gettransaction(txid) + assert_equal(tx_obj['amount'], Decimal('-0.0001')) # This will raise an exception because the amount type is wrong assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4") @@ -322,8 +349,8 @@ class WalletTest(BitcoinTestFramework): # 4. Check that the unspents after import are not spendable assert_array_result(self.nodes[1].listunspent(), - {"address": address_to_import}, - {"spendable": False}) + {"address": address_to_import}, + {"spendable": False}) # 5. Import private key of the previously imported address on node1 priv_key = self.nodes[2].dumpprivkey(address_to_import) @@ -331,17 +358,17 @@ class WalletTest(BitcoinTestFramework): # 6. Check that the unspents are now spendable on node1 assert_array_result(self.nodes[1].listunspent(), - {"address": address_to_import}, - {"spendable": True}) + {"address": address_to_import}, + {"spendable": True}) # Mine a block from node0 to an address from node1 - cbAddr = self.nodes[1].getnewaddress() - blkHash = self.nodes[0].generatetoaddress(1, cbAddr)[0] - cbTxId = self.nodes[0].getblock(blkHash)['tx'][0] + coinbase_addr = self.nodes[1].getnewaddress() + block_hash = self.nodes[0].generatetoaddress(1, coinbase_addr)[0] + coinbase_txid = self.nodes[0].getblock(block_hash)['tx'][0] self.sync_all([self.nodes[0:3]]) # Check that the txid and balance is found by node1 - self.nodes[1].gettransaction(cbTxId) + self.nodes[1].gettransaction(coinbase_txid) # check if wallet or blockchain maintenance changes the balance self.sync_all([self.nodes[0:3]]) @@ -354,14 +381,14 @@ class WalletTest(BitcoinTestFramework): # - True: unicode escaped as \u.... # - False: unicode directly as UTF-8 for mode in [True, False]: - self.nodes[0].ensure_ascii = mode + self.nodes[0].rpc.ensure_ascii = mode # unicode check: Basic Multilingual Plane, Supplementary Plane respectively - for s in [u'рыба', u'𝅘𝅥𝅯']: - addr = self.nodes[0].getaccountaddress(s) - label = self.nodes[0].getaccount(addr) - assert_equal(label, s) - assert(s in self.nodes[0].listaccounts().keys()) - self.nodes[0].ensure_ascii = True # restore to default + for label in [u'рыба', u'𝅘𝅥𝅯']: + addr = self.nodes[0].getnewaddress() + self.nodes[0].setlabel(addr, label) + assert_equal(self.nodes[0].getaddressinfo(addr)['label'], label) + assert(label in self.nodes[0].listlabels()) + self.nodes[0].rpc.ensure_ascii = True # restore to default # maintenance tests maintenance = [ @@ -377,9 +404,9 @@ class WalletTest(BitcoinTestFramework): self.log.info("check " + m) self.stop_nodes() # set lower ancestor limit for later - self.start_node(0, [m, "-deprecatedrpc=accounts", "-limitancestorcount="+str(chainlimit)]) - self.start_node(1, [m, "-deprecatedrpc=accounts", "-limitancestorcount="+str(chainlimit)]) - self.start_node(2, [m, "-deprecatedrpc=accounts", "-limitancestorcount="+str(chainlimit)]) + self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)]) + self.start_node(1, [m, "-limitancestorcount=" + str(chainlimit)]) + self.start_node(2, [m, "-limitancestorcount=" + str(chainlimit)]) if m == '-reindex': # reindex will leave rpc warm up "early"; Wait for it to finish wait_until(lambda: [block_count] * 3 == [self.nodes[i].getblockcount() for i in range(3)]) @@ -400,7 +427,7 @@ class WalletTest(BitcoinTestFramework): self.nodes[0].generate(1) node0_balance = self.nodes[0].getbalance() # Split into two chains - rawtx = self.nodes[0].createrawtransaction([{"txid":singletxid, "vout":0}], {chain_addrs[0]:node0_balance/2-Decimal('0.01'), chain_addrs[1]:node0_balance/2-Decimal('0.01')}) + rawtx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], {chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')}) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) singletxid = self.nodes[0].sendrawtransaction(signedtx["hex"]) self.nodes[0].generate(1) @@ -411,10 +438,10 @@ class WalletTest(BitcoinTestFramework): # So we should be able to generate exactly chainlimit txs for each original output sending_addr = self.nodes[1].getnewaddress() txid_list = [] - for i in range(chainlimit*2): + for i in range(chainlimit * 2): txid_list.append(self.nodes[0].sendtoaddress(sending_addr, Decimal('0.0001'))) - assert_equal(self.nodes[0].getmempoolinfo()['size'], chainlimit*2) - assert_equal(len(txid_list), chainlimit*2) + assert_equal(self.nodes[0].getmempoolinfo()['size'], chainlimit * 2) + assert_equal(len(txid_list), chainlimit * 2) # Without walletrejectlongchains, we will still generate a txid # The tx will be stored in the wallet but not accepted to the mempool @@ -422,26 +449,26 @@ class WalletTest(BitcoinTestFramework): assert(extra_txid not in self.nodes[0].getrawmempool()) assert(extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()]) self.nodes[0].abandontransaction(extra_txid) - total_txs = len(self.nodes[0].listtransactions("*",99999)) + total_txs = len(self.nodes[0].listtransactions("*", 99999)) # Try with walletrejectlongchains # Double chain limit but require combining inputs, so we pass SelectCoinsMinConf self.stop_node(0) - self.start_node(0, extra_args=["-deprecatedrpc=accounts", "-walletrejectlongchains", "-limitancestorcount="+str(2*chainlimit)]) + self.start_node(0, extra_args=["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]) # wait for loadmempool timeout = 10 - while (timeout > 0 and len(self.nodes[0].getrawmempool()) < chainlimit*2): + while (timeout > 0 and len(self.nodes[0].getrawmempool()) < chainlimit * 2): time.sleep(0.5) timeout -= 0.5 - assert_equal(len(self.nodes[0].getrawmempool()), chainlimit*2) + assert_equal(len(self.nodes[0].getrawmempool()), chainlimit * 2) node0_balance = self.nodes[0].getbalance() # With walletrejectlongchains we will not create the tx and store it in our wallet. assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01')) # Verify nothing new in wallet - assert_equal(total_txs, len(self.nodes[0].listtransactions("*",99999))) + assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) # Test getaddressinfo. Note that these addresses are taken from disablewallet.py assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index fcc11abce0..f07041706a 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -31,7 +31,7 @@ class BumpFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [["-prematurewitness", "-walletprematurewitness", "-deprecatedrpc=addwitnessaddress", "-walletrbf={}".format(i)] + self.extra_args = [["-deprecatedrpc=addwitnessaddress", "-walletrbf={}".format(i)] for i in range(self.num_nodes)] def run_test(self): diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index 997f67ec7e..ba420ab2a6 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -36,10 +36,10 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): addr_keypath = comment.split(" addr=")[1] addr = addr_keypath.split(" ")[0] keypath = None - if keytype == "inactivehdmaster=1": + if keytype == "inactivehdseed=1": # ensure the old master is still available assert(hd_master_addr_old == addr) - elif keytype == "hdmaster=1": + elif keytype == "hdseed=1": # ensure we have generated a new hd master key assert(hd_master_addr_old != addr) hd_master_addr_ret = addr @@ -136,7 +136,7 @@ class WalletDumpTest(BitcoinTestFramework): assert_equal(found_addr, test_addr_count) assert_equal(found_script_addr, 2) assert_equal(found_addr_chg, 90*2 + 50) # old reserve keys are marked as change now - assert_equal(found_addr_rsv, 90*2) + assert_equal(found_addr_rsv, 90*2) assert_equal(witness_addr_ret, witness_addr) # Overwriting should fail diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py index e9cd052344..cc0a5175d5 100755 --- a/test/functional/wallet_fallbackfee.py +++ b/test/functional/wallet_fallbackfee.py @@ -21,7 +21,6 @@ class WalletRBFTest(BitcoinTestFramework): self.restart_node(0, extra_args=["-fallbackfee=0"]) assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)) assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1}))) - assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendfrom("", self.nodes[0].getnewaddress(), 1)) assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1})) if __name__ == '__main__': diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 8c754807e6..86abe0ca99 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -11,6 +11,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes_bi, + assert_raises_rpc_error ) @@ -28,7 +29,8 @@ class WalletHDTest(BitcoinTestFramework): connect_nodes_bi(self.nodes, 0, 1) # Make sure we use hd, keep masterkeyid - masterkeyid = self.nodes[1].getwalletinfo()['hdmasterkeyid'] + masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + assert_equal(masterkeyid, self.nodes[1].getwalletinfo()['hdmasterkeyid']) assert_equal(len(masterkeyid), 40) # create an internal key @@ -53,6 +55,7 @@ class WalletHDTest(BitcoinTestFramework): hd_add = self.nodes[1].getnewaddress() hd_info = self.nodes[1].getaddressinfo(hd_add) assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'") + assert_equal(hd_info["hdseedid"], masterkeyid) assert_equal(hd_info["hdmasterkeyid"], masterkeyid) self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].generate(1) @@ -82,6 +85,7 @@ class WalletHDTest(BitcoinTestFramework): hd_add_2 = self.nodes[1].getnewaddress() hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2) assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'") + assert_equal(hd_info_2["hdseedid"], masterkeyid) assert_equal(hd_info_2["hdmasterkeyid"], masterkeyid) assert_equal(hd_add, hd_add_2) connect_nodes_bi(self.nodes, 0, 1) @@ -120,5 +124,39 @@ class WalletHDTest(BitcoinTestFramework): assert_equal(keypath[0:7], "m/0'/1'") + # Generate a new HD seed on node 1 and make sure it is set + orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + self.nodes[1].sethdseed() + new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + assert orig_masterkeyid != new_masterkeyid + addr = self.nodes[1].getnewaddress() + assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool + self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key + + # Set a new HD seed on node 1 without flushing the keypool + new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) + orig_masterkeyid = new_masterkeyid + self.nodes[1].sethdseed(False, new_seed) + new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + assert orig_masterkeyid != new_masterkeyid + addr = self.nodes[1].getnewaddress() + assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid']) + assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool + + # Check that the next address is from the new seed + self.nodes[1].keypoolrefill(1) + next_addr = self.nodes[1].getnewaddress() + assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid']) + assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool + assert next_addr != addr + + # Sethdseed parameter validity + assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0) + assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif") + assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool") + assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True) + assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) + assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) + if __name__ == '__main__': WalletHDTest().main () diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index 6775d8b46d..4c0ffb938b 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -42,16 +42,15 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): def do_import(self, timestamp): """Call one key import RPC.""" + rescan = self.rescan == Rescan.yes if self.call == Call.single: if self.data == Data.address: - response = self.try_rpc(self.node.importaddress, self.address["address"], self.label, - self.rescan == Rescan.yes) + response = self.try_rpc(self.node.importaddress, address=self.address["address"], rescan=rescan) elif self.data == Data.pub: - response = self.try_rpc(self.node.importpubkey, self.address["pubkey"], self.label, - self.rescan == Rescan.yes) + response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan) elif self.data == Data.priv: - response = self.try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes) + response = self.try_rpc(self.node.importprivkey, privkey=self.key, rescan=rescan) assert_equal(response, None) elif self.call == Call.multi: @@ -62,30 +61,22 @@ 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 getbalance/listtransactions return expected values.""" + """Verify that listreceivedbyaddress returns expected values.""" - balance = self.node.getbalance(self.label, 0, True) - assert_equal(balance, self.expected_balance) - - txs = self.node.listtransactions(self.label, 10000, 0, 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) + assert_equal(address["confirmations"], confirmations) # Verify the transaction is correctly marked watchonly depending on # whether the transaction pays to an imported public key or # imported private key. The test setup ensures that transaction @@ -93,9 +84,9 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): # involvesWatchonly will be true if either the transaction output # or inputs are watchonly). if self.data != Data.priv: - assert_equal(tx["involvesWatchonly"], True) + assert_equal(address["involvesWatchonly"], True) else: - assert_equal("involvesWatchonly" not in tx, True) + assert_equal("involvesWatchonly" not in address, True) # List of Variants for each way a key or address could be imported. @@ -119,7 +110,7 @@ class ImportRescanTest(BitcoinTestFramework): self.num_nodes = 2 + len(IMPORT_NODES) def setup_network(self): - extra_args = [["-addresstype=legacy", '-deprecatedrpc=accounts'] for _ in range(self.num_nodes)] + extra_args = [["-addresstype=legacy"] for _ in range(self.num_nodes)] for i, import_node in enumerate(IMPORT_NODES, 2): if import_node.prune: extra_args[i] += ["-prune=1"] @@ -130,11 +121,10 @@ class ImportRescanTest(BitcoinTestFramework): connect_nodes(self.nodes[i], 0) def run_test(self): - # Create one transaction on node 0 with a unique amount and label for + # 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.label = "label {} {}".format(i, variant) - variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label)) + variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress()) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = 10 - (i + 1) / 4.0 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index d0ec290f36..256901884b 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -3,14 +3,18 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importprunedfunds and removeprunedfunds RPCs.""" +from decimal import Decimal + from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) class ImportPrunedFundsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [['-deprecatedrpc=accounts']] * 2 def run_test(self): self.log.info("Mining blocks...") @@ -24,18 +28,18 @@ class ImportPrunedFundsTest(BitcoinTestFramework): address2 = self.nodes[0].getnewaddress() # privkey address3 = self.nodes[0].getnewaddress() - address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey + address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey - #Check only one address + # Check only one address address_info = self.nodes[0].getaddressinfo(address1) assert_equal(address_info['ismine'], True) self.sync_all() - #Node 1 sync test - assert_equal(self.nodes[1].getblockcount(),101) + # Node 1 sync test + assert_equal(self.nodes[1].getblockcount(), 101) - #Address Test - before import + # Address Test - before import address_info = self.nodes[1].getaddressinfo(address1) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) @@ -48,7 +52,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) - #Send funds to self + # Send funds to self txnid1 = self.nodes[0].sendtoaddress(address1, 0.1) self.nodes[0].generate(1) rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex'] @@ -66,27 +70,25 @@ class ImportPrunedFundsTest(BitcoinTestFramework): self.sync_all() - #Import with no affiliated address + # Import with no affiliated address assert_raises_rpc_error(-5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1) - balance1 = self.nodes[1].getbalance("", 0, True) + balance1 = self.nodes[1].getbalance() assert_equal(balance1, Decimal(0)) - #Import with affiliated address with no rescan - self.nodes[1].importaddress(address2, "add2", False) + # Import with affiliated address with no rescan + self.nodes[1].importaddress(address=address2, rescan=False) self.nodes[1].importprunedfunds(rawtxn2, proof2) - balance2 = self.nodes[1].getbalance("add2", 0, True) - assert_equal(balance2, Decimal('0.05')) + assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] - #Import with private key with no rescan - self.nodes[1].importprivkey(privkey=address3_privkey, label="add3", rescan=False) + # Import with private key with no rescan + self.nodes[1].importprivkey(privkey=address3_privkey, rescan=False) self.nodes[1].importprunedfunds(rawtxn3, proof3) - balance3 = self.nodes[1].getbalance("add3", 0, False) + assert [tx for tx in self.nodes[1].listtransactions() if tx['txid'] == txnid3] + balance3 = self.nodes[1].getbalance() assert_equal(balance3, Decimal('0.025')) - balance3 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance3, Decimal('0.075')) - #Addresses Test - after import + # Addresses Test - after import address_info = self.nodes[1].getaddressinfo(address1) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) @@ -97,19 +99,15 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], True) - #Remove transactions + # Remove transactions assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1) - - balance1 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance1, Decimal('0.075')) + assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid1] self.nodes[1].removeprunedfunds(txnid2) - balance2 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance2, Decimal('0.025')) + assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] self.nodes[1].removeprunedfunds(txnid3) - balance3 = self.nodes[1].getbalance("*", 0, True) - assert_equal(balance3, Decimal('0.0')) + assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid3] if __name__ == '__main__': ImportPrunedFundsTest().main() diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 505014e48f..1285515dfc 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -16,7 +16,8 @@ class KeyPoolTest(BitcoinTestFramework): addr_before_encrypting = nodes[0].getnewaddress() addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting) wallet_info_old = nodes[0].getwalletinfo() - assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info_old['hdmasterkeyid']) + assert_equal(wallet_info_old['hdseedid'], wallet_info_old['hdmasterkeyid']) + assert(addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']) # Encrypt wallet and wait to terminate nodes[0].node_encrypt_wallet('test') @@ -26,8 +27,9 @@ class KeyPoolTest(BitcoinTestFramework): addr = nodes[0].getnewaddress() addr_data = nodes[0].getaddressinfo(addr) wallet_info = nodes[0].getwalletinfo() - assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) - assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) + assert_equal(wallet_info['hdseedid'], wallet_info['hdmasterkeyid']) + assert(addr_before_encrypting_data['hdseedid'] != wallet_info['hdseedid']) + assert(addr_data['hdseedid'] == wallet_info['hdseedid']) assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min) diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index ab1493dd04..d657dc30c4 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -25,7 +25,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [['-deprecatedrpc=accounts'], ['-deprecatedrpc=accounts', '-keypool=100', '-keypoolmin=20']] + self.extra_args = [[], ['-keypool=100']] def run_test(self): wallet_path = os.path.join(self.nodes[1].datadir, "regtest", "wallets", "wallet.dat") diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 705dd8985e..8d961fb34a 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -5,7 +5,7 @@ """Test label RPCs. RPCs tested are: - - getlabeladdress + - getaccountaddress - getaddressesbyaccount/getaddressesbylabel - listaddressgroupings - setlabel @@ -85,24 +85,32 @@ class WalletLabelsTest(BitcoinTestFramework): # we want to reset so that the "" label has what's expected. # otherwise we're off by exactly the fee amount as that's mined # and matures in the next 100 blocks - node.sendfrom("", common_address, fee) + if accounts_api: + node.sendfrom("", common_address, fee) amount_to_send = 1.0 # Create labels and make sure subsequent label API calls # recognize the label/address associations. labels = [Label(name, accounts_api) for name in ("a", "b", "c", "d", "e")] for label in labels: - label.add_receive_address(node.getlabeladdress(label=label.name, force=True)) + if accounts_api: + address = node.getaccountaddress(label.name) + else: + address = node.getnewaddress(label.name) + label.add_receive_address(address) label.verify(node) # Check all labels are returned by listlabels. assert_equal(node.listlabels(), [label.name for label in labels]) # Send a transaction to each label, and make sure this forces - # getlabeladdress to generate a new receiving address. + # getaccountaddress to generate a new receiving address. for label in labels: - node.sendtoaddress(label.receive_address, amount_to_send) - label.add_receive_address(node.getlabeladdress(label.name)) + if accounts_api: + node.sendtoaddress(label.receive_address, amount_to_send) + label.add_receive_address(node.getaccountaddress(label.name)) + else: + node.sendtoaddress(label.addresses[0], amount_to_send) label.verify(node) # Check the amounts received. @@ -115,10 +123,17 @@ class WalletLabelsTest(BitcoinTestFramework): # Check that sendfrom label reduces listaccounts balances. for i, label in enumerate(labels): to_label = labels[(i + 1) % len(labels)] - node.sendfrom(label.name, to_label.receive_address, amount_to_send) + if accounts_api: + node.sendfrom(label.name, to_label.receive_address, amount_to_send) + else: + node.sendtoaddress(to_label.addresses[0], amount_to_send) node.generate(1) for label in labels: - label.add_receive_address(node.getlabeladdress(label.name)) + if accounts_api: + address = node.getaccountaddress(label.name) + else: + address = node.getnewaddress(label.name) + label.add_receive_address(address) label.verify(node) assert_equal(node.getreceivedbylabel(label.name), 2) if accounts_api: @@ -134,12 +149,12 @@ class WalletLabelsTest(BitcoinTestFramework): # Check that setlabel can assign a label to a new unused address. for label in labels: - address = node.getlabeladdress(label="", force=True) + address = node.getnewaddress() node.setlabel(address, label.name) label.add_address(address) label.verify(node) if accounts_api: - assert(address not in node.getaddressesbyaccount("")) + assert address not in node.getaddressesbyaccount("") else: assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "") @@ -152,7 +167,8 @@ class WalletLabelsTest(BitcoinTestFramework): label.add_address(multisig_address) label.purpose[multisig_address] = "send" label.verify(node) - node.sendfrom("", multisig_address, 50) + if accounts_api: + node.sendfrom("", multisig_address, 50) node.generate(101) if accounts_api: for label in labels: @@ -160,19 +176,20 @@ class WalletLabelsTest(BitcoinTestFramework): # Check that setlabel can change the label of an address from a # different label. - change_label(node, labels[0].addresses[0], labels[0], labels[1]) - - # Check that setlabel can change the label of an address which - # is the receiving address of a different label. - change_label(node, labels[0].receive_address, labels[0], labels[1]) + change_label(node, labels[0].addresses[0], labels[0], labels[1], accounts_api) # Check that setlabel can set the label of an address already # in the label. This is a no-op. - change_label(node, labels[2].addresses[0], labels[2], labels[2]) + change_label(node, labels[2].addresses[0], labels[2], labels[2], accounts_api) - # Check that setlabel can set the label of an address which is - # already the receiving address of the label. This is a no-op. - change_label(node, labels[2].receive_address, labels[2], labels[2]) + if accounts_api: + # Check that setaccount can change the label of an address which + # is the receiving address of a different label. + change_label(node, labels[0].receive_address, labels[0], labels[1], accounts_api) + + # Check that setaccount can set the label of an address which is + # already the receiving address of the label. This is a no-op. + change_label(node, labels[2].receive_address, labels[2], labels[2], accounts_api) class Label: def __init__(self, name, accounts_api): @@ -192,12 +209,14 @@ class Label: def add_receive_address(self, address): self.add_address(address) - self.receive_address = address + if self.accounts_api: + self.receive_address = address def verify(self, node): if self.receive_address is not None: assert self.receive_address in self.addresses - assert_equal(node.getlabeladdress(self.name), self.receive_address) + if self.accounts_api: + assert_equal(node.getaccountaddress(self.name), self.receive_address) for address in self.addresses: assert_equal( @@ -216,22 +235,26 @@ class Label: assert_equal(set(node.getaddressesbyaccount(self.name)), set(self.addresses)) -def change_label(node, address, old_label, new_label): +def change_label(node, address, old_label, new_label, accounts_api): assert_equal(address in old_label.addresses, True) - node.setlabel(address, new_label.name) + if accounts_api: + node.setaccount(address, new_label.name) + else: + node.setlabel(address, new_label.name) old_label.addresses.remove(address) new_label.add_address(address) - # Calling setlabel on an address which was previously the receiving - # address of a different label should reset the receiving address of - # the old label, causing getlabeladdress to return a brand new + # Calling setaccount on an address which was previously the receiving + # address of a different account should reset the receiving address of + # the old account, causing getaccountaddress to return a brand new # address. - if old_label.name != new_label.name and address == old_label.receive_address: - new_address = node.getlabeladdress(old_label.name) - assert_equal(new_address not in old_label.addresses, True) - assert_equal(new_address not in new_label.addresses, True) - old_label.add_receive_address(new_address) + if accounts_api: + if old_label.name != new_label.name and address == old_label.receive_address: + new_address = node.getaccountaddress(old_label.name) + assert_equal(new_address not in old_label.addresses, True) + assert_equal(new_address not in new_label.addresses, True) + old_label.add_receive_address(new_address) old_label.verify(node) new_label.verify(node) diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index aba5d642bc..e9912f994e 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -6,19 +6,22 @@ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import (assert_array_result, - assert_equal, - assert_raises_rpc_error, - ) +from test_framework.util import ( + assert_array_result, + assert_equal, + assert_raises_rpc_error, + sync_blocks, +) + class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [['-deprecatedrpc=accounts']] * 2 def run_test(self): # Generate block to get out of IBD self.nodes[0].generate(1) + sync_blocks(self.nodes) self.log.info("listreceivedbyaddress Test") @@ -51,37 +54,37 @@ class ReceivedByTest(BitcoinTestFramework): {"address": empty_addr}, {"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []}) - #Test Address filtering - #Only on addr - expected = {"address":addr, "label":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]} + # Test Address filtering + # Only on addr + expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]} res = self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) - assert_array_result(res, {"address":addr}, expected) + assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) - #Error on invalid address + # 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 + # Another address receive money res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 2) #Right now 2 entries + assert_equal(len(res), 2) # Right now 2 entries other_addr = self.nodes[1].getnewaddress() txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1) self.nodes[0].generate(1) self.sync_all() - #Same test as above should still pass - expected = {"address":addr, "label":"", "amount":Decimal("0.1"), "confirmations":11, "txids":[txid,]} + # Same test as above should still pass + expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 11, "txids": [txid, ]} res = self.nodes[1].listreceivedbyaddress(0, True, True, addr) - assert_array_result(res, {"address":addr}, expected) + assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) - #Same test as above but with other_addr should still pass - expected = {"address":other_addr, "label":"", "amount":Decimal("0.1"), "confirmations":1, "txids":[txid2,]} + # Same test as above but with other_addr should still pass + expected = {"address": other_addr, "label": "", "amount": Decimal("0.1"), "confirmations": 1, "txids": [txid2, ]} res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) - assert_array_result(res, {"address":other_addr}, expected) + assert_array_result(res, {"address": other_addr}, expected) assert_equal(len(res), 1) - #Should be two entries though without filter + # Should be two entries though without filter res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 3) #Became 3 entries + assert_equal(len(res), 3) # Became 3 entries - #Not on random addr - other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr + # Not on random addr + other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) assert_equal(len(res), 0) @@ -112,8 +115,9 @@ class ReceivedByTest(BitcoinTestFramework): self.log.info("listreceivedbylabel + getreceivedbylabel Test") # set pre-state - addrArr = self.nodes[1].getnewaddress() - label = self.nodes[1].getaccount(addrArr) + label = '' + address = self.nodes[1].getnewaddress() + assert_equal(self.nodes[1].getaddressinfo(address)['label'], label) received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label][0] balance_by_label = self.nodes[1].getreceivedbylabel(label) @@ -141,7 +145,8 @@ class ReceivedByTest(BitcoinTestFramework): assert_equal(balance, balance_by_label + Decimal("0.1")) # Create a new label named "mynewlabel" that has a 0 balance - self.nodes[1].getlabeladdress(label="mynewlabel", force=True) + address = self.nodes[1].getnewaddress() + self.nodes[1].setlabel(address, "mynewlabel") received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel(0, True) if r["label"] == "mynewlabel"][0] # Test includeempty of listreceivedbylabel diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 930bfcd7b0..63c179411c 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -11,7 +11,6 @@ class ListSinceBlockTest (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True - self.extra_args = [['-deprecatedrpc=accounts']] * 4 def run_test(self): self.nodes[2].generate(101) @@ -150,26 +149,26 @@ class ListSinceBlockTest (BitcoinTestFramework): # send from nodes[1] using utxo to nodes[0] change = '%.8f' % (float(utxo['amount']) - 1.0003) - recipientDict = { + recipient_dict = { self.nodes[0].getnewaddress(): 1, self.nodes[1].getnewaddress(): change, } - utxoDicts = [{ + utxo_dicts = [{ 'txid': utxo['txid'], 'vout': utxo['vout'], }] txid1 = self.nodes[1].sendrawtransaction( self.nodes[1].signrawtransactionwithwallet( - self.nodes[1].createrawtransaction(utxoDicts, recipientDict))['hex']) + self.nodes[1].createrawtransaction(utxo_dicts, recipient_dict))['hex']) # send from nodes[2] using utxo to nodes[3] - recipientDict2 = { + recipient_dict2 = { self.nodes[3].getnewaddress(): 1, self.nodes[2].getnewaddress(): change, } self.nodes[2].sendrawtransaction( self.nodes[2].signrawtransactionwithwallet( - self.nodes[2].createrawtransaction(utxoDicts, recipientDict2))['hex']) + self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict2))['hex']) # generate on both sides lastblockhash = self.nodes[1].generate(3)[2] @@ -225,16 +224,16 @@ class ListSinceBlockTest (BitcoinTestFramework): utxos = self.nodes[2].listunspent() utxo = utxos[0] change = '%.8f' % (float(utxo['amount']) - 1.0003) - recipientDict = { + recipient_dict = { self.nodes[0].getnewaddress(): 1, self.nodes[2].getnewaddress(): change, } - utxoDicts = [{ + utxo_dicts = [{ 'txid': utxo['txid'], 'vout': utxo['vout'], }] signedtxres = self.nodes[2].signrawtransactionwithwallet( - self.nodes[2].createrawtransaction(utxoDicts, recipientDict)) + self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict)) assert signedtxres['complete'] signedtx = signedtxres['hex'] diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 5466bbf18b..7cf2e456cf 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -3,13 +3,20 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listtransactions API.""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -from test_framework.mininode import CTransaction, COIN +from decimal import Decimal from io import BytesIO -def txFromHex(hexstring): +from test_framework.mininode import CTransaction, COIN +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_array_result, + assert_equal, + bytes_to_hex_str, + hex_str_to_bytes, + sync_mempools, +) + +def tx_from_hex(hexstring): tx = CTransaction() f = BytesIO(hex_str_to_bytes(hexstring)) tx.deserialize(f) @@ -18,7 +25,6 @@ def txFromHex(hexstring): class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [['-deprecatedrpc=accounts']] * 2 self.enable_mocktime() def run_test(self): @@ -26,61 +32,61 @@ class ListTransactionsTest(BitcoinTestFramework): txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) self.sync_all() assert_array_result(self.nodes[0].listtransactions(), - {"txid":txid}, - {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0}) + {"txid": txid}, + {"category": "send", "amount": Decimal("-0.1"), "confirmations": 0}) assert_array_result(self.nodes[1].listtransactions(), - {"txid":txid}, - {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0}) + {"txid": txid}, + {"category": "receive", "amount": Decimal("0.1"), "confirmations": 0}) # mine a block, confirmations should change: self.nodes[0].generate(1) self.sync_all() assert_array_result(self.nodes[0].listtransactions(), - {"txid":txid}, - {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1}) + {"txid": txid}, + {"category": "send", "amount": Decimal("-0.1"), "confirmations": 1}) assert_array_result(self.nodes[1].listtransactions(), - {"txid":txid}, - {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1}) + {"txid": txid}, + {"category": "receive", "amount": Decimal("0.1"), "confirmations": 1}) # send-to-self: txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) assert_array_result(self.nodes[0].listtransactions(), - {"txid":txid, "category":"send"}, - {"amount":Decimal("-0.2")}) + {"txid": txid, "category": "send"}, + {"amount": Decimal("-0.2")}) assert_array_result(self.nodes[0].listtransactions(), - {"txid":txid, "category":"receive"}, - {"amount":Decimal("0.2")}) + {"txid": txid, "category": "receive"}, + {"amount": Decimal("0.2")}) # sendmany from node1: twice to self, twice to node2: - send_to = { self.nodes[0].getnewaddress() : 0.11, - self.nodes[1].getnewaddress() : 0.22, - self.nodes[0].getaccountaddress("from1") : 0.33, - self.nodes[1].getaccountaddress("toself") : 0.44 } + send_to = {self.nodes[0].getnewaddress(): 0.11, + self.nodes[1].getnewaddress(): 0.22, + self.nodes[0].getnewaddress(): 0.33, + self.nodes[1].getnewaddress(): 0.44} txid = self.nodes[1].sendmany("", send_to) self.sync_all() assert_array_result(self.nodes[1].listtransactions(), - {"category":"send","amount":Decimal("-0.11")}, - {"txid":txid} ) + {"category": "send", "amount": Decimal("-0.11")}, + {"txid": txid}) assert_array_result(self.nodes[0].listtransactions(), - {"category":"receive","amount":Decimal("0.11")}, - {"txid":txid} ) + {"category": "receive", "amount": Decimal("0.11")}, + {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category":"send","amount":Decimal("-0.22")}, - {"txid":txid} ) + {"category": "send", "amount": Decimal("-0.22")}, + {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category":"receive","amount":Decimal("0.22")}, - {"txid":txid} ) + {"category": "receive", "amount": Decimal("0.22")}, + {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category":"send","amount":Decimal("-0.33")}, - {"txid":txid} ) + {"category": "send", "amount": Decimal("-0.33")}, + {"txid": txid}) assert_array_result(self.nodes[0].listtransactions(), - {"category":"receive","amount":Decimal("0.33")}, - {"txid":txid, "account" : "from1"} ) + {"category": "receive", "amount": Decimal("0.33")}, + {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category":"send","amount":Decimal("-0.44")}, - {"txid":txid, "account" : ""} ) + {"category": "send", "amount": Decimal("-0.44")}, + {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category":"receive","amount":Decimal("0.44")}, - {"txid":txid, "account" : "toself"} ) + {"category": "receive", "amount": Decimal("0.44")}, + {"txid": txid}) pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey'] multisig = self.nodes[1].createmultisig(1, [pubkey]) @@ -88,10 +94,9 @@ class ListTransactionsTest(BitcoinTestFramework): txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) self.nodes[1].generate(1) self.sync_all() - assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0) - assert_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True), - {"category":"receive","amount":Decimal("0.1")}, - {"txid":txid, "account" : "watchonly"} ) + 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}) self.run_rbf_opt_in_test() @@ -117,9 +122,9 @@ class ListTransactionsTest(BitcoinTestFramework): # 1. Chain a few transactions that don't opt-in. txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) assert(not is_opt_in(self.nodes[0], txid_1)) - assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) + assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable": "no"}) sync_mempools(self.nodes) - assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"}) + assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable": "no"}) # Tx2 will build off txid_1, still not opting in to RBF. utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_1) @@ -129,7 +134,7 @@ class ListTransactionsTest(BitcoinTestFramework): assert_equal(utxo_to_use["safe"], False) # Create tx2 using createrawtransaction - inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}] + inputs = [{"txid": utxo_to_use["txid"], "vout": utxo_to_use["vout"]}] outputs = {self.nodes[0].getnewaddress(): 0.999} tx2 = self.nodes[1].createrawtransaction(inputs, outputs) tx2_signed = self.nodes[1].signrawtransactionwithwallet(tx2)["hex"] @@ -137,51 +142,51 @@ class ListTransactionsTest(BitcoinTestFramework): # ...and check the result assert(not is_opt_in(self.nodes[1], txid_2)) - assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) + assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable": "no"}) sync_mempools(self.nodes) - assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"}) + assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable": "no"}) # Tx3 will opt-in to RBF utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2) - inputs = [{"txid": txid_2, "vout":utxo_to_use["vout"]}] + inputs = [{"txid": txid_2, "vout": utxo_to_use["vout"]}] outputs = {self.nodes[1].getnewaddress(): 0.998} tx3 = self.nodes[0].createrawtransaction(inputs, outputs) - tx3_modified = txFromHex(tx3) + tx3_modified = tx_from_hex(tx3) tx3_modified.vin[0].nSequence = 0 tx3 = bytes_to_hex_str(tx3_modified.serialize()) tx3_signed = self.nodes[0].signrawtransactionwithwallet(tx3)['hex'] txid_3 = self.nodes[0].sendrawtransaction(tx3_signed) assert(is_opt_in(self.nodes[0], txid_3)) - assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) + assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable": "yes"}) sync_mempools(self.nodes) - assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"}) + assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable": "yes"}) # Tx4 will chain off tx3. Doesn't signal itself, but depends on one # that does. utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3) - inputs = [{"txid": txid_3, "vout":utxo_to_use["vout"]}] + inputs = [{"txid": txid_3, "vout": utxo_to_use["vout"]}] outputs = {self.nodes[0].getnewaddress(): 0.997} tx4 = self.nodes[1].createrawtransaction(inputs, outputs) tx4_signed = self.nodes[1].signrawtransactionwithwallet(tx4)["hex"] txid_4 = self.nodes[1].sendrawtransaction(tx4_signed) assert(not is_opt_in(self.nodes[1], txid_4)) - assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) + assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "yes"}) sync_mempools(self.nodes) - assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"}) + assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "yes"}) # Replace tx3, and check that tx4 becomes unknown tx3_b = tx3_modified - tx3_b.vout[0].nValue -= int(Decimal("0.004") * COIN) # bump the fee + tx3_b.vout[0].nValue -= int(Decimal("0.004") * COIN) # bump the fee tx3_b = bytes_to_hex_str(tx3_b.serialize()) tx3_b_signed = self.nodes[0].signrawtransactionwithwallet(tx3_b)['hex'] txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True) assert(is_opt_in(self.nodes[0], txid_3b)) - assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) + assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "unknown"}) sync_mempools(self.nodes) - assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"}) + assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "unknown"}) # Check gettransaction as well: for n in self.nodes[0:2]: @@ -197,7 +202,5 @@ class ListTransactionsTest(BitcoinTestFramework): assert_equal(self.nodes[0].gettransaction(txid_3b)["bip125-replaceable"], "no") assert_equal(self.nodes[0].gettransaction(txid_4)["bip125-replaceable"], "unknown") - if __name__ == '__main__': ListTransactionsTest().main() - diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index e0571ea8f9..fa5a2154a4 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -88,7 +88,7 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') # should not initialize if the specified walletdir is not a directory not_a_dir = wallet_dir('notadir') - open(not_a_dir, 'a').close() + open(not_a_dir, 'a', encoding="utf8").close() self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory') self.log.info("Do not allow -zapwallettxes with multiwallet") @@ -170,5 +170,100 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(w1.getwalletinfo()['paytxfee'], 0) assert_equal(w2.getwalletinfo()['paytxfee'], 4.0) + self.log.info("Test dynamic wallet loading") + + self.restart_node(0, ['-nowallet']) + assert_equal(node.listwallets(), []) + assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo) + + self.log.info("Load first wallet") + loadwallet_name = node.loadwallet(wallet_names[0]) + assert_equal(loadwallet_name['name'], wallet_names[0]) + assert_equal(node.listwallets(), wallet_names[0:1]) + node.getwalletinfo() + w1 = node.get_wallet_rpc(wallet_names[0]) + w1.getwalletinfo() + + self.log.info("Load second wallet") + loadwallet_name = node.loadwallet(wallet_names[1]) + assert_equal(loadwallet_name['name'], wallet_names[1]) + assert_equal(node.listwallets(), wallet_names[0:2]) + assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) + w2 = node.get_wallet_rpc(wallet_names[1]) + w2.getwalletinfo() + + self.log.info("Load remaining wallets") + for wallet_name in wallet_names[2:]: + loadwallet_name = self.nodes[0].loadwallet(wallet_name) + assert_equal(loadwallet_name['name'], wallet_name) + + assert_equal(set(self.nodes[0].listwallets()), set(wallet_names)) + + # Fail to load if wallet doesn't exist + assert_raises_rpc_error(-18, 'Wallet wallets not found.', self.nodes[0].loadwallet, 'wallets') + + # 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 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 wallet file is a 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')) + assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file", self.nodes[0].loadwallet, 'empty_wallet_dir') + + self.log.info("Test dynamic wallet creation.") + + # Fail to create a wallet if it already exists. + assert_raises_rpc_error(-4, "Wallet w2 already exists.", self.nodes[0].createwallet, 'w2') + + # Successfully create a wallet with a new name + loadwallet_name = self.nodes[0].createwallet('w9') + assert_equal(loadwallet_name['name'], 'w9') + w9 = node.get_wallet_rpc('w9') + assert_equal(w9.getwalletinfo()['walletname'], 'w9') + + assert 'w9' in self.nodes[0].listwallets() + + # Successfully create a wallet using a full path + new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir') + new_wallet_name = os.path.join(new_wallet_dir, 'w10') + loadwallet_name = self.nodes[0].createwallet(new_wallet_name) + assert_equal(loadwallet_name['name'], new_wallet_name) + w10 = node.get_wallet_rpc(new_wallet_name) + assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name) + + assert new_wallet_name in self.nodes[0].listwallets() + + self.log.info("Test dynamic wallet unloading") + + # Test `unloadwallet` errors + assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet) + assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy") + assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet) + assert_raises_rpc_error(-8, "Cannot unload the requested wallet", w1.unloadwallet, "w2"), + + # Successfully unload the specified wallet name + self.nodes[0].unloadwallet("w1") + assert 'w1' not in self.nodes[0].listwallets() + + # Successfully unload the wallet referenced by the request endpoint + w2.unloadwallet() + assert 'w2' not in self.nodes[0].listwallets() + + # Successfully unload all wallets + for wallet_name in self.nodes[0].listwallets(): + self.nodes[0].unloadwallet(wallet_name) + assert_equal(self.nodes[0].listwallets(), []) + assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)", self.nodes[0].getwalletinfo) + + # Successfully load a previously unloaded wallet + self.nodes[0].loadwallet('w1') + assert_equal(self.nodes[0].listwallets(), ['w1']) + assert_equal(w1.getwalletinfo()['walletname'], 'w1') + if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index aee84f7e90..72ae2d5db7 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -5,12 +5,16 @@ """Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import ( + assert_equal, + connect_nodes, + disconnect_nodes, + sync_blocks, +) class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 - self.extra_args = [['-deprecatedrpc=accounts']] * 4 def add_options(self, parser): parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true", @@ -26,55 +30,53 @@ class TxnMallTest(BitcoinTestFramework): def run_test(self): if self.options.segwit: - output_type="p2sh-segwit" + output_type = "p2sh-segwit" else: - output_type="legacy" + output_type = "legacy" # All nodes should start with 1,250 BTC: starting_balance = 1250 for i in range(4): assert_equal(self.nodes[i].getbalance(), starting_balance) - self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress! + self.nodes[i].getnewaddress() # bug workaround, coins generated assigned to first getnewaddress! - # Assign coins to foo and bar accounts: self.nodes[0].settxfee(.001) - node0_address_foo = self.nodes[0].getnewaddress("foo", output_type) - fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219) - fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) + node0_address1 = self.nodes[0].getnewaddress(address_type=output_type) + node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219) + node0_tx1 = self.nodes[0].gettransaction(node0_txid1) - node0_address_bar = self.nodes[0].getnewaddress("bar", output_type) - fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29) - fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) + node0_address2 = self.nodes[0].getnewaddress(address_type=output_type) + node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29) + node0_tx2 = self.nodes[0].gettransaction(node0_txid2) - assert_equal(self.nodes[0].getbalance(""), - starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"]) + assert_equal(self.nodes[0].getbalance(), + starting_balance + node0_tx1["fee"] + node0_tx2["fee"]) # Coins are sent to node1_address - node1_address = self.nodes[1].getnewaddress("from0") + node1_address = self.nodes[1].getnewaddress() - # Send tx1, and another transaction tx2 that won't be cloned - txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0) - txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0) + # Send tx1, and another transaction tx2 that won't be cloned + txid1 = self.nodes[0].sendtoaddress(node1_address, 40) + txid2 = self.nodes[0].sendtoaddress(node1_address, 20) - # 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_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][0]["value"], - rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][1]["value"]} + # 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_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"] clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime) # createrawtransaction randomizes the order of its outputs, so swap them if necessary. # output 0 is at version+#inputs+input+sigstub+sequence+#outputs # 40 BTC serialized is 00286bee00000000 - pos0 = 2*(4+1+36+1+4+1) + pos0 = 2 * (4 + 1 + 36 + 1 + 4 + 1) hex40 = "00286bee00000000" - output_len = 16 + 2 + 2 * int("0x" + clone_raw[pos0 + 16 : pos0 + 16 + 2], 0) - if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0 : pos0 + 16] != hex40 or - rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0 : pos0 + 16] == hex40): - output0 = clone_raw[pos0 : pos0 + output_len] - output1 = clone_raw[pos0 + output_len : pos0 + 2 * output_len] + output_len = 16 + 2 + 2 * int("0x" + clone_raw[pos0 + 16:pos0 + 16 + 2], 0) + if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0:pos0 + 16] != hex40 or rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0:pos0 + 16] == hex40): + output0 = clone_raw[pos0:pos0 + output_len] + output1 = clone_raw[pos0 + output_len:pos0 + 2 * output_len] clone_raw = clone_raw[:pos0] + output1 + output0 + clone_raw[pos0 + 2 * output_len:] # Use a different signature hash type to sign. This creates an equivalent but malleated clone. @@ -92,28 +94,22 @@ class TxnMallTest(BitcoinTestFramework): # Node0's balance should be starting balance, plus 50BTC for another # matured block, minus tx1 and tx2 amounts, and minus transaction fees: - expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"] + expected = starting_balance + node0_tx1["fee"] + node0_tx2["fee"] if self.options.mine_block: expected += 50 expected += tx1["amount"] + tx1["fee"] expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) - # foo and bar accounts should be debited: - assert_equal(self.nodes[0].getbalance("foo", 0), 1219 + tx1["amount"] + tx1["fee"]) - assert_equal(self.nodes[0].getbalance("bar", 0), 29 + tx2["amount"] + tx2["fee"]) - if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) - # Node1's "from0" balance should be both transaction amounts: - assert_equal(self.nodes[1].getbalance("from0"), -(tx1["amount"] + tx2["amount"])) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) # Send clone and its parent to miner - self.nodes[2].sendrawtransaction(fund_foo_tx["hex"]) + self.nodes[2].sendrawtransaction(node0_tx1["hex"]) txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"]) if self.options.segwit: assert_equal(txid1, txid1_clone) @@ -124,7 +120,7 @@ class TxnMallTest(BitcoinTestFramework): # Reconnect the split network, and sync chain: connect_nodes(self.nodes[1], 2) - self.nodes[2].sendrawtransaction(fund_bar_tx["hex"]) + self.nodes[2].sendrawtransaction(node0_tx2["hex"]) self.nodes[2].sendrawtransaction(tx2["hex"]) self.nodes[2].generate(1) # Mine another block to make sure we sync sync_blocks(self.nodes) @@ -142,27 +138,9 @@ class TxnMallTest(BitcoinTestFramework): # Check node0's total balance; should be same as before the clone, + 100 BTC for 2 matured, # less possible orphaned matured subsidy expected += 100 - if (self.options.mine_block): + if (self.options.mine_block): expected -= 50 assert_equal(self.nodes[0].getbalance(), expected) - assert_equal(self.nodes[0].getbalance("*", 0), expected) - - # Check node0's individual account balances. - # "foo" should have been debited by the equivalent clone of tx1 - assert_equal(self.nodes[0].getbalance("foo"), 1219 + tx1["amount"] + tx1["fee"]) - # "bar" should have been debited by (possibly unconfirmed) tx2 - assert_equal(self.nodes[0].getbalance("bar", 0), 29 + tx2["amount"] + tx2["fee"]) - # "" should have starting balance, less funding txes, plus subsidies - assert_equal(self.nodes[0].getbalance("", 0), starting_balance - - 1219 - + fund_foo_tx["fee"] - - 29 - + fund_bar_tx["fee"] - + 100) - - # Node1's "from0" account balance - assert_equal(self.nodes[1].getbalance("from0", 0), -(tx1["amount"] + tx2["amount"])) if __name__ == '__main__': TxnMallTest().main() - diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py index d644a94c73..7ee60d5611 100755 --- a/test/functional/wallet_txn_doublespend.py +++ b/test/functional/wallet_txn_doublespend.py @@ -3,14 +3,20 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there is a double-spend conflict.""" +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import ( + assert_equal, + connect_nodes, + disconnect_nodes, + find_output, + sync_blocks, +) class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 - self.extra_args = [['-deprecatedrpc=accounts']] * 4 def add_options(self, parser): parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true", @@ -29,20 +35,20 @@ class TxnMallTest(BitcoinTestFramework): assert_equal(self.nodes[i].getbalance(), starting_balance) self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress! - # Assign coins to foo and bar accounts: - node0_address_foo = self.nodes[0].getnewaddress("foo") - fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219) + # Assign coins to foo and bar addresses: + node0_address_foo = self.nodes[0].getnewaddress() + fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219) fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) - node0_address_bar = self.nodes[0].getnewaddress("bar") - fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29) + node0_address_bar = self.nodes[0].getnewaddress() + fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29) fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) - assert_equal(self.nodes[0].getbalance(""), - starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"]) + assert_equal(self.nodes[0].getbalance(), + starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]) # Coins are sent to node1_address - node1_address = self.nodes[1].getnewaddress("from0") + node1_address = self.nodes[1].getnewaddress() # First: use raw transaction API to send 1240 BTC to node1_address, # but don't broadcast: @@ -63,8 +69,8 @@ class TxnMallTest(BitcoinTestFramework): assert_equal(doublespend["complete"], True) # Create two spends using 1 50 BTC coin each - txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0) - txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0) + txid1 = self.nodes[0].sendtoaddress(node1_address, 40) + txid2 = self.nodes[0].sendtoaddress(node1_address, 20) # Have node0 mine a block: if (self.options.mine_block): @@ -83,15 +89,11 @@ class TxnMallTest(BitcoinTestFramework): expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) - # foo and bar accounts should be debited: - assert_equal(self.nodes[0].getbalance("foo", 0), 1219+tx1["amount"]+tx1["fee"]) - assert_equal(self.nodes[0].getbalance("bar", 0), 29+tx2["amount"]+tx2["fee"]) - if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) - # Node1's "from0" balance should be both transaction amounts: - assert_equal(self.nodes[1].getbalance("from0"), -(tx1["amount"]+tx2["amount"])) + # Node1's balance should be both transaction amounts: + assert_equal(self.nodes[1].getbalance(), starting_balance - tx1["amount"] - tx2["amount"]) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) @@ -117,29 +119,14 @@ class TxnMallTest(BitcoinTestFramework): assert_equal(tx1["confirmations"], -2) assert_equal(tx2["confirmations"], -2) - # Node0's total balance should be starting balance, plus 100BTC for + # Node0's total balance should be starting balance, plus 100BTC for # two more matured blocks, minus 1240 for the double-spend, plus fees (which are # negative): expected = starting_balance + 100 - 1240 + fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee assert_equal(self.nodes[0].getbalance(), expected) - assert_equal(self.nodes[0].getbalance("*"), expected) - - # Final "" balance is starting_balance - amount moved to accounts - doublespend + subsidies + - # fees (which are negative) - assert_equal(self.nodes[0].getbalance("foo"), 1219) - assert_equal(self.nodes[0].getbalance("bar"), 29) - assert_equal(self.nodes[0].getbalance(""), starting_balance - -1219 - - 29 - -1240 - + 100 - + fund_foo_tx["fee"] - + fund_bar_tx["fee"] - + doublespend_fee) - - # Node1's "from0" account balance should be just the doublespend: - assert_equal(self.nodes[1].getbalance("from0"), 1240) + + # Node1's balance should be its initial balance (1250 for 25 block rewards) plus the doublespend: + assert_equal(self.nodes[1].getbalance(), 1250 + 1240) if __name__ == '__main__': TxnMallTest().main() - diff --git a/test/lint/README.md b/test/lint/README.md new file mode 100644 index 0000000000..15974a3598 --- /dev/null +++ b/test/lint/README.md @@ -0,0 +1,29 @@ +This folder contains lint scripts. + +check-doc.py +============ +Check for missing documentation of command line options. + +commit-script-check.sh +====================== +Verification of [scripted diffs](/doc/developer-notes.md#scripted-diffs). + +git-subtree-check.sh +==================== +Run this script from the root of the repository to verify that a subtree matches the contents of +the commit it claims to have been updated to. + +To use, make sure that you have fetched the upstream repository branch in which the subtree is +maintained: +* for `src/secp256k1`: https://github.com/bitcoin-core/secp256k1.git (branch master) +* for `src/leveldb`: https://github.com/bitcoin-core/leveldb.git (branch bitcoin-fork) +* for `src/univalue`: https://github.com/bitcoin-core/univalue.git (branch master) +* for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master) + +Usage: `git-subtree-check.sh DIR (COMMIT)` + +`COMMIT` may be omitted, in which case `HEAD` is used. + +lint-all.sh +=========== +Calls other scripts with the `lint-` prefix. diff --git a/contrib/devtools/check-doc.py b/test/lint/check-doc.py index 0c2e1a24be..89776b2a6b 100755 --- a/contrib/devtools/check-doc.py +++ b/test/lint/check-doc.py @@ -17,12 +17,12 @@ import sys FOLDER_GREP = 'src' FOLDER_TEST = 'src/test/' REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' -REGEX_DOC = 'HelpMessageOpt\("(-[^"=]+?)(?:=|")' +REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")' CMD_ROOT_DIR = '`git rev-parse --show-toplevel`/{}'.format(FOLDER_GREP) CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST) CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR) # list unsupported, deprecated and duplicate args as they need no documentation -SET_DOC_OPTIONAL = set(['-rpcssl', '-benchmark', '-h', '-help', '-socks', '-tor', '-debugnet', '-whitelistalwaysrelay', '-prematurewitness', '-walletprematurewitness', '-promiscuousmempoolflags', '-blockminsize', '-dbcrashratio', '-forcecompactdb', '-usehd']) +SET_DOC_OPTIONAL = set(['-rpcssl', '-benchmark', '-h', '-help', '-socks', '-tor', '-debugnet', '-whitelistalwaysrelay', '-promiscuousmempoolflags', '-blockminsize', '-dbcrashratio', '-forcecompactdb', '-usehd']) def main(): diff --git a/contrib/devtools/check-rpc-mappings.py b/test/lint/check-rpc-mappings.py index 7e96852c5c..c3cdeef580 100755 --- a/contrib/devtools/check-rpc-mappings.py +++ b/test/lint/check-rpc-mappings.py @@ -44,7 +44,7 @@ def process_commands(fname): """Find and parse dispatch table in implementation file `fname`.""" cmds = [] in_rpcs = False - with open(fname, "r") as f: + with open(fname, "r", encoding="utf8") as f: for line in f: line = line.rstrip() if not in_rpcs: @@ -70,7 +70,7 @@ def process_mapping(fname): """Find and parse conversion table in implementation file `fname`.""" cmds = [] in_rpcs = False - with open(fname, "r") as f: + with open(fname, "r", encoding="utf8") as f: for line in f: line = line.rstrip() if not in_rpcs: diff --git a/contrib/devtools/commit-script-check.sh b/test/lint/commit-script-check.sh index 1c9dbc7f68..f1327469f3 100755 --- a/contrib/devtools/commit-script-check.sh +++ b/test/lint/commit-script-check.sh @@ -11,6 +11,7 @@ # The resulting script should exactly transform the previous commit into the current # one. Any remaining diff signals an error. +export LC_ALL=C if test "x$1" = "x"; then echo "Usage: $0 <commit>..." exit 1 diff --git a/contrib/devtools/git-subtree-check.sh b/test/lint/git-subtree-check.sh index 184951715e..85e8b841b6 100755 --- a/contrib/devtools/git-subtree-check.sh +++ b/test/lint/git-subtree-check.sh @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +export LC_ALL=C DIR="$1" COMMIT="$2" if [ -z "$COMMIT" ]; then diff --git a/contrib/devtools/lint-all.sh b/test/lint/lint-all.sh index b6d86959c6..7c4f96cb3b 100755 --- a/contrib/devtools/lint-all.sh +++ b/test/lint/lint-all.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -7,6 +7,10 @@ # This script runs all contrib/devtools/lint-*.sh files, and fails if any exit # with a non-zero status code. +# This script is intentionally locale dependent by not setting "export LC_ALL=C" +# in order to allow for the executed lint scripts to opt in or opt out of locale +# dependence themselves. + set -u SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") diff --git a/test/lint/lint-filenames.sh b/test/lint/lint-filenames.sh new file mode 100755 index 0000000000..5391e43d91 --- /dev/null +++ b/test/lint/lint-filenames.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. +# +# Make sure only lowercase alphanumerics (a-z0-9), underscores (_), +# hyphens (-) and dots (.) are used in source code filenames. + +export LC_ALL=C + +EXIT_CODE=0 +OUTPUT=$(git ls-files --full-name -- "*.[cC][pP][pP]" "*.[hH]" "*.[pP][yY]" "*.[sS][hH]" | \ + grep -vE '^[a-z0-9_./-]+$' | \ + grep -vE '^src/(secp256k1|univalue)/') + +if [[ ${OUTPUT} != "" ]]; then + echo "Use only lowercase alphanumerics (a-z0-9), underscores (_), hyphens (-) and dots (.)" + echo "in source code filenames:" + echo + echo "${OUTPUT}" + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/contrib/devtools/lint-include-guards.sh b/test/lint/lint-include-guards.sh index 6a0dd556bb..464969794b 100755 --- a/contrib/devtools/lint-include-guards.sh +++ b/test/lint/lint-include-guards.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -6,6 +6,7 @@ # # Check include guards. +export LC_ALL=C HEADER_ID_PREFIX="BITCOIN_" HEADER_ID_SUFFIX="_H" diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh new file mode 100755 index 0000000000..40d28ed3e0 --- /dev/null +++ b/test/lint/lint-includes.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Check for duplicate includes. +# Guard against accidental introduction of new Boost dependencies. +# Check includes: Check for duplicate includes. Enforce bracket syntax includes. + +export LC_ALL=C +IGNORE_REGEXP="/(leveldb|secp256k1|univalue)/" + +filter_suffix() { + git ls-files | grep -E "^src/.*\.${1}"'$' | grep -Ev "${IGNORE_REGEXP}" +} + +EXIT_CODE=0 + +for HEADER_FILE in $(filter_suffix h); do + DUPLICATE_INCLUDES_IN_HEADER_FILE=$(grep -E "^#include " < "${HEADER_FILE}" | sort | uniq -d) + if [[ ${DUPLICATE_INCLUDES_IN_HEADER_FILE} != "" ]]; then + echo "Duplicate include(s) in ${HEADER_FILE}:" + echo "${DUPLICATE_INCLUDES_IN_HEADER_FILE}" + echo + EXIT_CODE=1 + fi +done + +for CPP_FILE in $(filter_suffix cpp); do + DUPLICATE_INCLUDES_IN_CPP_FILE=$(grep -E "^#include " < "${CPP_FILE}" | sort | uniq -d) + if [[ ${DUPLICATE_INCLUDES_IN_CPP_FILE} != "" ]]; then + echo "Duplicate include(s) in ${CPP_FILE}:" + echo "${DUPLICATE_INCLUDES_IN_CPP_FILE}" + echo + EXIT_CODE=1 + fi +done + +INCLUDED_CPP_FILES=$(git grep -E "^#include [<\"][^>\"]+\.cpp[>\"]" -- "*.cpp" "*.h") +if [[ ${INCLUDED_CPP_FILES} != "" ]]; then + echo "The following files #include .cpp files:" + echo "${INCLUDED_CPP_FILES}" + echo + EXIT_CODE=1 +fi + +EXPECTED_BOOST_INCLUDES=( + boost/algorithm/string.hpp + boost/algorithm/string/case_conv.hpp + boost/algorithm/string/classification.hpp + boost/algorithm/string/join.hpp + boost/algorithm/string/predicate.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 + boost/filesystem/detail/utf8_codecvt_facet.hpp + boost/filesystem/fstream.hpp + boost/interprocess/sync/file_lock.hpp + boost/multi_index/hashed_index.hpp + boost/multi_index/ordered_index.hpp + boost/multi_index/sequenced_index.hpp + boost/multi_index_container.hpp + boost/optional.hpp + boost/preprocessor/cat.hpp + boost/preprocessor/stringize.hpp + boost/program_options/detail/config_file.hpp + boost/scoped_array.hpp + boost/signals2/connection.hpp + boost/signals2/last_value.hpp + boost/signals2/signal.hpp + boost/test/unit_test.hpp + boost/thread.hpp + boost/thread/condition_variable.hpp + boost/thread/mutex.hpp + boost/thread/thread.hpp + boost/variant.hpp + boost/variant/apply_visitor.hpp + boost/variant/static_visitor.hpp +) + +for BOOST_INCLUDE in $(git grep '^#include <boost/' -- "*.cpp" "*.h" | cut -f2 -d: | cut -f2 -d'<' | cut -f1 -d'>' | sort -u); do + IS_EXPECTED_INCLUDE=0 + for EXPECTED_BOOST_INCLUDE in "${EXPECTED_BOOST_INCLUDES[@]}"; do + if [[ "${BOOST_INCLUDE}" == "${EXPECTED_BOOST_INCLUDE}" ]]; then + IS_EXPECTED_INCLUDE=1 + break + fi + done + if [[ ${IS_EXPECTED_INCLUDE} == 0 ]]; then + EXIT_CODE=1 + echo "A new Boost dependency in the form of \"${BOOST_INCLUDE}\" appears to have been introduced:" + git grep "${BOOST_INCLUDE}" -- "*.cpp" "*.h" + echo + fi +done + +for EXPECTED_BOOST_INCLUDE in "${EXPECTED_BOOST_INCLUDES[@]}"; do + if ! git grep -q "^#include <${EXPECTED_BOOST_INCLUDE}>" -- "*.cpp" "*.h"; then + echo "Good job! The Boost dependency \"${EXPECTED_BOOST_INCLUDE}\" is no longer used." + echo "Please remove it from EXPECTED_BOOST_INCLUDES in $0" + echo "to make sure this dependency is not accidentally reintroduced." + echo + EXIT_CODE=1 + fi +done + +QUOTE_SYNTAX_INCLUDES=$(git grep '^#include "' -- "*.cpp" "*.h" | grep -Ev "${IGNORE_REGEXP}") +if [[ ${QUOTE_SYNTAX_INCLUDES} != "" ]]; then + echo "Please use bracket syntax includes (\"#include <foo.h>\") instead of quote syntax includes:" + echo "${QUOTE_SYNTAX_INCLUDES}" + echo + EXIT_CODE=1 +fi + +exit ${EXIT_CODE} diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh new file mode 100755 index 0000000000..cbe1143bd0 --- /dev/null +++ b/test/lint/lint-locale-dependence.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash + +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/netbase.cpp.*to_lower" + "src/qt/rpcconsole.cpp:.*atoi" + "src/qt/rpcconsole.cpp:.*isdigit" + "src/rest.cpp:.*strtol" + "src/rpc/server.cpp.*to_upper" + "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" +) + +REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/)" + +LOCALE_DEPENDENT_FUNCTIONS=( + alphasort # LC_COLLATE (via strcoll) + asctime # LC_TIME (directly) + asprintf # (via vasprintf) + atof # LC_NUMERIC (via strtod) + atoi # LC_NUMERIC (via strtol) + atol # LC_NUMERIC (via strtol) + atoll # (via strtoll) + atoq + btowc # LC_CTYPE (directly) + ctime # (via asctime or localtime) + dprintf # (via vdprintf) + fgetwc + fgetws + fold_case # boost::locale::fold_case + fprintf # (via vfprintf) + fputwc + fputws + fscanf # (via __vfscanf) + fwprintf # (via __vfwprintf) + getdate # via __getdate_r => isspace // __localtime_r + getwc + getwchar + is_digit # boost::algorithm::is_digit + is_space # boost::algorithm::is_space + isalnum # LC_CTYPE + isalpha # LC_CTYPE + isblank # LC_CTYPE + iscntrl # LC_CTYPE + isctype # LC_CTYPE + isdigit # LC_CTYPE + isgraph # LC_CTYPE + islower # LC_CTYPE + isprint # LC_CTYPE + ispunct # LC_CTYPE + isspace # LC_CTYPE + isupper # LC_CTYPE + iswalnum # LC_CTYPE + iswalpha # LC_CTYPE + iswblank # LC_CTYPE + iswcntrl # LC_CTYPE + iswctype # LC_CTYPE + iswdigit # LC_CTYPE + iswgraph # LC_CTYPE + iswlower # LC_CTYPE + iswprint # LC_CTYPE + iswpunct # LC_CTYPE + iswspace # LC_CTYPE + iswupper # LC_CTYPE + iswxdigit # LC_CTYPE + isxdigit # LC_CTYPE + localeconv # LC_NUMERIC + LC_MONETARY + mblen # LC_CTYPE + mbrlen + mbrtowc + mbsinit + mbsnrtowcs + mbsrtowcs + mbstowcs # LC_CTYPE + mbtowc # LC_CTYPE + mktime + normalize # boost::locale::normalize +# printf # LC_NUMERIC + putwc + putwchar + scanf # LC_NUMERIC + setlocale + snprintf + sprintf + sscanf + stod + stof + stoi + stol + stold + stoll + stoul + stoull + strcasecmp + strcasestr + strcoll # LC_COLLATE +# strerror + strfmon + strftime # LC_TIME + strncasecmp + strptime + strtod # LC_NUMERIC + strtof + strtoimax + strtol # LC_NUMERIC + strtold + strtoll + strtoq + strtoul # LC_NUMERIC + strtoull + strtoumax + strtouq + strxfrm # LC_COLLATE + swprintf + to_lower # boost::locale::to_lower + to_title # boost::locale::to_title + to_upper # boost::locale::to_upper + tolower # LC_CTYPE + toupper # LC_CTYPE + towctrans + towlower # LC_CTYPE + towupper # LC_CTYPE + trim # boost::algorithm::trim + trim_left # boost::algorithm::trim_left + trim_right # boost::algorithm::trim_right + ungetwc + vasprintf + vdprintf + versionsort + vfprintf + vfscanf + vfwprintf + vprintf + vscanf + vsnprintf + vsprintf + vsscanf + vswprintf + vwprintf + wcrtomb + wcscasecmp + wcscoll # LC_COLLATE + wcsftime # LC_TIME + wcsncasecmp + wcsnrtombs + wcsrtombs + wcstod # LC_NUMERIC + wcstof + wcstoimax + wcstol # LC_NUMERIC + wcstold + wcstoll + wcstombs # LC_CTYPE + wcstoul # LC_NUMERIC + wcstoull + wcstoumax + wcswidth + wcsxfrm # LC_COLLATE + wctob + wctomb # LC_CTYPE + wctrans + wctype + wcwidth + wprintf +) + +function join_array { + local IFS="$1" + shift + echo "$*" +} + +REGEXP_IGNORE_KNOWN_VIOLATIONS=$(join_array "|" "${KNOWN_VIOLATIONS[@]}") + +# Invoke "git grep" only once in order to minimize run-time +REGEXP_LOCALE_DEPENDENT_FUNCTIONS=$(join_array "|" "${LOCALE_DEPENDENT_FUNCTIONS[@]}") +GIT_GREP_OUTPUT=$(git grep -E "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FUNCTIONS}(|_r|_s))[^a-zA-Z0-9_\`'\"<>]" -- "*.cpp" "*.h") + +EXIT_CODE=0 +for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do + MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(|_r|_s)[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \ + grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}" | \ + grep -vE 'fprintf\(.*(stdout|stderr)') + if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then + MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}") + fi + if [[ ${REGEXP_IGNORE_KNOWN_VIOLATIONS} != "" ]]; then + MATCHES=$(grep -vE "${REGEXP_IGNORE_KNOWN_VIOLATIONS}" <<< "${MATCHES}") + fi + if [[ ${MATCHES} != "" ]]; then + echo "The locale dependent function ${LOCALE_DEPENDENT_FUNCTION}(...) appears to be used:" + echo "${MATCHES}" + echo + EXIT_CODE=1 + fi +done +if [[ ${EXIT_CODE} != 0 ]]; then + echo "Unnecessary locale dependence can cause bugs that are very" + echo "tricky to isolate and fix. Please avoid using locale dependent" + echo "functions if possible." + echo + echo "Advice not applicable in this specific case? Add an exception" + echo "by updating the ignore list in $0" +fi +exit ${EXIT_CODE} diff --git a/contrib/devtools/lint-logs.sh b/test/lint/lint-logs.sh index 3bb54359a8..1afd4cfc1a 100755 --- a/contrib/devtools/lint-logs.sh +++ b/test/lint/lint-logs.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -12,13 +12,14 @@ # There are some instances of LogPrintf() in comments. Those can be # ignored - -UNTERMINATED_LOGS=$(git grep "LogPrintf(" -- "*.cpp" | \ +export LC_ALL=C +UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \ grep -v '\\n"' | \ grep -v "/\* Continued \*/" | \ + grep -v "LogPrint()" | \ grep -v "LogPrintf()") if [[ ${UNTERMINATED_LOGS} != "" ]]; then - echo "All calls to LogPrintf() should be terminated with \\n" + echo "All calls to LogPrintf() and LogPrint() should be terminated with \\n" echo echo "${UNTERMINATED_LOGS}" exit 1 diff --git a/contrib/devtools/lint-python-shebang.sh b/test/lint/lint-python-shebang.sh index f5c5971c03..4ff87f0bf7 100755 --- a/contrib/devtools/lint-python-shebang.sh +++ b/test/lint/lint-python-shebang.sh @@ -1,5 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash # Shebang must use python3 (not python or python2) + +export LC_ALL=C EXIT_CODE=0 for PYTHON_FILE in $(git ls-files -- "*.py"); do if [[ $(head -c 2 "${PYTHON_FILE}") == "#!" && diff --git a/test/lint/lint-python-utf8-encoding.sh b/test/lint/lint-python-utf8-encoding.sh new file mode 100755 index 0000000000..14183a5ccf --- /dev/null +++ b/test/lint/lint-python-utf8-encoding.sh @@ -0,0 +1,20 @@ +#!/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. +# +# Make sure we explicitly open all text files using UTF-8 (or ASCII) encoding to +# avoid potential issues on the BSDs where the locale is not always set. + +export LC_ALL=C +EXIT_CODE=0 +OUTPUT=$(git grep " open(" -- "*.py" | grep -vE "encoding=.(ascii|utf8|utf-8)." | grep -vE "open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]") +if [[ ${OUTPUT} != "" ]]; then + echo "Python's open(...) seems to be used to open text files without explicitly" + echo "specifying encoding=\"utf8\":" + echo + echo "${OUTPUT}" + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/contrib/devtools/lint-python.sh b/test/lint/lint-python.sh index 239337000d..d9d46d86d5 100755 --- a/contrib/devtools/lint-python.sh +++ b/test/lint/lint-python.sh @@ -6,11 +6,15 @@ # # Check for specified flake8 warnings in python files. +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 @@ -61,6 +65,8 @@ # 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 @@ -71,4 +77,4 @@ # W605 invalid escape sequence "x" # W606 'async' and 'await' are reserved keywords starting with Python 3.7 -flake8 --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 . +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,F401,E901,E902,F402,F404,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 . diff --git a/test/lint/lint-shell-locale.sh b/test/lint/lint-shell-locale.sh new file mode 100755 index 0000000000..242b27c763 --- /dev/null +++ b/test/lint/lint-shell-locale.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. +# +# Make sure all shell scripts: +# a.) explicitly opt out of locale dependence using "export LC_ALL=C", or +# b.) explicitly opt in to locale dependence using the annotation below. + +export LC_ALL=C + +EXIT_CODE=0 +for SHELL_SCRIPT in $(git ls-files -- "*.sh" | grep -vE "src/(secp256k1|univalue)/"); do + if grep -q "# This script is intentionally locale dependent by not setting \"export LC_ALL=C\"" "${SHELL_SCRIPT}"; then + continue + fi + FIRST_NON_COMMENT_LINE=$(grep -vE '^(#.*|)$' "${SHELL_SCRIPT}" | head -1) + if [[ ${FIRST_NON_COMMENT_LINE} != "export LC_ALL=C" ]]; then + echo "Missing \"export LC_ALL=C\" (to avoid locale dependence) as first non-comment non-empty line in ${SHELL_SCRIPT}" + EXIT_CODE=1 + fi +done +exit ${EXIT_CODE} diff --git a/contrib/devtools/lint-shell.sh b/test/lint/lint-shell.sh index 5f5fa9a925..e2ccdb5165 100755 --- a/contrib/devtools/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -6,6 +6,10 @@ # # Check for shellcheck warnings in shell scripts. +# This script is intentionally locale dependent by not setting "export LC_ALL=C" +# to allow running certain versions of shellcheck that core dump when LC_ALL=C +# is set. + # Disabled warnings: # SC2001: See if you can use ${variable//search/replace} instead. # SC2004: $/${} is unnecessary on arithmetic variables. diff --git a/contrib/devtools/lint-tests.sh b/test/lint/lint-tests.sh index ffc0660551..35d11023eb 100755 --- a/contrib/devtools/lint-tests.sh +++ b/test/lint/lint-tests.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -6,6 +6,7 @@ # # Check the test suite naming conventions +export LC_ALL=C EXIT_CODE=0 NAMING_INCONSISTENCIES=$(git grep -E '^BOOST_FIXTURE_TEST_SUITE\(' -- \ diff --git a/contrib/devtools/lint-whitespace.sh b/test/lint/lint-whitespace.sh index c5d43043d5..beb7ec42f4 100755 --- a/contrib/devtools/lint-whitespace.sh +++ b/test/lint/lint-whitespace.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -8,6 +8,7 @@ # We can't run this check unless we know the commit range for the PR. +export LC_ALL=C while getopts "?" opt; do case $opt in ?) diff --git a/test/util/bitcoin-util-test.py b/test/util/bitcoin-util-test.py index 30bd13d0dc..16371a6234 100755 --- a/test/util/bitcoin-util-test.py +++ b/test/util/bitcoin-util-test.py @@ -28,7 +28,7 @@ import sys def main(): config = configparser.ConfigParser() config.optionxform = str - config.readfp(open(os.path.join(os.path.dirname(__file__), "../config.ini"))) + config.readfp(open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8")) env_conf = dict(config.items('environment')) parser = argparse.ArgumentParser(description=__doc__) @@ -49,7 +49,7 @@ def main(): def bctester(testDir, input_basename, buildenv): """ Loads and parses the input file, runs all tests and reports results""" input_filename = os.path.join(testDir, input_basename) - raw_data = open(input_filename).read() + raw_data = open(input_filename, encoding="utf8").read() input_data = json.loads(raw_data) failed_testcases = [] @@ -86,7 +86,7 @@ def bctest(testDir, testObj, buildenv): inputData = None if "input" in testObj: filename = os.path.join(testDir, testObj["input"]) - inputData = open(filename).read() + inputData = open(filename, encoding="utf8").read() stdinCfg = subprocess.PIPE # Read the expected output data (if there is any) @@ -97,7 +97,7 @@ def bctest(testDir, testObj, buildenv): outputFn = testObj['output_cmp'] outputType = os.path.splitext(outputFn)[1][1:] # output type from file extension (determines how to compare) try: - outputData = open(os.path.join(testDir, outputFn)).read() + outputData = open(os.path.join(testDir, outputFn), encoding="utf8").read() except: logging.error("Output file " + outputFn + " can not be opened") raise diff --git a/test/util/data/bitcoin-util-test.json b/test/util/data/bitcoin-util-test.json index a115aa30d9..f2213f4f2e 100644 --- a/test/util/data/bitcoin-util-test.json +++ b/test/util/data/bitcoin-util-test.json @@ -27,6 +27,12 @@ "description": "Creates a blank transaction when nothing is piped into bitcoin-tx (output in json)" }, { "exec": "./bitcoin-tx", + "args": ["-create", "nversion=1foo"], + "return_code": 1, + "error_txt": "error: Invalid TX version requested", + "description": "Tests the check for invalid nversion value" + }, + { "exec": "./bitcoin-tx", "args": ["-", "delin=1"], "input": "tx394b54bb.hex", "output_cmp": "tt-delin1-out.hex", @@ -46,6 +52,13 @@ "description": "Attempts to delete an input with a bad index from a transaction. Expected to fail." }, { "exec": "./bitcoin-tx", + "args": ["-", "delin=1foo"], + "input": "tx394b54bb.hex", + "return_code": 1, + "error_txt": "error: Invalid TX input index", + "description": "Tests the check for an invalid input index with delin" + }, + { "exec": "./bitcoin-tx", "args": ["-", "delout=1"], "input": "tx394b54bb.hex", "output_cmp": "tt-delout1-out.hex", @@ -65,6 +78,13 @@ "description": "Attempts to delete an output with a bad index from a transaction. Expected to fail." }, { "exec": "./bitcoin-tx", + "args": ["-", "delout=1foo"], + "input": "tx394b54bb.hex", + "return_code": 1, + "error_txt": "error: Invalid TX output index", + "description": "Tests the check for an invalid output index with delout" + }, + { "exec": "./bitcoin-tx", "args": ["-", "locktime=317000"], "input": "tx394b54bb.hex", "output_cmp": "tt-locktime317000-out.hex", @@ -77,6 +97,29 @@ "description": "Adds an nlocktime to a transaction (output in json)" }, { "exec": "./bitcoin-tx", + "args": ["-create", "locktime=317000foo"], + "return_code": 1, + "error_txt": "error: Invalid TX locktime requested", + "description": "Tests the check for invalid locktime value" + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", + "replaceable=0foo"], + "return_code": 1, + "error_txt": "error: Invalid TX input index", + "description": "Tests the check for an invalid input index with replaceable" + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0x"], + "return_code": 1, + "error_txt": "error: invalid TX input vout", + "description": "Tests the check for an invalid vout value when adding an input" + }, + { "exec": "./bitcoin-tx", "args": ["-create", "outaddr=1"], @@ -227,6 +270,18 @@ }, { "exec": "./bitcoin-tx", "args": + ["-create", + "in=4d49a71ec9da436f71ec4ee231d04f292a29cd316f598bb7068feccabdc59485:0", + "set=privatekeys:[\"5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf\"]", + "set=prevtxs:[{\"txid\":\"4d49a71ec9da436f71ec4ee231d04f292a29cd316f598bb7068feccabdc59485\",\"vout\":\"0foo\",\"scriptPubKey\":\"76a91491b24bf9f5288532960ac687abb035127b1d28a588ac\"}]", + "sign=ALL", + "outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"], + "return_code": 1, + "error_txt": "error: prevtxs internal object typecheck fail", + "description": "Tests the check for invalid vout index in prevtxs for sign" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outpubkey=0:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397", "nversion=1"], "output_cmp": "txcreateoutpubkey1.hex", "description": "Creates a new transaction with a single pay-to-pubkey output" diff --git a/test/util/rpcauth-test.py b/test/util/rpcauth-test.py new file mode 100755 index 0000000000..46e9fbc739 --- /dev/null +++ b/test/util/rpcauth-test.py @@ -0,0 +1,48 @@ +#!/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 share/rpcauth/rpcauth.py +""" +import base64 +import configparser +import hmac +import importlib +import os +import sys +import unittest + +class TestRPCAuth(unittest.TestCase): + def setUp(self): + config = configparser.ConfigParser() + config_path = os.path.abspath( + os.path.join(os.sep, os.path.abspath(os.path.dirname(__file__)), + "../config.ini")) + with open(config_path, encoding="utf8") as config_file: + config.read_file(config_file) + sys.path.insert(0, os.path.dirname(config['environment']['RPCAUTH'])) + 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) + + def test_generate_password(self): + password = self.rpcauth.generate_password() + expected_password = base64.urlsafe_b64encode( + base64.urlsafe_b64decode(password)).decode('utf-8') + self.assertEqual(expected_password, password) + + def test_check_password_hmac(self): + salt = self.rpcauth.generate_salt() + password = self.rpcauth.generate_password() + password_hmac = self.rpcauth.password_to_hmac(salt, password) + + m = hmac.new(bytearray(salt, 'utf-8'), + bytearray(password, 'utf-8'), 'SHA256') + expected_password_hmac = m.hexdigest() + + self.assertEqual(expected_password_hmac, password_hmac) + +if __name__ == '__main__': + unittest.main() |