diff options
-rw-r--r-- | .travis.yml | 112 | ||||
-rw-r--r-- | depends/packages/packages.mk | 4 | ||||
-rw-r--r-- | depends/packages/qt.mk | 1 | ||||
-rw-r--r-- | depends/packages/xextproto.mk | 4 | ||||
-rw-r--r-- | doc/dependencies.md | 22 | ||||
-rw-r--r-- | doc/developer-notes.md | 16 | ||||
-rw-r--r-- | src/bitcoin-cli.cpp | 13 | ||||
-rw-r--r-- | src/bitcoin-tx.cpp | 9 | ||||
-rw-r--r-- | src/bitcoind.cpp | 6 | ||||
-rw-r--r-- | src/miner.cpp | 7 | ||||
-rw-r--r-- | src/qt/utilitydialog.cpp | 5 | ||||
-rw-r--r-- | src/validation.cpp | 19 | ||||
-rw-r--r-- | src/wallet/db.cpp | 2 | ||||
-rw-r--r-- | src/wallet/feebumper.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 10 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 89 | ||||
-rw-r--r-- | src/wallet/wallet.h | 13 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 10 | ||||
-rwxr-xr-x | test/functional/feature_cltv.py | 13 | ||||
-rwxr-xr-x | test/functional/feature_dersig.py | 14 | ||||
-rwxr-xr-x | test/functional/feature_segwit.py | 18 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 2 | ||||
-rwxr-xr-x | test/functional/wallet_multiwallet.py | 36 | ||||
-rwxr-xr-x | test/lint/lint-format-strings.py | 254 | ||||
-rwxr-xr-x | test/lint/lint-format-strings.sh | 41 |
25 files changed, 530 insertions, 192 deletions
diff --git a/.travis.yml b/.travis.yml index b07cbfbe1b..aafdc2e89d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ env: - RUN_TESTS=false - RUN_BENCH=false # Set to true for any one job that has debug enabled, to quickly check bench is not crashing or hitting assertions - DOCKER_NAME_TAG=ubuntu:18.04 + - LC_ALL=C.UTF-8 - BOOST_TEST_RANDOM=1$TRAVIS_BUILD_ID - CCACHE_SIZE=100M - CCACHE_TEMPDIR=/tmp/.ccache-temp @@ -26,16 +27,58 @@ env: - 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: +before_install: + - export PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/python/d' | tr "\n" ":" | sed "s|::|:|g") + - BEGIN_FOLD () { echo ""; CURRENT_FOLD_NAME=$1; echo "travis_fold:start:${CURRENT_FOLD_NAME}"; } + - END_FOLD () { RET=$?; echo "travis_fold:end:${CURRENT_FOLD_NAME}"; return $RET; } +install: + - travis_retry docker pull $DOCKER_NAME_TAG + - env | grep -E '^(CCACHE_|WINEDEBUG|LC_ALL|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 $DOCKER_NAME_TAG) + - 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: + - 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 [[ $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: + - export TRAVIS_COMMIT_LOG=`git log --format=fuller -1` + - 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 DOCKER_EXEC ccache --max-size=$CCACHE_SIZE; fi + - BEGIN_FOLD autogen; test -n "$CONFIG_SHELL" && DOCKER_EXEC "$CONFIG_SHELL" -c "./autogen.sh" || DOCKER_EXEC ./autogen.sh; END_FOLD + - mkdir build && cd build + - BEGIN_FOLD configure; DOCKER_EXEC ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false); END_FOLD + - BEGIN_FOLD distdir; DOCKER_EXEC make distdir VERSION=$HOST; END_FOLD + - cd bitcoin-$HOST + - BEGIN_FOLD configure; DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false); END_FOLD + - BEGIN_FOLD build; DOCKER_EXEC make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && DOCKER_EXEC make $GOAL V=1 ; false ); END_FOLD + - if [ "$RUN_TESTS" = "true" ]; then BEGIN_FOLD unit-tests; DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib make $MAKEJOBS check VERBOSE=1; END_FOLD; fi + - if [ "$RUN_BENCH" = "true" ]; then BEGIN_FOLD bench; DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib $OUTDIR/bin/bench_bitcoin -scaling=0.001 ; END_FOLD; fi + - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude feature_pruning,feature_dbcrash"; fi + - if [ "$RUN_TESTS" = "true" ]; then BEGIN_FOLD functional-tests; DOCKER_EXEC test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended}; END_FOLD; fi +after_script: + - echo $TRAVIS_COMMIT_RANGE + - echo $TRAVIS_COMMIT_LOG +jobs: + include: # ARM - - >- + - stage: test + env: >- 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 - - >- + - stage: test + env: >- HOST=i686-w64-mingw32 DPKG_ADD_ARCH="i386" DEP_OPTS="NO_QT=1" @@ -44,7 +87,8 @@ env: GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" # Win64 - - >- + - stage: test + env: >- HOST=x86_64-w64-mingw32 DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine-binfmt wine64" @@ -52,7 +96,8 @@ env: GOAL="install" BITCOIN_CONFIG="--enable-reduce-exports" # 32-bit + dash - - >- + - stage: test + env: >- HOST=i686-pc-linux-gnu PACKAGES="g++-multilib python3-zmq" DEP_OPTS="NO_QT=1" @@ -61,7 +106,8 @@ env: BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" CONFIG_SHELL="/bin/dash" # x86_64 Linux (uses qt5 dev package instead of depends Qt to speed up build and avoid timeout) - - >- + - stage: test + env: >- HOST=x86_64-unknown-linux-gnu PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools protobuf-compiler libdbus-1-dev libharfbuzz-dev libprotobuf-dev" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" @@ -70,7 +116,8 @@ env: GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CXXFLAGS=\"-g0 -O2\"" # x86_64 Linux (Qt5 & system libs) - - >- + - stage: test + env: >- HOST=x86_64-unknown-linux-gnu PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 @@ -78,7 +125,8 @@ env: 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 - - >- + - stage: test + env: >- HOST=x86_64-unknown-linux-gnu PACKAGES="python3" DEP_OPTS="NO_WALLET=1" @@ -86,55 +134,13 @@ env: GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" # Cross-Mac - - >- + - stage: test + env: >- HOST=x86_64-apple-darwin14 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") - - BEGIN_FOLD () { echo ""; CURRENT_FOLD_NAME=$1; echo "travis_fold:start:${CURRENT_FOLD_NAME}"; } - - END_FOLD () { RET=$?; echo "travis_fold:end:${CURRENT_FOLD_NAME}"; return $RET; } -install: - - travis_retry docker pull $DOCKER_NAME_TAG - - 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 $DOCKER_NAME_TAG) - - 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: - - 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 [[ $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: - - export TRAVIS_COMMIT_LOG=`git log --format=fuller -1` - - 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 DOCKER_EXEC ccache --max-size=$CCACHE_SIZE; fi - - BEGIN_FOLD autogen; test -n "$CONFIG_SHELL" && DOCKER_EXEC "$CONFIG_SHELL" -c "./autogen.sh" || DOCKER_EXEC ./autogen.sh; END_FOLD - - mkdir build && cd build - - BEGIN_FOLD configure; DOCKER_EXEC ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false); END_FOLD - - BEGIN_FOLD distdir; DOCKER_EXEC make distdir VERSION=$HOST; END_FOLD - - cd bitcoin-$HOST - - BEGIN_FOLD configure; DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false); END_FOLD - - BEGIN_FOLD build; DOCKER_EXEC make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && DOCKER_EXEC make $GOAL V=1 ; false ); END_FOLD - - if [ "$RUN_TESTS" = "true" ]; then BEGIN_FOLD unit-tests; DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib make $MAKEJOBS check VERBOSE=1; END_FOLD; fi - - if [ "$RUN_BENCH" = "true" ]; then BEGIN_FOLD bench; DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib $OUTDIR/bin/bench_bitcoin -scaling=0.001 ; END_FOLD; fi - - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude feature_pruning,feature_dbcrash"; fi - - if [ "$RUN_TESTS" = "true" ]; then BEGIN_FOLD functional-tests; DOCKER_EXEC test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended}; END_FOLD; fi -after_script: - - echo $TRAVIS_COMMIT_RANGE - - echo $TRAVIS_COMMIT_LOG - -jobs: - include: + BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror" - stage: lint env: sudo: false diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 69acb19b8f..5fe6f98da2 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -3,9 +3,7 @@ packages:=boost openssl libevent zeromq qt_native_packages = native_protobuf 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_linux_packages:=qt expat dbus libxcb xcb_proto libXau xproto freetype fontconfig libX11 xextproto libXext xtrans qt_darwin_packages=qt qt_mingw32_packages=qt diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 7fdd7612c3..71b7f83db9 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -92,6 +92,7 @@ $(package)_config_opts_linux += -no-opengl $(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_aarch64_linux = -xplatform linux-aarch64-gnu-g++ $(package)_config_opts_mingw32 = -no-opengl -xplatform win32-g++ -device-option CROSS_COMPILE="$(host)-" $(package)_build_env = QT_RCC_TEST=1 $(package)_build_env += QT_RCC_SOURCE_DATE_OVERRIDE=1 diff --git a/depends/packages/xextproto.mk b/depends/packages/xextproto.mk index 98a11eb497..7065237bd5 100644 --- a/depends/packages/xextproto.mk +++ b/depends/packages/xextproto.mk @@ -4,6 +4,10 @@ $(package)_download_path=http://xorg.freedesktop.org/releases/individual/proto $(package)_file_name=$(package)-$($(package)_version).tar.bz2 $(package)_sha256_hash=f3f4b23ac8db9c3a9e0d8edb591713f3d70ef9c3b175970dd8823dfc92aa5bb0 +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . +endef + define $(package)_set_vars $(package)_config_opts=--disable-shared endef diff --git a/doc/dependencies.md b/doc/dependencies.md index 793c659419..3fc7aecba8 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -6,25 +6,25 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html) | | --- | --- | --- | --- | --- | --- | | Berkeley DB | [4.8.30](http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | -| Boost | [1.64.0](http://www.boost.org/users/download/) | [1.47.0](https://github.com/bitcoin/bitcoin/pull/8920) | No | | | -| Clang | | [3.3+](http://llvm.org/releases/download.html) (C++11 support) | | | | +| Boost | [1.64.0](https://www.boost.org/users/download/) | [1.47.0](https://github.com/bitcoin/bitcoin/pull/8920) | No | | | +| Clang | | [3.3+](https://llvm.org/releases/download.html) (C++11 support) | | | | | D-Bus | [1.10.18](https://cgit.freedesktop.org/dbus/dbus/tree/NEWS?h=dbus-1.10) | | No | Yes | | | Expat | [2.2.5](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](http://download.savannah.gnu.org/releases/freetype) | | No | | | -| GCC | | [4.8+](https://gcc.gnu.org/) | | | | +| GCC | | [4.8+](https://gcc.gnu.org/) (C++11 support) | | | | | HarfBuzz-NG | | | | | | | libevent | [2.1.8-stable](https://github.com/libevent/libevent/releases) | 2.0.22 | No | | | -| libjpeg | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L75) | -| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L74) | +| libjpeg | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L65) | +| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L64) | | MiniUPnPc | [2.0.20180203](http://miniupnp.free.fr/files) | | No | | | | OpenSSL | [1.0.1k](https://www.openssl.org/source) | | Yes | | | -| PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L76) | -| protobuf | [2.6.3](https://github.com/google/protobuf/releases) | | No | | | +| PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L66) | +| protobuf | [2.6.1](https://github.com/google/protobuf/releases) | | No | | | | Python (tests) | | [3.4](https://www.python.org/downloads) | | | | | qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | -| Qt | [5.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) | +| Qt | [5.9.6](https://download.qt.io/official_releases/qt/) | 5.x | No | | | +| XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L87) (Linux only) | +| xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L86) (Linux only) | | ZeroMQ | [4.2.3](https://github.com/zeromq/libzmq/releases) | | No | | | -| zlib | [1.2.11](http://zlib.net/) | | | | No | +| zlib | [1.2.11](https://zlib.net/) | | | | No | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 70070fa24b..da8384c537 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -5,8 +5,10 @@ Developer Notes **Table of Contents** - [Developer Notes](#developer-notes) - - [Coding Style](#coding-style) + - [Coding Style (General)](#coding-style-general) + - [Coding Style (C++)](#coding-style-c) - [Doxygen comments](#doxygen-comments) + - [Coding Style (Python)](#coding-style-python) - [Development tips and tricks](#development-tips-and-tricks) - [Compiling for debugging](#compiling-for-debugging) - [Compiling for gprof profiling](#compiling-for-gprof-profiling) @@ -35,8 +37,8 @@ Developer Notes <!-- markdown-toc end --> -Coding Style ---------------- +Coding Style (General) +---------------------- Various coding styles have been used during the history of the codebase, and the result is not very consistent. However, we're now trying to converge to @@ -46,6 +48,9 @@ commits. Do not submit patches solely to modify the style of existing code. +Coding Style (C++) +------------------ + - **Indentation and whitespace rules** as specified in [src/.clang-format](/src/.clang-format). You can use the provided [clang-format-diff script](/contrib/devtools/README.md#clang-format-diffpy) @@ -174,6 +179,11 @@ but if possible use one of the above styles. Documentation can be generated with `make docs` and cleaned up with `make clean-docs`. +Coding Style (Python) +--------------------- + +Refer to [/test/functional/README.md#style-guidelines](/test/functional/README.md#style-guidelines). + Development tips and tricks --------------------------- diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index a77e3d49b8..24a6dc23b2 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -104,14 +104,13 @@ static int AppInitRPC(int argc, char* argv[]) 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 = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n"; if (!gArgs.IsArgSet("-version")) { - 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" + "Usage: bitcoin-cli [options] <command> [params] Send command to " PACKAGE_NAME "\n" + "or: bitcoin-cli [options] -named <command> [name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n" + "or: bitcoin-cli [options] help List commands\n" + "or: bitcoin-cli [options] help <command> Get help for a command\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 181e2bb1bc..5fef4724c9 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -98,11 +98,10 @@ 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" + - "\n"; + std::string strUsage = PACKAGE_NAME " bitcoin-tx utility version " + FormatFullVersion() + "\n\n" + + "Usage: bitcoin-tx [options] <hex-tx> [commands] Update hex-encoded bitcoin transaction\n" + + "or: bitcoin-tx [options] -create [commands] Create hex-encoded bitcoin transaction\n" + + "\n"; strUsage += gArgs.GetHelpMessage(); fprintf(stdout, "%s", strUsage.c_str()); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 4d010c0d14..06f8622426 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -71,7 +71,7 @@ static bool AppInit(int argc, char* argv[]) // 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 = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { @@ -79,9 +79,7 @@ static bool AppInit(int argc, char* argv[]) } else { - strUsage += "\nUsage:\n" - " bitcoind [options] " + strprintf("Start %s Daemon", PACKAGE_NAME) + "\n"; - + strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME " Daemon\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } diff --git a/src/miner.cpp b/src/miner.cpp index c32dc26f86..6cd78b5c18 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -133,8 +133,11 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc // Decide whether to include witness transactions // This is only needed in case the witness softfork activation is reverted - // (which would require a very deep reorganization) or when - // -promiscuousmempoolflags is used. + // (which would require a very deep reorganization). + // Note that the mempool would accept transactions with witness data before + // IsWitnessEnabled, but we would only ever mine blocks after IsWitnessEnabled + // unless there is a massive block reorganization with the witness softfork + // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx; diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 1da25b0761..c9469593a5 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -70,8 +70,7 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo ui->helpMessage->setVisible(false); } else { setWindowTitle(tr("Command-line options")); - QString header = "Usage:\n" - " bitcoin-qt [command-line options] \n"; + QString header = "Usage: bitcoin-qt [command-line options] \n"; QTextCursor cursor(ui->helpMessage->document()); cursor.insertText(version); cursor.insertBlock(); @@ -80,7 +79,7 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo std::string strUsage = gArgs.GetHelpMessage(); QString coreOptions = QString::fromStdString(strUsage); - text = version + "\n" + header + "\n" + coreOptions; + text = version + "\n\n" + header + "\n" + coreOptions; QTextTableFormat tf; tf.setBorderStyle(QTextFrameFormat::BorderStyle_None); diff --git a/src/validation.cpp b/src/validation.cpp index 702a8d7e05..fceb135854 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -894,10 +894,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } - unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; - if (!chainparams.RequireStandard()) { - scriptVerifyFlags = gArgs.GetArg("-promiscuousmempoolflags", scriptVerifyFlags); - } + constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. @@ -932,20 +929,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // transactions into the mempool can be exploited as a DoS attack. unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), Params().GetConsensus()); if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, true, txdata)) { - // If we're using promiscuousmempoolflags, we may hit this normally - // Check if current block has some flags that scriptVerifyFlags - // does not before printing an ominous warning - if (!(~scriptVerifyFlags & currentBlockScriptVerifyFlags)) { - return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against latest-block but not STANDARD flags %s, %s", + return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); - } else { - if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, false, txdata)) { - return error("%s: ConnectInputs failed against MANDATORY but not STANDARD flags due to promiscuous mempool %s, %s", - __func__, hash.ToString(), FormatStateMessage(state)); - } else { - LogPrintf("Warning: -promiscuousmempool flags set to not include currently enforced soft forks, this may break mining or otherwise cause instability!\n"); - } - } } if (test_accept) { diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 01b8eacccb..00964b8cc8 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -768,7 +768,7 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) env->mapFileUseCount.erase(strFile); // Copy wallet file - fs::path pathSrc = GetWalletDir() / strFile; + fs::path pathSrc = env->Directory() / strFile; fs::path pathDest(strDest); if (fs::is_directory(pathDest)) pathDest /= strFile; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 0eb85a6e5c..97ec5e073c 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -195,7 +195,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // If the output would become dust, discard it (converting the dust to fee) poutput->nValue -= nDelta; if (poutput->nValue <= GetDustThreshold(*poutput, GetDiscardRate(*wallet, ::feeEstimator))) { - LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n"); + wallet->WalletLogPrintf("Bumping fee and discarding dust output\n"); new_fee += poutput->nValue; mtx.vout.erase(mtx.vout.begin() + nOutput); } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 3cb4050f01..2a7777a690 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -553,7 +553,7 @@ UniValue importwallet(const JSONRPCRequest& request) // 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 showing the import progress. uiInterface.ShowProgress does not have a cancel button. - uiInterface.ShowProgress(_("Importing..."), 0, false); // show progress dialog in GUI + uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 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); std::string line; @@ -571,7 +571,7 @@ UniValue importwallet(const JSONRPCRequest& request) assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); if (pwallet->HaveKey(keyid)) { - LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); continue; } int64_t nTime = DecodeDumpTime(vstr[1]); @@ -589,7 +589,7 @@ UniValue importwallet(const JSONRPCRequest& request) fLabel = true; } } - LogPrintf("Importing %s...\n", EncodeDestination(keyid)); + pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid)); if (!pwallet->AddKeyPubKey(key, pubkey)) { fGood = false; continue; @@ -603,11 +603,11 @@ UniValue importwallet(const JSONRPCRequest& request) CScript script = CScript(vData.begin(), vData.end()); CScriptID id(script); if (pwallet->HaveCScript(id)) { - LogPrintf("Skipping import of %s (script already present)\n", vstr[0]); + pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", vstr[0]); continue; } if(!pwallet->AddCScript(script)) { - LogPrintf("Error importing script %s\n", vstr[0]); + pwallet->WalletLogPrintf("Error importing script %s\n", vstr[0]); fGood = false; continue; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 218684fdf1..2c15ff7d90 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -82,7 +82,7 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { - LogPrintf("Releasing wallet %s\n", wallet->GetName()); + wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); delete wallet; @@ -354,8 +354,7 @@ bool CWallet::LoadCScript(const CScript& redeemScript) if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { std::string strAddr = EncodeDestination(CScriptID(redeemScript)); - LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", - __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); return true; } @@ -445,7 +444,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, if (pMasterKey.second.nDeriveIterations < 25000) pMasterKey.second.nDeriveIterations = 25000; - LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; @@ -653,7 +652,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) if (kMasterKey.nDeriveIterations < 25000) kMasterKey.nDeriveIterations = 25000; - LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) return false; @@ -907,7 +906,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) bool success = true; if (!batch.WriteTx(wtx)) { - LogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString()); + WalletLogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString()); success = false; } @@ -974,7 +973,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) } //// debug print - LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); + WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if (fInsertedNew || fUpdated) @@ -1032,7 +1031,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); MarkConflicted(pIndex->GetBlockHash(), range.first->second); } range.first++; @@ -1058,11 +1057,11 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI for (const CKeyID &keyid : vAffected) { std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); if (mi != m_pool_key_to_index.end()) { - LogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); + WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); MarkReserveKeysAsUsed(mi->second); if (!TopUpKeyPool()) { - LogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); + WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); } } } @@ -1641,8 +1640,8 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { - LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->GetHash().ToString()); + pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + this->GetHash().ToString()); address = CNoDestination(); } @@ -1676,7 +1675,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r { LOCK(cs_main); startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); } if (startBlock) { @@ -1717,11 +1716,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlockIndex* pindex = pindexStart; CBlockIndex* ret = nullptr; - if (pindex) LogPrintf("Rescan started from block %d...\n", pindex->nHeight); + if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); { fAbortRescan = false; - ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup CBlockIndex* tip = nullptr; double progress_begin; double progress_end; @@ -1739,11 +1738,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock while (pindex && !fAbortRescan && !ShutdownRequested()) { 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)))); + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 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, progress_current); + WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); } CBlock block; @@ -1776,11 +1775,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock } } if (pindex && fAbortRescan) { - LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("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); + WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); } - ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI } return ret; } @@ -1823,7 +1822,7 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman) CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { - LogPrintf("Relaying wtx %s\n", GetHash().ToString()); + pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { CInv inv(MSG_TX, GetHash()); connman->ForEachNode([&inv](CNode* pnode) @@ -2085,7 +2084,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman // block was found: std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); if (!relayed.empty()) - LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); + WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } /** @} */ // end of mapWallet @@ -3025,7 +3024,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac } } - LogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, feeCalc.est.pass.start, feeCalc.est.pass.end, 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool), @@ -3051,7 +3050,7 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve wtxNew.fTimeReceivedIsTxTime = true; wtxNew.fFromMe = true; - LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ + WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ { // Take key pair from key pool so it won't be used again reservekey.KeepKey(); @@ -3077,7 +3076,7 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve { // Broadcast if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { - LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); + WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { wtx.RelayWalletTransaction(connman); @@ -3285,7 +3284,7 @@ bool CWallet::NewKeyPool() if (!TopUpKeyPool()) { return false; } - LogPrintf("CWallet::NewKeyPool rewrote keypool\n"); + WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n"); } return true; } @@ -3369,7 +3368,7 @@ 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() + set_pre_split_keypool.size(), setInternalKeyPool.size()); + WalletLogPrintf("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; @@ -3414,7 +3413,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe } m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - LogPrintf("keypool reserve %d\n", nIndex); + WalletLogPrintf("keypool reserve %d\n", nIndex); } return true; } @@ -3424,7 +3423,7 @@ void CWallet::KeepKey(int64_t nIndex) // Remove from key pool WalletBatch batch(*database); batch.ErasePool(nIndex); - LogPrintf("keypool keep %d\n", nIndex); + WalletLogPrintf("keypool keep %d\n", nIndex); } void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) @@ -3441,7 +3440,7 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) } m_pool_key_to_index[pubkey.GetID()] = nIndex; } - LogPrintf("keypool return %d\n", nIndex); + WalletLogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) @@ -3703,7 +3702,7 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) } LearnAllRelatedScripts(keypool.vchPubKey); batch.ErasePool(index); - LogPrintf("keypool index %d removed\n", index); + WalletLogPrintf("keypool index %d removed\n", index); it = setKeyPool->erase(it); } } @@ -3867,7 +3866,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const int64_t blocktime = pindex->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { - LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); + WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); } } return nTimeSmart; @@ -4006,7 +4005,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } } - uiInterface.InitMessage(_("Loading wallet...")); + uiInterface.InitMessage(strprintf(_("Loading wallet %s..."), walletFile)); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; @@ -4047,12 +4046,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); if (nMaxVersion == 0) // the -upgradewallet without argument case { - LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); + walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = FEATURE_LATEST; walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately } else - LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); if (nMaxVersion < walletInstance->GetVersion()) { InitError(_("Cannot downgrade wallet")); @@ -4075,7 +4074,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, bool hd_upgrade = false; bool split_upgrade = false; if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { - LogPrintf("Upgrading wallet to HD\n"); + walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); walletInstance->SetMinVersion(FEATURE_HD); // generate a new master key @@ -4085,7 +4084,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } // Upgrade to HD chain split if necessary if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { - LogPrintf("Upgrading wallet to use HD chain split\n"); + walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n"); walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); split_upgrade = FEATURE_HD_SPLIT > prev_version; } @@ -4218,7 +4217,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); - LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("wallet %15dms\n", GetTimeMillis() - nStart); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); @@ -4254,7 +4253,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } uiInterface.InitMessage(_("Rescanning...")); - LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) @@ -4271,7 +4270,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } - LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->ChainStateFlushed(chainActive.GetLocator()); walletInstance->database->IncrementUpdateCounter(); @@ -4310,9 +4309,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, { LOCK(walletInstance->cs_wallet); - LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); - LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); - LogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); + walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); + walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); + walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); } return walletInstance; @@ -4379,7 +4378,9 @@ int CMerkleTx::GetBlocksToMaturity() const { if (!IsCoinBase()) return 0; - return std::max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain()); + int chain_depth = GetDepthInMainChain(); + assert(chain_depth >= 0); // coinbase tx should not be conflicted + return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e005147073..06a7c0a752 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1195,6 +1195,19 @@ public: /** overwrite all flags by the given uint64_t returns false if unknown, non-tolerable flags are present */ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly); + + /** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */ + const std::string GetDisplayName() const { + std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName(); + return strprintf("[%s]", wallet_name); + }; + + /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */ + template<typename... Params> + void WalletLogPrintf(std::string fmt, Params... parameters) const { + LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); + }; + }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 43e4747317..7f9e4a0458 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -545,7 +545,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) Dbc* pcursor = m_batch.GetCursor(); if (!pcursor) { - LogPrintf("Error getting wallet database cursor\n"); + pwallet->WalletLogPrintf("Error getting wallet database cursor\n"); return DBErrors::CORRUPT; } @@ -559,7 +559,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) break; else if (ret != 0) { - LogPrintf("Error reading next record from wallet database\n"); + pwallet->WalletLogPrintf("Error reading next record from wallet database\n"); return DBErrors::CORRUPT; } @@ -583,7 +583,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) } } if (!strErr.empty()) - LogPrintf("%s\n", strErr); + pwallet->WalletLogPrintf("%s\n", strErr); } pcursor->close(); } @@ -602,9 +602,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (result != DBErrors::LOAD_OK) return result; - LogPrintf("nFileVersion = %d\n", wss.nFileVersion); + pwallet->WalletLogPrintf("nFileVersion = %d\n", wss.nFileVersion); - LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n", + pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records); // nTimeFirstKey is only reliable if all keys have metadata diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index b484bffe0d..10f8b92a8f 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -62,7 +62,7 @@ def create_transaction(node, coinbase, to_address, amount): class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-promiscuousmempoolflags=1', '-whitelist=127.0.0.1']] + self.extra_args = [['-whitelist=127.0.0.1']] self.setup_clean_chain = True def run_test(self): @@ -116,12 +116,13 @@ class BIP65Test(BitcoinTestFramework): spendtx.rehash() # First we show that this tx is valid except for CLTV by getting it - # accepted to the mempool (which we can achieve with - # -promiscuousmempoolflags). - self.nodes[0].p2p.send_and_ping(msg_tx(spendtx)) - assert spendtx.hash in self.nodes[0].getrawmempool() + # rejected from the mempool for exactly that reason. + assert_equal( + [{'txid': spendtx.hash, 'allowed': False, 'reject-reason': '64: non-mandatory-script-verify-flag (Negative locktime)'}], + self.nodes[0].testmempoolaccept(rawtxs=[bytes_to_hex_str(spendtx.serialize())], allowhighfees=True) + ) - # Now we verify that a block with this transaction is invalid. + # Now we verify that a block with this transaction is also invalid. block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 13224466d3..4337d42dc0 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -47,10 +47,11 @@ def create_transaction(node, coinbase, to_address, amount): tx.deserialize(BytesIO(hex_str_to_bytes(signresult['hex']))) return tx + class BIP66Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-promiscuousmempoolflags=1', '-whitelist=127.0.0.1']] + self.extra_args = [['-whitelist=127.0.0.1']] self.setup_clean_chain = True def run_test(self): @@ -108,12 +109,13 @@ class BIP66Test(BitcoinTestFramework): spendtx.rehash() # First we show that this tx is valid except for DERSIG by getting it - # accepted to the mempool (which we can achieve with - # -promiscuousmempoolflags). - self.nodes[0].p2p.send_and_ping(msg_tx(spendtx)) - assert spendtx.hash in self.nodes[0].getrawmempool() + # rejected from the mempool for exactly that reason. + assert_equal( + [{'txid': spendtx.hash, 'allowed': False, 'reject-reason': '64: non-mandatory-script-verify-flag (Non-canonical DER signature)'}], + self.nodes[0].testmempoolaccept(rawtxs=[bytes_to_hex_str(spendtx.serialize())], allowhighfees=True) + ) - # Now we verify that a block with this transaction is invalid. + # Now we verify that a block with this transaction is also invalid. block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index b10306b283..99ad2d5222 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -43,8 +43,8 @@ class SegWitTest(BitcoinTestFramework): self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. 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"]] + ["-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"], + ["-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]] def setup_network(self): super().setup_network() @@ -64,12 +64,8 @@ class SegWitTest(BitcoinTestFramework): sync_blocks(self.nodes) def fail_accept(self, node, error_msg, txid, sign, redeem_script=""): - assert_raises_rpc_error(-26, error_msg, send_to_witness, 1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script) + assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script) - def fail_mine(self, node, txid, sign, redeem_script=""): - send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script) - assert_raises_rpc_error(-1, "CreateNewBlock: TestBlockValidity failed", node.generate, 1) - sync_blocks(self.nodes) def run_test(self): self.nodes[0].generate(161) #block 161 @@ -171,10 +167,10 @@ class SegWitTest(BitcoinTestFramework): assert(self.nodes[0].getrawtransaction(segwit_tx_list[i]) == bytes_to_hex_str(tx.serialize_without_witness())) self.log.info("Verify witness txs without witness data are invalid after the fork") - self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][2], False) - self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][2], False) - self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][2], False, witness_script(False, self.pubkey[2])) - self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][2], False, witness_script(True, self.pubkey[2])) + self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program hash mismatch) (code 64)', wit_ids[NODE_2][WIT_V0][2], sign=False) + self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness) (code 64)', wit_ids[NODE_2][WIT_V1][2], sign=False) + self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program hash mismatch) (code 64)', p2sh_ids[NODE_2][WIT_V0][2], sign=False, redeem_script=witness_script(False, self.pubkey[2])) + self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness) (code 64)', p2sh_ids[NODE_2][WIT_V1][2], sign=False, redeem_script=witness_script(True, self.pubkey[2])) self.log.info("Verify default node can now use witness txs") self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) #block 432 diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d324cd9bba..b5c440625f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -229,7 +229,7 @@ def main(): logging.basicConfig(format='%(message)s', level=logging_level) # Create base test directory - tmpdir = "%s/bitcoin_test_runner_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) + tmpdir = "%s/test_runner_₿_🏃_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) os.makedirs(tmpdir) logging.debug("Temporary test directory at %s" % tmpdir) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index fa5a2154a4..e2938dba3b 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -30,6 +30,11 @@ class MultiWalletTest(BitcoinTestFramework): wallet_dir = lambda *p: data_dir('wallets', *p) wallet = lambda name: node.get_wallet_rpc(name) + def wallet_file(name): + if os.path.isdir(wallet_dir(name)): + return wallet_dir(name, "wallet.dat") + return wallet_dir(name) + # check wallet.dat is created self.stop_nodes() assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True) @@ -43,6 +48,12 @@ class MultiWalletTest(BitcoinTestFramework): # directory paths) can be loaded os.rename(wallet_dir("wallet.dat"), wallet_dir("w8")) + # create another dummy wallet for use in testing backups later + self.start_node(0, []) + self.stop_nodes() + empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat') + os.rename(wallet_dir("wallet.dat"), empty_wallet) + # restart node with a mix of wallet names: # w1, w2, w3 - to verify new wallets created when non-existing paths specified # w - to verify wallet name matching works when one wallet path is prefix of another @@ -59,10 +70,7 @@ class MultiWalletTest(BitcoinTestFramework): # check that all requested wallets were created self.stop_node(0) for wallet_name in wallet_names: - if os.path.isdir(wallet_dir(wallet_name)): - assert_equal(os.path.isfile(wallet_dir(wallet_name, "wallet.dat")), True) - else: - assert_equal(os.path.isfile(wallet_dir(wallet_name)), True) + assert_equal(os.path.isfile(wallet_file(wallet_name)), True) # should not initialize if wallet path can't be created exp_stderr = "boost::filesystem::create_directory: (The system cannot find the path specified|Not a directory):" @@ -265,5 +273,25 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].listwallets(), ['w1']) assert_equal(w1.getwalletinfo()['walletname'], 'w1') + # Test backing up and restoring wallets + self.log.info("Test wallet backup") + self.restart_node(0, ['-nowallet']) + for wallet_name in wallet_names: + self.nodes[0].loadwallet(wallet_name) + for wallet_name in wallet_names: + rpc = self.nodes[0].get_wallet_rpc(wallet_name) + addr = rpc.getnewaddress() + backup = os.path.join(self.options.tmpdir, 'backup.dat') + rpc.backupwallet(backup) + self.nodes[0].unloadwallet(wallet_name) + shutil.copyfile(empty_wallet, wallet_file(wallet_name)) + self.nodes[0].loadwallet(wallet_name) + assert_equal(rpc.getaddressinfo(addr)['ismine'], False) + self.nodes[0].unloadwallet(wallet_name) + shutil.copyfile(backup, wallet_file(wallet_name)) + self.nodes[0].loadwallet(wallet_name) + assert_equal(rpc.getaddressinfo(addr)['ismine'], True) + + if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py new file mode 100755 index 0000000000..c07dfe317b --- /dev/null +++ b/test/lint/lint-format-strings.py @@ -0,0 +1,254 @@ +#!/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. +# +# Lint format strings: This program checks that the number of arguments passed +# to a variadic format string function matches the number of format specifiers +# in the format string. + +import argparse +import re +import sys + +FALSE_POSITIVES = [ + ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), + ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), + ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), + ("src/util.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), + ("src/util.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), + ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), +] + + +def parse_function_calls(function_name, source_code): + """Return an array with all calls to function function_name in string source_code. + Preprocessor directives and C++ style comments ("//") in source_code are removed. + + >>> len(parse_function_calls("foo", "foo();bar();foo();bar();")) + 2 + >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[0].startswith("foo(1);") + True + >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[1].startswith("foo(2);") + True + >>> len(parse_function_calls("foo", "foo();bar();// foo();bar();")) + 1 + >>> len(parse_function_calls("foo", "#define FOO foo();")) + 0 + """ + assert(type(function_name) is str and type(source_code) is str and function_name) + lines = [re.sub("// .*", " ", line).strip() + for line in source_code.split("\n") + if not line.strip().startswith("#")] + return re.findall(r"[^a-zA-Z_](?=({}\(.*).*)".format(function_name), " " + " ".join(lines)) + + +def normalize(s): + """Return a normalized version of string s with newlines, tabs and C style comments ("/* ... */") + replaced with spaces. Multiple spaces are replaced with a single space. + + >>> normalize(" /* nothing */ foo\tfoo /* bar */ foo ") + 'foo foo foo' + """ + assert(type(s) is str) + s = s.replace("\n", " ") + s = s.replace("\t", " ") + s = re.sub("/\*.*?\*/", " ", s) + s = re.sub(" {2,}", " ", s) + return s.strip() + + +ESCAPE_MAP = { + r"\n": "[escaped-newline]", + r"\t": "[escaped-tab]", + r'\"': "[escaped-quote]", +} + + +def escape(s): + """Return the escaped version of string s with "\\\"", "\\n" and "\\t" escaped as + "[escaped-backslash]", "[escaped-newline]" and "[escaped-tab]". + + >>> unescape(escape("foo")) == "foo" + True + >>> escape(r'foo \\t foo \\n foo \\\\ foo \\ foo \\"bar\\"') + 'foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]' + """ + assert(type(s) is str) + for raw_value, escaped_value in ESCAPE_MAP.items(): + s = s.replace(raw_value, escaped_value) + return s + + +def unescape(s): + """Return the unescaped version of escaped string s. + Reverses the replacements made in function escape(s). + + >>> unescape(escape("bar")) + 'bar' + >>> unescape("foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]") + 'foo \\\\t foo \\\\n foo \\\\\\\\ foo \\\\ foo \\\\"bar\\\\"' + """ + assert(type(s) is str) + for raw_value, escaped_value in ESCAPE_MAP.items(): + s = s.replace(escaped_value, raw_value) + return s + + +def parse_function_call_and_arguments(function_name, function_call): + """Split string function_call into an array of strings consisting of: + * the string function_call followed by "(" + * the function call argument #1 + * ... + * the function call argument #n + * a trailing ");" + + The strings returned are in escaped form. See escape(...). + + >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') + ['foo(', '"%s",', ' "foo"', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') + ['foo(', '"%s",', ' "foo"', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("%s %s", "foo", "bar");') + ['foo(', '"%s %s",', ' "foo",', ' "bar"', ')'] + >>> parse_function_call_and_arguments("fooprintf", 'fooprintf("%050d", i);') + ['fooprintf(', '"%050d",', ' i', ')'] + >>> parse_function_call_and_arguments("foo", 'foo(bar(foobar(barfoo("foo"))), foobar); barfoo') + ['foo(', 'bar(foobar(barfoo("foo"))),', ' foobar', ')'] + >>> parse_function_call_and_arguments("foo", "foo()") + ['foo(', '', ')'] + >>> parse_function_call_and_arguments("foo", "foo(123)") + ['foo(', '123', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("foo")') + ['foo(', '"foo"', ')'] + """ + assert(type(function_name) is str and type(function_call) is str and function_name) + remaining = normalize(escape(function_call)) + expected_function_call = "{}(".format(function_name) + assert(remaining.startswith(expected_function_call)) + parts = [expected_function_call] + remaining = remaining[len(expected_function_call):] + open_parentheses = 1 + in_string = False + parts.append("") + for char in remaining: + parts.append(parts.pop() + char) + if char == "\"": + in_string = not in_string + continue + if in_string: + continue + if char == "(": + open_parentheses += 1 + continue + if char == ")": + open_parentheses -= 1 + if open_parentheses > 1: + continue + if open_parentheses == 0: + parts.append(parts.pop()[:-1]) + parts.append(char) + break + if char == ",": + parts.append("") + return parts + + +def parse_string_content(argument): + """Return the text within quotes in string argument. + + >>> parse_string_content('1 "foo %d bar" 2') + 'foo %d bar' + >>> parse_string_content('1 foobar 2') + '' + >>> parse_string_content('1 "bar" 2') + 'bar' + >>> parse_string_content('1 "foo" 2 "bar" 3') + 'foobar' + >>> parse_string_content('1 "foo" 2 " " "bar" 3') + 'foo bar' + >>> parse_string_content('""') + '' + >>> parse_string_content('') + '' + >>> parse_string_content('1 2 3') + '' + """ + assert(type(argument) is str) + string_content = "" + in_string = False + for char in normalize(escape(argument)): + if char == "\"": + in_string = not in_string + elif in_string: + string_content += char + return string_content + + +def count_format_specifiers(format_string): + """Return the number of format specifiers in string format_string. + + >>> count_format_specifiers("foo bar foo") + 0 + >>> count_format_specifiers("foo %d bar foo") + 1 + >>> count_format_specifiers("foo %d bar %i foo") + 2 + >>> count_format_specifiers("foo %d bar %i foo %% foo") + 2 + >>> count_format_specifiers("foo %d bar %i foo %% foo %d foo") + 3 + >>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo") + 4 + """ + assert(type(format_string) is str) + n = 0 + in_specifier = False + for i, char in enumerate(format_string): + if format_string[i - 1:i + 1] == "%%" or format_string[i:i + 2] == "%%": + pass + elif char == "%": + in_specifier = True + n += 1 + elif char in "aAcdeEfFgGinopsuxX": + in_specifier = False + elif in_specifier and char == "*": + n += 1 + return n + + +def main(): + parser = argparse.ArgumentParser(description="This program checks that the number of arguments passed " + "to a variadic format string function matches the number of format " + "specifiers in the format string.") + parser.add_argument("--skip-arguments", type=int, help="number of arguments before the format string " + "argument (e.g. 1 in the case of fprintf)", default=0) + parser.add_argument("function_name", help="function name (e.g. fprintf)", default=None) + parser.add_argument("file", type=argparse.FileType("r", encoding="utf-8"), nargs="*", help="C++ source code file (e.g. foo.cpp)") + args = parser.parse_args() + + exit_code = 0 + for f in args.file: + for function_call_str in parse_function_calls(args.function_name, f.read()): + parts = parse_function_call_and_arguments(args.function_name, function_call_str) + relevant_function_call_str = unescape("".join(parts))[:512] + if (f.name, relevant_function_call_str) in FALSE_POSITIVES: + continue + if len(parts) < 3 + args.skip_arguments: + exit_code = 1 + print("{}: Could not parse function call string \"{}(...)\": {}".format(f.name, args.function_name, relevant_function_call_str)) + continue + argument_count = len(parts) - 3 - args.skip_arguments + format_str = parse_string_content(parts[1 + args.skip_arguments]) + format_specifier_count = count_format_specifiers(format_str) + if format_specifier_count != argument_count: + exit_code = 1 + print("{}: Expected {} argument(s) after format string but found {} argument(s): {}".format(f.name, format_specifier_count, argument_count, relevant_function_call_str)) + continue + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh new file mode 100755 index 0000000000..17f846d29b --- /dev/null +++ b/test/lint/lint-format-strings.sh @@ -0,0 +1,41 @@ +#!/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. +# +# Lint format strings: This program checks that the number of arguments passed +# to a variadic format string function matches the number of format specifiers +# in the format string. + +export LC_ALL=C + +FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=( + FatalError,0 + fprintf,1 + LogConnectFailure,1 + LogPrint,1 + LogPrintf,0 + printf,0 + snprintf,2 + sprintf,1 + strprintf,0 + vfprintf,1 + vprintf,1 + vsnprintf,1 + vsprintf,1 + WalletLogPrintf,0 +) + +EXIT_CODE=0 +if ! python3 -m doctest test/lint/lint-format-strings.py; then + EXIT_CODE=1 +fi +for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do + IFS="," read -r FUNCTION_NAME SKIP_ARGUMENTS <<< "${S}" + mapfile -t MATCHING_FILES < <(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|tinyformat|univalue)") + if ! test/lint/lint-format-strings.py --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then + EXIT_CODE=1 + fi +done +exit ${EXIT_CODE} |