diff options
123 files changed, 2591 insertions, 1501 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 0a644992dd..ac17e2eeb6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,7 +33,7 @@ before_build: - ps: Start-Process clcache-server - ps: fsutil behavior set disablelastaccess 0 # Enable Access time feature on Windows (for clcache) build_script: -- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nologo +- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:n /nologo after_build: - ps: fsutil behavior set disablelastaccess 1 # Disable Access time feature on Windows (better performance) - ps: clcache -z diff --git a/.gitignore b/.gitignore index 5cef80f92f..edc683e48b 100644 --- a/.gitignore +++ b/.gitignore @@ -86,10 +86,14 @@ src/qt/bitcoin-qt.includes # Compilation and Qt preprocessor part *.qm Makefile +!depends/Makefile bitcoin-qt Bitcoin-Qt.app background.tiff* +# Qt Creator +Makefile.am.user + # Unit-tests Makefile.test bitcoin-qt_test diff --git a/.travis.yml b/.travis.yml index b893f35699..d49c116a5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,8 +64,10 @@ before_script: script: - export CONTINUE=1 - if [ $SECONDS -gt 1200 ]; then export CONTINUE=0; fi # Likely the depends build took very long + - if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # Whitelisted repo (90 minutes build time) - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_a.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi - if [ $SECONDS -gt 2000 ]; then export CONTINUE=0; fi # Likely the build took very long; The tests take about 1000s, so we should abort if we have less than 50*60-1000=2000s left + - if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # Whitelisted repo (90 minutes build time) - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_b.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi after_script: - echo $TRAVIS_COMMIT_RANGE @@ -130,21 +132,21 @@ jobs: CONFIG_SHELL="/bin/dash" - stage: test - name: 'x86_64 Linux [GOAL: install] [bionic] [uses qt5 dev package instead of depends Qt to speed up build and avoid timeout]' + name: 'x86_64 Linux [GOAL: install] [bionic] [uses qt5 dev package instead of depends Qt to speed up build and avoid timeout] [unsigned char]' 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" TEST_RUNNER_EXTRA="--coverage --extended --exclude feature_dbcrash" # Run extended tests so that coverage does not fail, but exclude the very slow dbcrash GOAL="install" - BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CXXFLAGS=\"-g0 -O2\"" + BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\"" - stage: test name: 'x86_64 Linux [GOAL: install] [trusty] [no functional tests, no depends, only system libs]' env: >- HOST=x86_64-unknown-linux-gnu DOCKER_NAME_TAG=ubuntu:14.04 - PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libicu-dev libpng-dev libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.1++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" + PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libicu-dev libpng-dev libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.1++-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 RUN_FUNCTIONAL_TESTS=false GOAL="install" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2456f5b8c..268259c82d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,21 +80,15 @@ The title of the pull request should be prefixed by the component or area that the pull request affects. Valid areas as: - *Consensus* for changes to consensus critical code - - *Docs* for changes to the documentation + - *Doc* for changes to the documentation - *Qt* for changes to bitcoin-qt + - *Log* Changes to log messages - *Mining* for changes to the mining code - *Net* or *P2P* for changes to the peer-to-peer network code + - *Refactor* for structural changes that do not change behavior - *RPC/REST/ZMQ* for changes to the RPC, REST or ZMQ APIs - *Scripts and tools* for changes to the scripts and tools - - *Tests* for changes to the bitcoin unit tests or QA tests - - *Trivial* should **only** be used for PRs that do not change generated - executable code. Notably, refactors (change of function arguments and code - reorganization) and changes in behavior should **not** be marked as trivial. - Examples of trivial PRs are changes to: - - comments - - whitespace - - variable names - - logging and messages + - *Test* for changes to the bitcoin unit tests or QA tests - *Utils and libraries* for changes to the utils and libraries - *Wallet* for changes to the wallet code @@ -103,10 +97,10 @@ Examples: Consensus: Add new opcode for BIP-XXXX OP_CHECKAWESOMESIG Net: Automatically create hidden service, listen on Tor Qt: Add feed bump button - Trivial: Fix typo in init.cpp + Log: Fix typo in log message Note that translations should not be submitted as pull requests, please see -[Translation Process](https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md) +[Translation Process](https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md) for more information on helping with translations. If a pull request is not to be considered for merging (yet), please @@ -322,7 +316,7 @@ The project leader is the release manager for each Bitcoin Core release. Copyright --------- -By contributing to this repository, you agree to license your work under the -MIT license unless specified otherwise in `contrib/debian/copyright` or at -the top of the file itself. Any work contributed where you are not the original +By contributing to this repository, you agree to license your work under the +MIT license unless specified otherwise in `contrib/debian/copyright` or at +the top of the file itself. Any work contributed where you are not the original author must contain its license header with the original author(s) and source. diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index 8ccbb19300..675fb6d3fb 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -268,7 +268,7 @@ AC_DEFUN([_BITCOIN_QT_CHECK_QT5],[ 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 +dnl Output: bitcoin_cv_qt58=yes|no AC_DEFUN([_BITCOIN_QT_CHECK_QT58],[ AC_CACHE_CHECK(for > Qt 5.7, bitcoin_cv_qt58,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @@ -468,7 +468,6 @@ 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([jpeg_create_decompress] ,[qtjpeg jpeg],,AC_MSG_WARN([libjpeg not found. Assuming qt has it built-in]))) 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]))) diff --git a/build_msvc/README.md b/build_msvc/README.md index d6543e6d67..2e93979aca 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -44,7 +44,7 @@ The instructions below use `vcpkg` to install the dependencies. - Use Python to generate *.vcxproj from Makefile ``` - PS >python msvc-autogen.py + PS >py -3 msvc-autogen.py ``` - Build in Visual Studio. diff --git a/configure.ac b/configure.ac index 08a850cfa2..4fd285bc8d 100644 --- a/configure.ac +++ b/configure.ac @@ -239,7 +239,7 @@ AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) # Enable debug AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], - [use debug compiler flags and macros (default is no)])], + [use compiler flags and macros suited for debugging (default is no)])], [enable_debug=$enableval], [enable_debug=no]) @@ -271,12 +271,9 @@ if test "x$enable_debug" = xyes; then 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]]) + + # Disable all optimizations + AX_CHECK_COMPILE_FLAG([-O0], [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]]) # Prefer -g3, fall back to -g if that is unavailable. AX_CHECK_COMPILE_FLAG( @@ -323,6 +320,7 @@ if test "x$enable_werror" = "xyes"; then AC_MSG_ERROR("enable-werror set but -Werror is not usable") fi AX_CHECK_COMPILE_FLAG([-Werror=vla],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=vla"],,[[$CXXFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-Werror=switch],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=switch"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Werror=thread-safety-analysis],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=thread-safety-analysis"],,[[$CXXFLAG_WERROR]]) fi @@ -331,6 +329,7 @@ if test "x$CXXFLAGS_overridden" = "xno"; then 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([-Wswitch],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wswitch"],,[[$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]]) @@ -992,6 +991,26 @@ if test x$use_upnp != xno; then [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS=-lminiupnpc], [have_miniupnpc=no])], [have_miniupnpc=no] ) +dnl The minimum supported miniUPnPc API version is set to 10. This keeps compatibility +dnl with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. +if test x$have_miniupnpc != xno; then + AC_MSG_CHECKING([whether miniUPnPc API version is supported]) + AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ + @%:@include <miniupnpc/miniupnpc.h> + ]], [[ + #if MINIUPNPC_API_VERSION >= 10 + // Everything is okay + #else + # error miniUPnPc API version is too old + #endif + ]])],[ + AC_MSG_RESULT(yes) + ],[ + AC_MSG_RESULT(no) + AC_MSG_WARN([miniUPnPc API version < 10 is unsupported, disabling UPnP support.]) + have_miniupnpc=no + ]) +fi fi if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then @@ -1385,9 +1404,10 @@ dnl enable upnp support AC_MSG_CHECKING([whether to build with support for UPnP]) if test x$have_miniupnpc = xno; then if test x$use_upnp = xyes; then - AC_MSG_ERROR("UPnP requested but cannot be built. use --without-miniupnpc") + AC_MSG_ERROR("UPnP requested but cannot be built. Use --without-miniupnpc.") fi AC_MSG_RESULT(no) + use_upnp=no else if test x$use_upnp != xno; then AC_MSG_RESULT(yes) @@ -1602,7 +1622,7 @@ if test x$need_bundled_univalue = xyes; then AC_CONFIG_SUBDIRS([src/univalue]) fi -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --disable-jni" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT diff --git a/contrib/debian/copyright b/contrib/debian/copyright index 2d5b0188d2..8fb9df9e6c 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -60,7 +60,7 @@ Files: src/qt/res/icons/connect*.png Copyright: Marco Falke Luke Dashjr License: Expat -Comment: Inspired by Stephan Hutchings Typicons +Comment: Inspired by Stephen Hutchings' Typicons Files: src/qt/res/icons/tx_mined.png src/qt/res/src/mine.svg @@ -72,7 +72,6 @@ Files: src/qt/res/icons/tx_mined.png src/qt/res/src/hd_enabled.svg Copyright: Jonas Schnelli License: Expat -Comment: Files: src/qt/res/icons/clock*.png src/qt/res/icons/eye_*.png @@ -81,9 +80,9 @@ Files: src/qt/res/icons/clock*.png src/qt/res/src/clock_*.svg src/qt/res/src/tx_*.svg src/qt/res/src/verify.svg -Copyright: Stephan Hutching, Jonas Schnelli +Copyright: Stephen Hutchings, Jonas Schnelli License: Expat -Comment: Modifications of Stephan Hutchings Typicons +Comment: Modifications of Stephen Hutchings' Typicons Files: src/qt/res/icons/about.png src/qt/res/icons/bitcoin.* diff --git a/contrib/gitian-keys/keys.txt b/contrib/gitian-keys/keys.txt index 33f0f7e5b0..9222a40b17 100644 --- a/contrib/gitian-keys/keys.txt +++ b/contrib/gitian-keys/keys.txt @@ -1,3 +1,4 @@ +9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C Aaron Clauson (sipsorcery) 617C90010B3BD370B0AC7D424BB42E31C79111B8 Akira Takizawa E944AE667CF960B1004BC32FCA662BE18B877A60 Andreas Schildbach 152812300785C96444D3334D17565732E08E5E41 Andrew Chow diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index c497c5ac20..90bc15f9f4 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -7,6 +7,18 @@ $(package)_dependencies=xcb_proto libXau define $(package)_set_vars $(package)_config_opts=--disable-static +# Because we pass -qt-xcb to Qt, it will compile in a set of xcb helper libraries and extensions, +# so we skip building all of the extensions here. +# More info is available from: https://doc.qt.io/qt-5.9/linux-requirements.html +$(package)_config_opts += --disable-composite --disable-damage --disable-dpms +$(package)_config_opts += --disable-dri2 --disable-dri3 --disable-glx +$(package)_config_opts += --disable-present --disable-randr --disable-record +$(package)_config_opts += --disable-render --disable-resource --disable-screensaver +$(package)_config_opts += --disable-shape --disable-shm --disable-sync +$(package)_config_opts += --disable-xevie --disable-xfixes --disable-xfree86-dri +$(package)_config_opts += --disable-xinerama --disable-xinput --disable-xkb +$(package)_config_opts += --disable-xprint --disable-selinux --disable-xtest +$(package)_config_opts += --disable-xv --disable-xvmc endef define $(package)_preprocess_cmds diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 6f369e2c52..2610c1e748 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -35,9 +35,11 @@ $(package)_config_opts += -no-freetype $(package)_config_opts += -no-gif $(package)_config_opts += -no-glib $(package)_config_opts += -no-icu +$(package)_config_opts += -no-ico $(package)_config_opts += -no-iconv $(package)_config_opts += -no-kms $(package)_config_opts += -no-linuxfb +$(package)_config_opts += -no-libjpeg $(package)_config_opts += -no-libudev $(package)_config_opts += -no-mtdev $(package)_config_opts += -no-openvg @@ -63,26 +65,42 @@ $(package)_config_opts += -pch $(package)_config_opts += -pkg-config $(package)_config_opts += -prefix $(host_prefix) $(package)_config_opts += -qt-libpng -$(package)_config_opts += -qt-libjpeg $(package)_config_opts += -qt-pcre $(package)_config_opts += -qt-harfbuzz $(package)_config_opts += -system-zlib $(package)_config_opts += -static $(package)_config_opts += -silent $(package)_config_opts += -v +$(package)_config_opts += -no-feature-bearermanagement +$(package)_config_opts += -no-feature-colordialog +$(package)_config_opts += -no-feature-commandlineparser +$(package)_config_opts += -no-feature-concurrent $(package)_config_opts += -no-feature-dial +$(package)_config_opts += -no-feature-filesystemwatcher +$(package)_config_opts += -no-feature-fontcombobox $(package)_config_opts += -no-feature-ftp +$(package)_config_opts += -no-feature-image_heuristic_mask +$(package)_config_opts += -no-feature-keysequenceedit $(package)_config_opts += -no-feature-lcdnumber $(package)_config_opts += -no-feature-pdf -$(package)_config_opts += -no-feature-printer $(package)_config_opts += -no-feature-printdialog -$(package)_config_opts += -no-feature-concurrent +$(package)_config_opts += -no-feature-printer +$(package)_config_opts += -no-feature-printpreviewdialog +$(package)_config_opts += -no-feature-printpreviewwidget +$(package)_config_opts += -no-feature-regularexpression +$(package)_config_opts += -no-feature-sessionmanager $(package)_config_opts += -no-feature-sql $(package)_config_opts += -no-feature-statemachine $(package)_config_opts += -no-feature-syntaxhighlighter $(package)_config_opts += -no-feature-textbrowser $(package)_config_opts += -no-feature-textodfwriter +$(package)_config_opts += -no-feature-topleveldomain $(package)_config_opts += -no-feature-udpsocket +$(package)_config_opts += -no-feature-undocommand +$(package)_config_opts += -no-feature-undogroup +$(package)_config_opts += -no-feature-undostack +$(package)_config_opts += -no-feature-undoview +$(package)_config_opts += -no-feature-vnc $(package)_config_opts += -no-feature-wizard $(package)_config_opts += -no-feature-xml @@ -101,7 +119,6 @@ $(package)_config_opts_linux += -qt-xcb $(package)_config_opts_linux += -no-xcb-xlib $(package)_config_opts_linux += -no-feature-xlib $(package)_config_opts_linux += -system-freetype -$(package)_config_opts_linux += -no-feature-sessionmanager $(package)_config_opts_linux += -fontconfig $(package)_config_opts_linux += -no-opengl $(package)_config_opts_arm_linux += -platform linux-g++ -xplatform bitcoin-linux-g++ diff --git a/doc/benchmarking.md b/doc/benchmarking.md index 48f81cf6c1..8e3d88ab7a 100644 --- a/doc/benchmarking.md +++ b/doc/benchmarking.md @@ -2,10 +2,17 @@ Benchmarking ============ Bitcoin Core has an internal benchmarking framework, with benchmarks -for cryptographic algorithms (e.g. SHA1, SHA256, SHA512, RIPEMD160), as well as the rolling bloom filter. +for cryptographic algorithms (e.g. SHA1, SHA256, SHA512, RIPEMD160, Poly1305, ChaCha20), rolling bloom filter, coins selection, +thread queue, wallet balance. Running --------------------- + +For benchmarks purposes you only need to compile `bitcoin_bench`. Beware of configuring without `--enable-debug` as this would impact +benchmarking by unlatching log printers and lock analysis. + + make -C src bench_bitcoin + After compiling bitcoin-core, the benchmarks can be run with: src/bench/bench_bitcoin @@ -13,43 +20,29 @@ After compiling bitcoin-core, the benchmarks can be run with: The output will look similar to: ``` # Benchmark, evals, iterations, total, min, max, median -Base58CheckEncode, 5, 320000, 120.772, 7.49351e-05, 7.59374e-05, 7.54759e-05 -Base58Decode, 5, 800000, 122.833, 3.0467e-05, 3.11732e-05, 3.06304e-05 -Base58Encode, 5, 470000, 137.094, 5.81061e-05, 5.85109e-05, 5.84462e-05 -BenchLockedPool, 5, 530, 34.2023, 0.0128247, 0.0129613, 0.0129026 -CCheckQueueSpeedPrevectorJob, 5, 1400, 26.1762, 0.00365048, 0.00388629, 0.00367108 -CCoinsCaching, 5, 170000, 48.1074, 5.60229e-05, 5.72316e-05, 5.66214e-05 -CoinSelection, 5, 650, 34.6426, 0.0105801, 0.0107699, 0.010664 -DeserializeAndCheckBlockTest, 5, 160, 39.2084, 0.0483662, 0.0494199, 0.0490138 -DeserializeBlockTest, 5, 130, 23.8129, 0.0357731, 0.0373763, 0.0365858 -FastRandom_1bit, 5, 440000000, 38.1609, 1.72974e-08, 1.73882e-08, 1.73478e-08 -FastRandom_32bit, 5, 110000000, 72.8237, 1.29992e-07, 1.37014e-07, 1.30115e-07 -MempoolEviction, 5, 41000, 89.8883, 0.000432748, 0.000446857, 0.000438483 -PrevectorClear, 5, 5600, 47.9229, 0.00169952, 0.0017455, 0.00170315 -PrevectorDestructor, 5, 5700, 44.5498, 0.0015561, 0.00156977, 0.00156469 -RIPEMD160, 5, 440, 135.988, 0.0615496, 0.062268, 0.0617779 -RollingBloom, 5, 1500000, 36.5109, 4.80961e-06, 4.97463e-06, 4.85811e-06 -SHA1, 5, 570, 51.808, 0.018065, 0.0182623, 0.0181865 -SHA256, 5, 340, 8.31841, 0.00483231, 0.00499803, 0.00485486 -SHA256_32b, 5, 4700000, 10.469, 4.43441e-07, 4.47611e-07, 4.45223e-07 -SHA512, 5, 330, 33.3408, 0.02017, 0.0202554, 0.0201921 -SipHash_32b, 5, 40000000, 38.7088, 1.91103e-07, 1.96998e-07, 1.93792e-07 -Sleep100ms, 5, 10, 5.01062, 0.100131, 0.100368, 0.100147 -Trig, 5, 12000000, 5.95494, 9.78115e-08, 1.04354e-07, 9.80682e-08 -VerifyScriptBench, 5, 6300, 9.02493, 0.000285566, 0.000288433, 0.000286175 +AssembleBlock, 5, 700, 1.79954, 0.000510913, 0.000517018, 0.000514497 +... ``` Help --------------------- -`-?` will print a list of options and exit: - src/bench/bench_bitcoin -? + src/bench/bench_bitcoin --help + +To print options like scaling factor or per-benchmark filter. Notes --------------------- More benchmarks are needed for, in no particular order: - Script Validation -- CCoinDBView caching - Coins database - Memory pool -- Wallet coin selection +- Cuckoo Cache +- P2P throughput + +Going Further +-------------------- + +To monitor Bitcoin Core performance more in depth (like reindex or IBD): https://github.com/chaincodelabs/bitcoinperf + +To generate Flame Graphs for Bitcoin Core: https://github.com/eklitzke/bitcoin/blob/flamegraphs/doc/flamegraphs.md diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index 88ecb8fe65..f8146b5d75 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -30,6 +30,21 @@ Network specific options can be: - placed into sections with headers `[main]` (not `[mainnet]`), `[test]` (not `[testnet]`) or `[regtest]`; - prefixed with a chain name; e.g., `regtest.maxmempool=100`. +Network specific options take precedence over non-network specific options. +If multiple values for the same option are found with the same precedence, the +first one is generally chosen. + +This means that given the following configuration, `regtest.rpcport` is set to `3000`: + +``` +regtest=1 +rpcport=2000 +regtest.rpcport=3000 + +[regtest] +rpcport=4000 +``` + ## Configuration File Path The configuration file is not automatically created; you can create it using your favorite text editor. By default, the configuration file name is `bitcoin.conf` and it is located in the Bitcoin data directory, but both the Bitcoin data directory and the configuration file path may be changed using the `-datadir` and `-conf` command-line options. diff --git a/doc/build-unix.md b/doc/build-unix.md index da65bc347a..9aca0336c0 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -61,6 +61,14 @@ tuned to conserve memory with additional CXXFLAGS: ./configure CXXFLAGS="--param ggc-min-expand=1 --param ggc-min-heapsize=32768" +Alternatively, or in addition, debugging information can be skipped for compilation. The default compile flags are +`-g -O2`, and can be changed with: + + ./configure CXXFLAGS="-O2" + +Finally, clang (often less resource hungry) can be used instead of gcc, which is used by default: + + ./configure CXX=clang++ CC=clang ## Linux Distribution Specific Instructions @@ -78,7 +86,7 @@ Now, you can either build from self-compiled [depends](/depends/README.md) or in BerkeleyDB is required for the wallet. -Ubuntu and Debian have their own libdb-dev and libdb++-dev packages, but these will install +Ubuntu and Debian have their own `libdb-dev` and `libdb++-dev` packages, but these will install BerkeleyDB 5.1 or later. This will break binary wallet compatibility with the distributed executables, which are based on BerkeleyDB 4.8. If you do not care about wallet compatibility, pass `--with-incompatible-bdb` to configure. diff --git a/doc/dependencies.md b/doc/dependencies.md index c9c6a93c01..0b23ca0a2d 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -7,25 +7,24 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | --- | --- | --- | --- | --- | --- | | Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | | Boost | [1.70.0](https://www.boost.org/users/download/) | [1.47.0](https://github.com/bitcoin/bitcoin/pull/8920) | No | | | -| Clang | | [3.3+](https://llvm.org/releases/download.html) (C++11 support) | | | | +| Clang | | [3.3+](https://releases.llvm.org/download.html) (C++11 support) | | | | | Expat | [2.2.7](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | | | GCC | | [4.8+](https://gcc.gnu.org/) (C++11 support) | | | | | 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#L65) | -| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L64) | +| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | librsvg | | | | | | | 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#L66) | +| PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | protobuf | [2.6.1](https://github.com/google/protobuf/releases) | | No | | | | Python (tests) | | [3.5](https://www.python.org/downloads) | | | | | qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | | Qt | [5.9.7](https://download.qt.io/official_releases/qt/) | [5.5.1](https://github.com/bitcoin/bitcoin/issues/13478) | 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) | +| XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | +| xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | | ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | | zlib | [1.2.11](https://zlib.net/) | | | | No | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 39463dc6f8..d1114b0c73 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -62,7 +62,7 @@ tool to clean up patches automatically before submission. - Braces on the same line for everything else. - 4 space indentation (no tabs) for every block except namespaces. - No indentation for `public`/`protected`/`private` or for `namespace`. - - No extra spaces inside parenthesis; don't do ( this ) + - No extra spaces inside parenthesis; don't do ( this ). - No space after function names; one space after `if`, `for` and `while`. - If an `if` only has a single-statement `then`-clause, it can appear on the same line as the `if`, without braces. In every other case, @@ -72,12 +72,12 @@ tool to clean up patches automatically before submission. - **Symbol naming conventions**. These are preferred in new code, but are not required when doing so would need changes to significant pieces of existing code. - - Variable (including function arguments) and namespace names are all lowercase, and may use `_` to + - Variable (including function arguments) and namespace names are all lowercase and may use `_` to separate words (snake_case). - Class member variables have a `m_` prefix. - Global variables have a `g_` prefix. - Constant names are all uppercase, and use `_` to separate words. - - Class names, function names and method names are UpperCamelCase + - Class names, function names, and method names are UpperCamelCase (PascalCase). Do not prefix class names with `C`. - Test suite naming convention: The Boost test suite in file `src/test/foo_tests.cpp` should be named `foo_tests`. Test suite names @@ -134,6 +134,7 @@ Bitcoin Core uses [Doxygen](http://www.doxygen.nl/) to generate its official doc Use Doxygen-compatible comment blocks for functions, methods, and fields. For example, to describe a function use: + ```c++ /** * ... text ... @@ -143,11 +144,12 @@ For example, to describe a function use: */ bool function(int arg1, const char *arg2) ``` + A complete list of `@xxx` commands can be found at http://www.doxygen.nl/manual/commands.html. As Doxygen recognizes the comments by the delimiters (`/**` and `*/` in this case), you don't *need* to provide any commands for a comment to be valid; just a description text is fine. -To describe a class use the same construct above the class definition: +To describe a class, use the same construct above the class definition: ```c++ /** * Alerts are for notifying old versions if they become too obsolete and @@ -189,7 +191,7 @@ but the above styles are favored. Documentation can be generated with `make docs` and cleaned up with `make clean-docs`. The resulting files are located in `doc/doxygen/html`; open `index.html` to view the homepage. -Before running `make docs`, you will need to install dependencies `doxygen` and `dot`. For example, on MacOS via Homebrew: +Before running `make docs`, you will need to install dependencies `doxygen` and `dot`. For example, on macOS via Homebrew: ``` brew install doxygen --with-graphviz ``` @@ -231,7 +233,7 @@ that run in `-regtest` mode. Bitcoin Core is a multi-threaded application, and deadlocks or other multi-threading bugs can be very difficult to track down. The `--enable-debug` configure option adds `-DDEBUG_LOCKORDER` to the compiler flags. This inserts -run-time checks to keep track of which locks are held, and adds warnings to the +run-time checks to keep track of which locks are held and adds warnings to the debug.log file if inconsistencies are detected. ### Valgrind suppressions file @@ -299,7 +301,7 @@ $ perf record \ -p `pgrep bitcoind` -- sleep 60 ``` -You could then analyze the results by running +You could then analyze the results by running: ```sh perf report --stdio | c++filt | less @@ -364,7 +366,7 @@ Additional resources: Locking/mutex usage notes ------------------------- -The code is multi-threaded, and uses mutexes and the +The code is multi-threaded and uses mutexes and the `LOCK` and `TRY_LOCK` macros to protect data structures. Deadlocks due to inconsistent lock ordering (thread 1 locks `cs_main` and then @@ -389,7 +391,7 @@ Threads - ThreadDNSAddressSeed : Loads addresses of peers from the DNS. -- ThreadMapPort : Universal plug-and-play startup/shutdown +- ThreadMapPort : Universal plug-and-play startup/shutdown. - ThreadSocketHandler : Sends/Receives data from peers on port 8333. @@ -408,7 +410,7 @@ Threads Ignoring IDE/editor files -------------------------- -In closed-source environments in which everyone uses the same IDE it is common +In closed-source environments in which everyone uses the same IDE, it is common to add temporary files it produces to the project-wide `.gitignore` file. However, in open source software such as Bitcoin Core, where everyone uses @@ -446,19 +448,19 @@ pay attention to for reviewers of Bitcoin Core code. General Bitcoin Core ---------------------- -- New features should be exposed on RPC first, then can be made available in the GUI +- New features should be exposed on RPC first, then can be made available in the GUI. - *Rationale*: RPC allows for better automatic testing. The test suite for - the GUI is very limited + the GUI is very limited. -- Make sure pull requests pass Travis CI before merging +- Make sure pull requests pass Travis CI before merging. - *Rationale*: Makes sure that they pass thorough testing, and that the tester will keep passing - on the master branch. Otherwise all new pull requests will start failing the tests, resulting in - confusion and mayhem + on the master branch. Otherwise, all new pull requests will start failing the tests, resulting in + confusion and mayhem. - *Explanation*: If the test suite is to be updated for a change, this has to - be done first + be done first. Wallet ------- @@ -466,13 +468,13 @@ Wallet - Make sure that no crashes happen with run-time option `-disablewallet`. - *Rationale*: In RPC code that conditionally uses the wallet (such as - `validateaddress`) it is easy to forget that global pointer `pwalletMain` + `validateaddress`), it is easy to forget that global pointer `pwalletMain` can be nullptr. See `test/functional/disablewallet.py` for functional tests - exercising the API with `-disablewallet` + exercising the API with `-disablewallet`. -- Include `db_cxx.h` (BerkeleyDB header) only when `ENABLE_WALLET` is set +- Include `db_cxx.h` (BerkeleyDB header) only when `ENABLE_WALLET` is set. - - *Rationale*: Otherwise compilation of the disable-wallet build will fail in environments without BerkeleyDB + - *Rationale*: Otherwise compilation of the disable-wallet build will fail in environments without BerkeleyDB. General C++ ------------- @@ -483,26 +485,26 @@ Guidelines](https://isocpp.github.io/CppCoreGuidelines/). Common misconceptions are clarified in those sections: - Passing (non-)fundamental types in the [C++ Core - Guideline](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-conventional) + Guideline](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-conventional). -- Assertions should not have side-effects +- Assertions should not have side-effects. - *Rationale*: Even though the source code is set to refuse to compile with assertions disabled, having side-effects in assertions is unexpected and - makes the code harder to understand + makes the code harder to understand. -- If you use the `.h`, you must link the `.cpp` +- If you use the `.h`, you must link the `.cpp`. - *Rationale*: Include files define the interface for the code in implementation files. Including one but not linking the other is confusing. Please avoid that. Moving functions from - the `.h` to the `.cpp` should not result in build errors + the `.h` to the `.cpp` should not result in build errors. -- Use the RAII (Resource Acquisition Is Initialization) paradigm where possible. For example by using +- Use the RAII (Resource Acquisition Is Initialization) paradigm where possible. For example, by using `unique_ptr` for allocations in a function. - - *Rationale*: This avoids memory and resource leaks, and ensures exception safety + - *Rationale*: This avoids memory and resource leaks, and ensures exception safety. -- Use `MakeUnique()` to construct objects owned by `unique_ptr`s +- Use `MakeUnique()` to construct objects owned by `unique_ptr`s. - *Rationale*: `MakeUnique` is concise and ensures exception safety in complex expressions. `MakeUnique` is a temporary project local implementation of `std::make_unique` (C++14). @@ -510,27 +512,27 @@ Common misconceptions are clarified in those sections: C++ data structures -------------------- -- Never use the `std::map []` syntax when reading from a map, but instead use `.find()` +- Never use the `std::map []` syntax when reading from a map, but instead use `.find()`. - *Rationale*: `[]` does an insert (of the default element) if the item doesn't exist in the map yet. This has resulted in memory leaks in the past, as well as - race conditions (expecting read-read behavior). Using `[]` is fine for *writing* to a map + race conditions (expecting read-read behavior). Using `[]` is fine for *writing* to a map. - Do not compare an iterator from one data structure with an iterator of - another data structure (even if of the same type) + another data structure (even if of the same type). - *Rationale*: Behavior is undefined. In C++ parlor this means "may reformat - the universe", in practice this has resulted in at least one hard-to-debug crash bug + the universe", in practice this has resulted in at least one hard-to-debug crash bug. - Watch out for out-of-bounds vector access. `&vch[vch.size()]` is illegal, including `&vch[0]` for an empty vector. Use `vch.data()` and `vch.data() + vch.size()` instead. -- Vector bounds checking is only enabled in debug mode. Do not rely on it +- Vector bounds checking is only enabled in debug mode. Do not rely on it. - Initialize all non-static class members where they are defined. If this is skipped for a good reason (i.e., optimization on the critical - path), add an explicit comment about this + path), add an explicit comment about this. - *Rationale*: Ensure determinism by avoiding accidental use of uninitialized values. Also, static analyzers balk about this. @@ -554,12 +556,12 @@ class A `int8_t`. Do not use bare `char` unless it is to pass to a third-party API. This type can be signed or unsigned depending on the architecture, which can lead to interoperability problems or dangerous conditions such as - out-of-bounds array accesses + out-of-bounds array accesses. -- Prefer explicit constructions over implicit ones that rely on 'magical' C++ behavior +- Prefer explicit constructions over implicit ones that rely on 'magical' C++ behavior. - *Rationale*: Easier to understand what is happening, thus easier to spot mistakes, even for those - that are not language lawyers + that are not language lawyers. Strings and formatting ------------------------ @@ -567,17 +569,17 @@ Strings and formatting - Be careful of `LogPrint` versus `LogPrintf`. `LogPrint` takes a `category` argument, `LogPrintf` does not. - *Rationale*: Confusion of these can result in runtime exceptions due to - formatting mismatch, and it is easy to get wrong because of subtly similar naming + formatting mismatch, and it is easy to get wrong because of subtly similar naming. -- Use `std::string`, avoid C string manipulation functions +- Use `std::string`, avoid C string manipulation functions. - *Rationale*: C++ string handling is marginally safer, less scope for - buffer overflows and surprises with `\0` characters. Also some C string manipulations - tend to act differently depending on platform, or even the user locale + buffer overflows, and surprises with `\0` characters. Also, some C string manipulations + tend to act differently depending on platform, or even the user locale. -- Use `ParseInt32`, `ParseInt64`, `ParseUInt32`, `ParseUInt64`, `ParseDouble` from `utilstrencodings.h` for number parsing +- 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`](/test/lint/lint-locale-dependence.sh) @@ -607,14 +609,14 @@ Strings and formatting `wcstoll`, `wcstombs`, `wcstoul`, `wcstoull`, `wcstoumax`, `wcswidth`, `wcsxfrm`, `wctob`, `wctomb`, `wctrans`, `wctype`, `wcwidth`, `wprintf` -- For `strprintf`, `LogPrint`, `LogPrintf` formatting characters don't need size specifiers +- For `strprintf`, `LogPrint`, `LogPrintf` formatting characters don't need size specifiers. - - *Rationale*: Bitcoin Core uses tinyformat, which is type safe. Leave them out to avoid confusion + - *Rationale*: Bitcoin Core uses tinyformat, which is type safe. Leave them out to avoid confusion. Variable names -------------- -Although the shadowing warning (`-Wshadow`) is not enabled by default (it prevents issues rising +Although the shadowing warning (`-Wshadow`) is not enabled by default (it prevents issues arising from using a different variable with the same name), please name variables so that their names do not shadow variables defined in the source code. @@ -633,22 +635,20 @@ AddressBookPage::AddressBookPage(Mode _mode) ``` When using nested cycles, do not name the inner cycle variable the same as in -upper cycle etc. - +the upper cycle, etc. Threads and synchronization ---------------------------- - Build and run tests with `-DDEBUG_LOCKORDER` to verify that no potential deadlocks are introduced. As of 0.12, this is defined by default when - configuring with `--enable-debug` + configuring with `--enable-debug`. - When using `LOCK`/`TRY_LOCK` be aware that the lock exists in the context of the current scope, so surround the statement and the code that needs the lock - with braces + with braces. OK: - ```c++ { TRY_LOCK(cs_vNodes, lockNodes); @@ -657,7 +657,6 @@ Threads and synchronization ``` Wrong: - ```c++ TRY_LOCK(cs_vNodes, lockNodes); { @@ -679,13 +678,11 @@ Scripts `#!/usr/bin/env bash` searches the user's PATH to find the bash binary. OK: - ```bash #!/usr/bin/env bash ``` Wrong: - ```bash #!/bin/bash ``` @@ -694,9 +691,9 @@ Source code organization -------------------------- - Implementation code should go into the `.cpp` file and not the `.h`, unless necessary due to template usage or - when performance due to inlining is critical + when performance due to inlining is critical. - - *Rationale*: Shorter and simpler header files are easier to read, and reduce compile time + - *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. @@ -714,7 +711,7 @@ Source code organization - Don't import anything into the global namespace (`using namespace ...`). Use fully specified types such as `std::string`. - - *Rationale*: Avoids symbol conflicts + - *Rationale*: Avoids symbol conflicts. - Terminate namespaces with a comment (`// namespace mynamespace`). The comment should be placed on the same line as the brace closing the namespace, e.g. @@ -729,7 +726,7 @@ namespace { } // namespace ``` - - *Rationale*: Avoids confusion about the namespace context + - *Rationale*: Avoids confusion about the namespace context. - Use `#include <primitives/transaction.h>` bracket syntax instead of `#include "primitives/transactions.h"` quote syntax. @@ -752,13 +749,13 @@ namespace { GUI ----- -- Do not display or manipulate dialogs in model code (classes `*Model`) +- Do not display or manipulate dialogs in model code (classes `*Model`). - *Rationale*: Model classes pass through events and data from the core, they should not interact with the user. That's where View classes come in. The converse also holds: try to not directly access core data structures from Views. -- Avoid adding slow or blocking code in the GUI thread. In particular do not +- Avoid adding slow or blocking code in the GUI thread. In particular, do not add new `interfaces::Node` and `interfaces::Wallet` method calls, even if they may be fast now, in case they are changed to lock or communicate across processes in the future. @@ -777,12 +774,12 @@ Subtrees Several parts of the repository are subtrees of software maintained elsewhere. Some of these are maintained by active developers of Bitcoin Core, in which case changes should probably go -directly upstream without being PRed directly against the project. They will be merged back in the next +directly upstream without being PRed directly against the project. They will be merged back in the next subtree merge. -Others are external projects without a tight relationship with our project. Changes to these should also -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. +Others are external projects without a tight relationship with our project. Changes to these should also +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 `test/lint/git-subtree-check.sh` to check a subtree directory for consistency with its upstream repository. @@ -812,11 +809,11 @@ you must be aware of. ### File Descriptor Counts -In most configurations we use the default LevelDB value for `max_open_files`, +In most configurations, we use the default LevelDB value for `max_open_files`, which is 1000 at the time of this writing. If LevelDB actually uses this many -file descriptors it will cause problems with Bitcoin's `select()` loop, because +file descriptors, it will cause problems with Bitcoin's `select()` loop, because it may cause new sockets to be created where the fd value is >= 1024. For this -reason, on 64-bit Unix systems we rely on an internal LevelDB optimization that +reason, on 64-bit Unix systems, we rely on an internal LevelDB optimization that uses `mmap()` + `close()` to open table files without actually retaining references to the table file descriptors. If you are upgrading LevelDB, you must sanity check the changes to make sure that this assumption remains valid. @@ -841,14 +838,14 @@ details. It is possible for LevelDB changes to inadvertently change consensus compatibility between nodes. This happened in Bitcoin 0.8 (when LevelDB was -first introduced). When upgrading LevelDB you should review the upstream changes +first introduced). When upgrading LevelDB, you should review the upstream changes to check for issues affecting consensus compatibility. For example, if LevelDB had a bug that accidentally prevented a key from being returned in an edge case, and that bug was fixed upstream, the bug "fix" would -be an incompatible consensus change. In this situation the correct behavior +be an incompatible consensus change. In this situation, the correct behavior would be to revert the upstream fix before applying the updates to Bitcoin's -copy of LevelDB. In general you should be wary of any upstream changes affecting +copy of LevelDB. In general, you should be wary of any upstream changes affecting what data is returned from LevelDB queries. Scripted diffs @@ -867,7 +864,7 @@ To create a scripted-diff: - `-BEGIN VERIFY SCRIPT-` - `-END VERIFY SCRIPT-` -The scripted-diff is verified by the tool `test/lint/commit-script-check.sh`. The tool's default behavior when supplied +The scripted-diff is verified by the tool `test/lint/commit-script-check.sh`. The tool's default behavior, when supplied with a commit is to verify all scripted-diffs from the beginning of time up to said commit. Internally, the tool passes the first supplied argument to `git rev-list --reverse` to determine which commits to verify script-diffs for, ignoring commits that don't conform to the commit message format described above. @@ -900,23 +897,23 @@ RPC interface guidelines A few guidelines for introducing and reviewing new RPC interfaces: -- Method naming: use consecutive lower-case names such as `getrawtransaction` and `submitblock` +- Method naming: use consecutive lower-case names such as `getrawtransaction` and `submitblock`. - - *Rationale*: Consistency with existing interface. + - *Rationale*: Consistency with the existing interface. - Argument naming: use snake case `fee_delta` (and not, e.g. camel case `feeDelta`) - - *Rationale*: Consistency with existing interface. + - *Rationale*: Consistency with the existing interface. - Use the JSON parser for parsing, don't manually parse integers or strings from arguments unless absolutely necessary. - *Rationale*: Introduces hand-rolled string manipulation code at both the caller and callee sites, - which is error prone, and it is easy to get things such as escaping wrong. + which is error-prone, and it is easy to get things such as escaping wrong. JSON already supports nested data structures, no need to re-invent the wheel. - *Exception*: AmountFromValue can parse amounts as string. This was introduced because many JSON - parsers and formatters hard-code handling decimal numbers as floating point + parsers and formatters hard-code handling decimal numbers as floating-point values, resulting in potential loss of precision. This is unacceptable for monetary values. **Always** use `AmountFromValue` and `ValueFromAmount` when inputting or outputting monetary values. The only exceptions to this are @@ -925,7 +922,7 @@ A few guidelines for introducing and reviewing new RPC interfaces: - Missing arguments and 'null' should be treated the same: as default values. If there is no default value, both cases should fail in the same way. The easiest way to follow this - guideline is detect unspecified arguments with `params[x].isNull()` instead of + guideline is to detect unspecified arguments with `params[x].isNull()` instead of `params.size() <= x`. The former returns true if the argument is either null or missing, while the latter returns true if is missing, and false if it is null. @@ -959,7 +956,7 @@ A few guidelines for introducing and reviewing new RPC interfaces: from there. - A RPC method must either be a wallet method or a non-wallet method. Do not - introduce new methods that differ in behavior based on presence of a wallet. + introduce new methods that differ in behavior based on the presence of a wallet. - *Rationale*: as well as complicating the implementation and interfering with the introduction of multi-wallet, wallet and non-wallet code should be @@ -967,7 +964,7 @@ A few guidelines for introducing and reviewing new RPC interfaces: - Try to make the RPC response a JSON object. - - *Rationale*: If a RPC response is not a JSON object then it is harder to avoid API breakage if + - *Rationale*: If a RPC response is not a JSON object, then it is harder to avoid API breakage if new data in the response is needed. - Wallet RPCs call BlockUntilSyncedToCurrentChain to maintain consistency with diff --git a/doc/release-notes-15993.md b/doc/release-notes-15993.md new file mode 100644 index 0000000000..493c7126ee --- /dev/null +++ b/doc/release-notes-15993.md @@ -0,0 +1,3 @@ +Build system changes +-------------------- +The minimum supported miniUPnPc API version is set to 10. This keeps compatibility with Ubuntu 16.04 LTS and Debian 8 `libminiupnpc-dev` packages. Please note, on Debian this package is still vulnerable to [CVE-2017-8798](https://security-tracker.debian.org/tracker/CVE-2017-8798) (in jessie only) and [CVE-2017-1000494](https://security-tracker.debian.org/tracker/CVE-2017-1000494) (both in jessie and in stretch). diff --git a/doc/release-notes-16394.md b/doc/release-notes-16394.md new file mode 100644 index 0000000000..f09cba4b6d --- /dev/null +++ b/doc/release-notes-16394.md @@ -0,0 +1,4 @@ +RPC changes +----------- +`createwallet` now returns a warning if an empty string is used as an encryption password, and does not encrypt the wallet, instead of raising an error. +This makes it easier to disable encryption but also specify other options when using the `bitcoin-cli` tool. diff --git a/doc/release-notes.md b/doc/release-notes.md index afb0469291..e0673ae7ee 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -88,6 +88,12 @@ Low-level Changes section below. `-limitancestorcount`, `-limitdescendantcount` and `-walletrejectlongchains` command line arguments. +Deprecated or removed RPCs +-------------------------- + +- The `totalFee` option of the `bumpfee` RPC has been deprecated and will be + removed in 0.20. To continue using this option start with + `-deprecatedrpc=totalFee`. See the `bumpfee` RPC help text for more details. Low-level changes ================= diff --git a/doc/release-notes/release-notes-0.18.1.md b/doc/release-notes/release-notes-0.18.1.md new file mode 100644 index 0000000000..483cc5075e --- /dev/null +++ b/doc/release-notes/release-notes-0.18.1.md @@ -0,0 +1,136 @@ +Bitcoin Core version 0.18.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.18.1/> + +This is a new minor version release, including new features, various bug +fixes and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has +completely shut down (which might take a few minutes for older +versions), then run the installer (on Windows) or just copy over +`/Applications/Bitcoin-Qt` (on Mac) or `bitcoind`/`bitcoin-qt` (on +Linux). + +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 later. Upgrading directly from 0.7.x and earlier without +redownloading the blockchain is not supported. However, as usual, old +wallet versions are still supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.10+, and Windows 7 and newer. It is not +recommended to use Bitcoin Core on unsupported systems. + +Bitcoin Core should also work on most other Unix-like systems but is not +as frequently tested on them. + +From 0.17.0 onwards, macOS <10.10 is no longer supported. 0.17.0 is +built using Qt 5.9.x, which doesn't support versions of macOS older than +10.10. Additionally, Bitcoin Core does not yet change appearance when +macOS "dark mode" is activated. + +Known issues +============ + +Wallet GUI +---------- + +For advanced users who have both (1) enabled coin control features, and +(2) are using multiple wallets loaded at the same time: The coin control +input selection dialog can erroneously retain wrong-wallet state when +switching wallets using the dropdown menu. For now, it is recommended +not to use coin control features with multiple wallets loaded. + +0.18.1 change log +================= + +### P2P protocol and network code +- #15990 Add tests and documentation for blocksonly (MarcoFalke) +- #16021 Avoid logging transaction decode errors to stderr (MarcoFalke) +- #16405 fix: tor: Call `event_base_loopbreak` from the event's callback (promag) +- #16412 Make poll in InterruptibleRecv only filter for POLLIN events (tecnovert) + +### Wallet +- #15913 Add -ignorepartialspends to list of ignored wallet options (luke-jr) + +### RPC and other APIs +- #15991 Bugfix: fix pruneblockchain returned prune height (jonasschnelli) +- #15899 Document iswitness flag and fix bug in converttopsbt (MarcoFalke) +- #16026 Ensure that uncompressed public keys in a multisig always returns a legacy address (achow101) +- #14039 Disallow extended encoding for non-witness transactions (sipa) +- #16210 add 2nd arg to signrawtransactionwithkey examples (dooglus) +- #16250 signrawtransactionwithkey: report error when missing redeemScript/witnessScript (ajtowns) + +### GUI +- #16044 fix the bug of OPEN CONFIGURATION FILE on Mac (shannon1916) +- #15957 Show "No wallets available" in open menu instead of nothing (meshcollider) +- #16118 Enable open wallet menu on setWalletController (promag) +- #16135 Set progressDialog to nullptr (promag) +- #16231 Fix open wallet menu initialization order (promag) +- #16254 Set `AA_EnableHighDpiScaling` attribute early (hebasto) +- #16122 Enable console line edit on setClientModel (promag) +- #16348 Assert QMetaObject::invokeMethod result (promag) + +### Build system +- #15985 Add test for GCC bug 90348 (sipa) +- #15947 Install bitcoin-wallet manpage (domob1812) +- #15983 build with -fstack-reuse=none (MarcoFalke) + +### Tests and QA +- #15826 Pure python EC (sipa) +- #15893 Add test for superfluous witness record in deserialization (instagibbs) +- #14818 Bugfix: test/functional/rpc_psbt: Remove check for specific error message that depends on uncertain assumptions (luke-jr) +- #15831 Add test that addmultisigaddress fails for watchonly addresses (MarcoFalke) + +### Documentation +- #15890 Remove text about txes always relayed from -whitelist (harding) + +### Miscellaneous +- #16095 Catch by reference not value in wallettool (kristapsk) +- #16205 Replace fprintf with tfm::format (MarcoFalke) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Andrew Chow +- Anthony Towns +- Chris Moore +- Daniel Kraft +- David A. Harding +- fanquake +- Gregory Sanders +- Hennadii Stepanov +- John Newbery +- Jonas Schnelli +- João Barbosa +- Kristaps Kaupe +- Luke Dashjr +- MarcoFalke +- Michele Federici +- Pieter Wuille +- Samuel Dobson +- shannon1916 +- tecnovert +- Wladimir J. van der Laan + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/src/Makefile.am b/src/Makefile.am index 0f05439227..141d8e68ea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -150,6 +150,7 @@ BITCOIN_CORE_H = \ merkleblock.h \ miner.h \ net.h \ + net_permissions.h \ net_processing.h \ netaddress.h \ netbase.h \ @@ -310,7 +311,7 @@ libbitcoin_server_a_SOURCES += dummywallet.cpp endif if ENABLE_ZMQ -libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) +libbitcoin_zmq_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ zmq/zmqabstractnotifier.cpp \ @@ -454,6 +455,7 @@ libbitcoin_common_a_SOURCES = \ merkleblock.cpp \ netaddress.cpp \ netbase.cpp \ + net_permissions.cpp \ outputtype.cpp \ policy/feerate.cpp \ policy/policy.cpp \ diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 8eea96d930..d0d7c03ee1 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -21,14 +21,14 @@ static void SetupBenchArgs() { SetupHelpOptions(gArgs); - 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); + gArgs.AddArg("-list", "List benchmarks without executing them. Can be combined with -scaling and -filter", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-evals=<n>", strprintf("Number of measurement evaluations to perform. (default: %u)", DEFAULT_BENCH_EVALUATIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-scaling=<n>", strprintf("Scaling factor for benchmark's runtime (default: %u)", DEFAULT_BENCH_SCALING), ArgsManager::ALLOW_ANY, 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), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-plotlyurl=<uri>", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-width=<x>", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-height=<x>", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } int main(int argc, char** argv) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 8ca985458d..5f6d69a4f3 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -43,22 +43,22 @@ static void SetupCliArgs() const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST); - gArgs.AddArg("-version", "Print version and exit", 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); + gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, 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)", ArgsManager::ALLOW_ANY, 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, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwait", "Wait for RPC server to start", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, 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)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", ArgsManager::ALLOW_ANY, 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.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } /** libevent event log callback */ diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 89e2ab305b..f4972c3cd4 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -40,36 +40,36 @@ static void SetupBitcoinTxArgs() { SetupHelpOptions(gArgs); - 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); + gArgs.AddArg("-create", "Create new, empty TX.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-json", "Select JSON output", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-txid", "Output only the hex-encoded transaction id of the resultant transaction.", ArgsManager::ALLOW_ANY, 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("delin=N", "Delete input N from TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("delout=N", "Delete output N from TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("locktime=N", "Set TX lock time to N", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("nversion=N", "Set TX version to N", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", ArgsManager::ALLOW_ANY, 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); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, 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); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, 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); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("replaceable(=N)", "Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)", ArgsManager::ALLOW_ANY, 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 signrawtransactionwithkey docs for format of sighash flags, JSON objects.", false, OptionsCategory::COMMANDS); + "See signrawtransactionwithkey docs for format of sighash flags, JSON objects.", ArgsManager::ALLOW_ANY, 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); + gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", ArgsManager::ALLOW_ANY, OptionsCategory::REGISTER_COMMANDS); + gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", ArgsManager::ALLOW_ANY, OptionsCategory::REGISTER_COMMANDS); } // diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index a690e2facb..203f909cc4 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -24,13 +24,13 @@ static void SetupWalletToolArgs() SetupHelpOptions(gArgs); SetupChainParamsBaseOptions(); - gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("info", "Get wallet info", false, OptionsCategory::COMMANDS); - gArgs.AddArg("create", "Create new wallet file", false, OptionsCategory::COMMANDS); + gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); } static bool WalletAppInit(int argc, char* argv[]) diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index ba021a5163..8e31f6e32b 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -143,7 +143,7 @@ static bool AppInit(int argc, char* argv[]) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif - tfm::format(std::cout, PACKAGE_NAME "daemon starting\n"); + tfm::format(std::cout, PACKAGE_NAME " daemon starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index f937e2754b..c24234aeb7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -167,9 +167,6 @@ public: /* nTxCount */ 383732546, /* dTxRate */ 3.685496590998308 }; - - /* disable fallback fee on mainnet */ - m_fallback_fee_enabled = false; } }; @@ -262,9 +259,6 @@ public: /* nTxCount */ 19438708, /* dTxRate */ 0.626 }; - - /* enable fallback fee on testnet */ - m_fallback_fee_enabled = true; } }; @@ -346,9 +340,6 @@ public: base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "bcrt"; - - /* enable fallback fee on regtest */ - m_fallback_fee_enabled = true; } /** diff --git a/src/chainparams.h b/src/chainparams.h index b3fcd77cea..8f1d27e03c 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -66,7 +66,7 @@ public: bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; } /** Policy: Filter transactions that do not match well-defined patterns */ bool RequireStandard() const { return fRequireStandard; } - /** If this is a test chain */ + /** If this chain is exclusively used for testing */ bool IsTestChain() const { return m_is_test_chain; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } /** Minimum free space (in GB) needed for data directory */ @@ -77,8 +77,6 @@ public: bool MineBlocksOnDemand() const { return consensus.fPowNoRetargeting; } /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return strNetworkID; } - /** Return true if the fallback fee is by default enabled for this network */ - bool IsFallbackFeeEnabled() const { return m_fallback_fee_enabled; } /** Return the list of hostnames to look up for DNS seeds */ const std::vector<std::string>& DNSSeeds() const { return vSeeds; } const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } @@ -106,7 +104,6 @@ protected: bool m_is_test_chain; CCheckpointData checkpointData; ChainTxData chainTxData; - bool m_fallback_fee_enabled; }; /** diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index f0559a319a..deb8e0fb57 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -18,9 +18,9 @@ const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions() { 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); - gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", true, OptionsCategory::CHAINPARAMS); + "This is intended for regression testing tools and app development.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-testnet", "Use the test chain", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); } static std::unique_ptr<CBaseChainParams> globalChainBaseParams; diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 355df043d3..f34646f7ac 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -7,7 +7,6 @@ #include <memory> #include <string> -#include <vector> /** * CBaseChainParams defines the base parameters (shared between bitcoin-cli and bitcoind) diff --git a/src/init.cpp b/src/init.cpp index b84c7dc93d..25c964205a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -27,6 +27,7 @@ #include <key.h> #include <miner.h> #include <net.h> +#include <net_permissions.h> #include <net_processing.h> #include <netbase.h> #include <policy/feerate.h> @@ -338,7 +339,7 @@ static void OnRPCStopped() void SetupServerArgs() { SetupHelpOptions(gArgs); - gArgs.AddArg("-help-debug", "Print help message with debugging options and exit", false, OptionsCategory::DEBUG_TEST); // server-only for now + gArgs.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); @@ -353,103 +354,103 @@ void SetupServerArgs() // GUI args. These will be overwritten by SetupUIArgs for the GUI "-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>", "-min", "-resetguisettings", "-rootcertificates=<file>", "-splash", "-uiplatform"}; - gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM - 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("-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif - 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 directory to hold blocks subdirectory for *.dat files (default: <datadir>)", 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()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM - gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif - 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 reject transactions from network peers. Transactions from the wallet or RPC are not affected. (default: %u)", DEFAULT_BLOCKSONLY), 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("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), true, OptionsCategory::OPTIONS); - gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", 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. (-nodebuglogfile 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("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Transactions from the wallet or RPC are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-loadblock=<file>", "Imports blocks from external blk000??.dat file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, 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()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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); - 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); + -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); 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), 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. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", false, OptionsCategory::OPTIONS); + "(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), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #ifndef WIN32 - gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-sysperms"); #endif - gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-blockfilterindex=<type>", strprintf("Maintain an index of compact filters by block (default: %s, values: %s).", DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) + " If <type> is not supplied or if <type> = 1, indexes for all known types are enabled.", - 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; -noconnect 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, set -noonion to disable (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, testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", 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("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), true, OptionsCategory::CONNECTION); - gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", false, OptionsCategory::CONNECTION); + ArgsManager::ALLOW_ANY, 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.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-bantime=<n>", strprintf("Number of seconds to keep misbehaving peers from reconnecting (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect 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.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-dnsseed", "Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect used)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-enablebip61", strprintf("Send reject messages per BIP61 (default: %u)", DEFAULT_ENABLE_BIP61), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-externalip=<ip>", "Specify your own public address", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-listen", "Accept connections from outside (default: 1 if no -proxy or -connect)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-listenonion", strprintf("Automatically create Tor hidden service (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u)", DEFAULT_MAX_PEER_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, 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), ArgsManager::ALLOW_ANY, 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), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor hidden services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, 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.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, 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.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #ifdef USE_UPNP #if USE_UPNP - gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #else - gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #endif #else hidden_args.emplace_back("-upnp"); #endif - 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("-whitebind=<addr>", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY, 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", false, OptionsCategory::CONNECTION); + " Whitelisted peers cannot be DoS banned", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); g_wallet_init_interface.AddWalletOptions(); #if ENABLE_ZMQ - 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); - gArgs.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashblock=<address>", "Enable publish hash block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); #else hidden_args.emplace_back("-zmqpubhashblock=<address>"); hidden_args.emplace_back("-zmqpubhashtx=<address>"); @@ -461,7 +462,7 @@ void SetupServerArgs() hidden_args.emplace_back("-zmqpubrawtxhwm=<n>"); #endif - gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of -checkblocks is: " "level 0 reads the blocks from disk, " "level 1 verifies block validity, " @@ -469,68 +470,68 @@ void SetupServerArgs() "level 3 checks disconnection of tip blocks, " "and level 4 tries to reconnect the blocks, " "each level includes the checks of the previous levels " - "(0-4, default: %u)", DEFAULT_CHECKLEVEL), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for the block tree, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", true, OptionsCategory::DEBUG_TEST); - 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("-addrmantest", "Allows to test address relay on localhost", true, OptionsCategory::DEBUG_TEST); + "(0-4, default: %u)", DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for the block tree, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-debug=<category>", "Output debugging information (default: -nodebug, supplying <category> is optional). " - "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("-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("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), 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("-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 -nodebuglogfile)", 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); + "If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + ListLogCategories() + ".", ArgsManager::ALLOW_ANY, 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."), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, 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 define 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("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define 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)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, 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), ArgsManager::ALLOW_ANY, 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 the transactions were already in the mempool or 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 HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", false, OptionsCategory::RPC); - gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::RPC); - 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); + CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted peers even if the transactions were already in the mempool or violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistrelay", strprintf("Accept relayed transactions received from whitelisted peers even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + + + gArgs.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, 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)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + gArgs.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); + + gArgs.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, 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", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, 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)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, 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), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); #if HAVE_DECL_DAEMON - gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-daemon"); #endif @@ -1775,21 +1776,16 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.vBinds.push_back(addrBind); } for (const std::string& strBind : gArgs.GetArgs("-whitebind")) { - CService addrBind; - if (!Lookup(strBind.c_str(), addrBind, 0, false)) { - return InitError(ResolveErrMsg("whitebind", strBind)); - } - if (addrBind.GetPort() == 0) { - return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'").translated, strBind)); - } - connOptions.vWhiteBinds.push_back(addrBind); + NetWhitebindPermissions whitebind; + std::string error; + if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error); + connOptions.vWhiteBinds.push_back(whitebind); } for (const auto& net : gArgs.GetArgs("-whitelist")) { - CSubNet subnet; - LookupSubNet(net.c_str(), subnet); - if (!subnet.IsValid()) - return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net)); + NetWhitelistPermissions subnet; + std::string error; + if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error); connOptions.vWhitelistedRange.push_back(subnet); } diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 02f39cef8e..b8b9ecded9 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -9,7 +9,9 @@ #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> +#include <net_processing.h> #include <node/coin.h> +#include <node/transaction.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -149,12 +151,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection> LockAssertion lock(::cs_main); return CheckFinalTx(tx); } - bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) override - { - LockAssertion lock(::cs_main); - return AcceptToMemoryPool(::mempool, state, tx, nullptr /* missing inputs */, nullptr /* txn replaced */, - false /* bypass limits */, absurd_fee); - } using UniqueLock::UniqueLock; }; @@ -290,10 +286,13 @@ public: auto it = ::mempool.GetIter(txid); return it && (*it)->GetCountWithDescendants() > 1; } - void relayTransaction(const uint256& txid) override + bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override { - CInv inv(MSG_TX, txid); - g_connman->ForEachNode([&inv](CNode* node) { node->PushInventory(inv); }); + const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. + // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures + // that Chain clients do not need to know about. + return TransactionError::OK == err; } void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override { @@ -333,7 +332,6 @@ public: LOCK(cs_main); return ::fHavePruned; } - bool p2pEnabled() override { return g_connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index e675defd47..da670a3370 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -43,10 +43,6 @@ class Wallet; //! asynchronously //! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! -//! * The relayTransactions() and submitToMemoryPool() methods could be replaced -//! with a higher-level broadcastTransaction method -//! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984). -//! //! * The initMessages() and loadWallet() methods which the wallet uses to send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node @@ -127,11 +123,6 @@ public: //! Check if transaction will be final given chain height current time. virtual bool checkFinalTx(const CTransaction& tx) = 0; - - //! Add transaction to memory pool if the transaction fee is below the - //! amount specified by absurd_fee. Returns false if the transaction - //! could not be added due to the fee or for another reason. - virtual bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) = 0; }; //! Return Lock interface. Chain is locked when this is called, and @@ -164,8 +155,10 @@ public: //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const uint256& txid) = 0; - //! Relay transaction. - virtual void relayTransaction(const uint256& txid) = 0; + //! Transaction is added to memory pool, if the transaction fee is below the + //! amount specified by max_tx_fee, and broadcast to all peers if relay is set to true. + //! Return false if the transaction could not be added due to the fee or for another reason. + virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) = 0; //! Calculate mempool ancestor and descendant counts for the given transaction. virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0; @@ -194,9 +187,6 @@ public: //! Check if any block has been pruned. virtual bool havePruned() = 0; - //! Check if p2p enabled. - virtual bool p2pEnabled() = 0; - //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index fd2fb6531b..bcd226edd9 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -198,6 +198,7 @@ public: return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isAddressTypeSet() override { return !::gArgs.GetArg("-addresstype", "").empty(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override diff --git a/src/interfaces/node.h b/src/interfaces/node.h index bb4b3e1fae..b93b52c5cc 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -150,6 +150,9 @@ public: //! Is initial block download. virtual bool isInitialBlockDownload() = 0; + //! Is -addresstype set. + virtual bool isAddressTypeSet() = 0; + //! Get reindex. virtual bool getReindex() = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index deb1618ceb..077dc1ab4d 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -33,31 +33,6 @@ namespace interfaces { namespace { -class PendingWalletTxImpl : public PendingWalletTx -{ -public: - explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet) {} - - const CTransaction& get() override { return *m_tx; } - - bool commit(WalletValueMap value_map, - WalletOrderForm order_form, - std::string& reject_reason) override - { - auto locked_chain = m_wallet.chain().lock(); - LOCK(m_wallet.cs_wallet); - CValidationState state; - if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), state)) { - reject_reason = state.GetRejectReason(); - return false; - } - return true; - } - - CTransactionRef m_tx; - CWallet& m_wallet; -}; - //! Construct wallet tx struct. WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) { @@ -227,7 +202,7 @@ public: LOCK(m_wallet->cs_wallet); return m_wallet->ListLockedCoins(outputs); } - std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, + CTransactionRef createTransaction(const std::vector<CRecipient>& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, @@ -236,12 +211,26 @@ public: { auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); - auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet); - if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, fee, change_pos, + CTransactionRef tx; + if (!m_wallet->CreateTransaction(*locked_chain, recipients, tx, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } - return std::move(pending); + return tx; + } + bool commitTransaction(CTransactionRef tx, + WalletValueMap value_map, + WalletOrderForm order_form, + std::string& reject_reason) override + { + auto locked_chain = m_wallet->chain().lock(); + LOCK(m_wallet->cs_wallet); + CValidationState state; + if (!m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form), state)) { + reject_reason = state.GetRejectReason(); + return false; + } + return true; } bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index db47dbafaf..89e056b18b 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -34,7 +34,6 @@ struct CRecipient; namespace interfaces { class Handler; -class PendingWalletTx; struct WalletAddress; struct WalletBalances; struct WalletTx; @@ -134,13 +133,19 @@ public: virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0; //! Create transaction. - virtual std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, + virtual CTransactionRef createTransaction(const std::vector<CRecipient>& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, CAmount& fee, std::string& fail_reason) = 0; + //! Commit transaction. + virtual bool commitTransaction(CTransactionRef tx, + WalletValueMap value_map, + WalletOrderForm order_form, + std::string& reject_reason) = 0; + //! Return whether transaction can be abandoned. virtual bool transactionCanBeAbandoned(const uint256& txid) = 0; @@ -288,21 +293,6 @@ public: virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; }; -//! Tracking object returned by CreateTransaction and passed to CommitTransaction. -class PendingWalletTx -{ -public: - virtual ~PendingWalletTx() {} - - //! Get transaction data. - virtual const CTransaction& get() = 0; - - //! Send pending transaction and commit to wallet. - virtual bool commit(WalletValueMap value_map, - WalletOrderForm order_form, - std::string& reject_reason) = 0; -}; - //! Information about one wallet address. struct WalletAddress { diff --git a/src/net.cpp b/src/net.cpp index 8e263b7953..0464a6e9ea 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -16,6 +16,7 @@ #include <crypto/common.h> #include <crypto/sha256.h> #include <netbase.h> +#include <net_permissions.h> #include <primitives/transaction.h> #include <scheduler.h> #include <ui_interface.h> @@ -37,6 +38,9 @@ #include <miniupnpc/miniwget.h> #include <miniupnpc/upnpcommands.h> #include <miniupnpc/upnperrors.h> +// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility +// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. +static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); #endif #include <unordered_map> @@ -64,7 +68,6 @@ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), - BF_WHITELIST = (1U << 2), }; // The set of sockets cannot be modified while waiting @@ -456,12 +459,10 @@ void CNode::CloseSocketDisconnect() } } -bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { - for (const CSubNet& subnet : vWhitelistedRange) { - if (subnet.Match(addr)) - return true; +void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const { + for (const auto& subnet : vWhitelistedRange) { + if (subnet.m_subnet.Match(addr)) NetPermissions::AddFlag(flags, subnet.m_flags); } - return false; } std::string CNode::GetAddrName() const { @@ -525,7 +526,8 @@ void CNode::copyStats(CNodeStats &stats) X(mapRecvBytesPerMsgCmd); X(nRecvBytes); } - X(fWhitelisted); + X(m_legacyWhitelisted); + X(m_permissionFlags); { LOCK(cs_feeFilter); X(minFeeFilter); @@ -810,7 +812,7 @@ bool CConnman::AttemptToEvictConnection() LOCK(cs_vNodes); for (const CNode* node : vNodes) { - if (node->fWhitelisted) + if (node->HasPermission(PF_NOBAN)) continue; if (!node->fInbound) continue; @@ -901,7 +903,20 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } - bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr); + NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; + hListenSocket.AddSocketPermissionFlags(permissionFlags); + AddWhitelistPermissionFlags(permissionFlags, addr); + const bool noban = NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN); + bool legacyWhitelisted = false; + if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { + NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); + if (gArgs.GetBoolArg("-whitelistforcerelay", false)) NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY); + if (gArgs.GetBoolArg("-whitelistrelay", false)) NetPermissions::AddFlag(permissionFlags, PF_RELAY); + NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL); + NetPermissions::AddFlag(permissionFlags, PF_NOBAN); + legacyWhitelisted = true; + } + { LOCK(cs_vNodes); for (const CNode* pnode : vNodes) { @@ -938,7 +953,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { // Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept // if the only banning reason was an automatic misbehavior ban. - if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) + if (!noban && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -959,9 +974,15 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); + ServiceFlags nodeServices = nLocalServices; + if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { + nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); + } + CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); - pnode->fWhitelisted = whitelisted; + pnode->m_permissionFlags = permissionFlags; + // If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility) + pnode->m_legacyWhitelisted = legacyWhitelisted; pnode->m_prefer_evict = bannedlevel > 0; m_msgproc->InitializeNode(pnode); @@ -1404,16 +1425,10 @@ static void ThreadMapPort() struct UPNPDev * devlist = nullptr; char lanaddr[64]; -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0); -#elif MINIUPNPC_API_VERSION < 14 - /* miniupnpc 1.6 */ int error = 0; +#if MINIUPNPC_API_VERSION < 14 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); #else - /* miniupnpc 1.9.20150730 */ - int error = 0; devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif @@ -1427,43 +1442,32 @@ static void ThreadMapPort() if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); - if(r != UPNPCOMMAND_SUCCESS) + if (r != UPNPCOMMAND_SUCCESS) { LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); - else - { - if(externalIPAddress[0]) - { + } else { + if (externalIPAddress[0]) { CNetAddr resolved; - if(LookupHost(externalIPAddress, resolved, false)) { + if (LookupHost(externalIPAddress, resolved, false)) { LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString().c_str()); AddLocal(resolved, LOCAL_UPNP); } - } - else + } else { LogPrintf("UPnP: GetExternalIPAddress failed.\n"); + } } } - std::string strDesc = "Bitcoin " + FormatFullVersion(); + std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0); -#else - /* miniupnpc 1.6 */ - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); -#endif + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); - if(r!=UPNPCOMMAND_SUCCESS) - LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", - port, port, lanaddr, r, strupnperror(r)); - else + if (r != UPNPCOMMAND_SUCCESS) { + LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); + } else { LogPrintf("UPnP Port Mapping successful.\n"); - } - while(g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); + } + } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); @@ -1997,7 +2001,7 @@ void CConnman::ThreadMessageHandler() -bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted) +bool CConnman::BindListenPort(const CService& addrBind, std::string& strError, NetPermissionFlags permissions) { strError = ""; int nOne = 1; @@ -2058,9 +2062,9 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b return false; } - vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); + vhListenSocket.push_back(ListenSocket(hListenSocket, permissions)); - if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) + if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0) AddLocal(addrBind, LOCAL_BIND); return true; @@ -2144,11 +2148,11 @@ NodeId CConnman::GetNewNodeId() } -bool CConnman::Bind(const CService &addr, unsigned int flags) { +bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) { if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) return false; std::string strError; - if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { + if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && clientInterface) { clientInterface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR); } @@ -2157,20 +2161,21 @@ bool CConnman::Bind(const CService &addr, unsigned int flags) { return true; } -bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds) { +bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds) +{ bool fBound = false; for (const auto& addrBind : binds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); + fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::PF_NONE); } for (const auto& addrBind : whiteBinds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); + fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); } if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; 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); + fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE); + fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE); } return fBound; } @@ -15,6 +15,7 @@ #include <hash.h> #include <limitedmap.h> #include <netaddress.h> +#include <net_permissions.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -138,8 +139,9 @@ public: uint64_t nMaxOutboundLimit = 0; int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector<std::string> vSeedNodes; - std::vector<CSubNet> vWhitelistedRange; - std::vector<CService> vBinds, vWhiteBinds; + std::vector<NetWhitelistPermissions> vWhitelistedRange; + std::vector<NetWhitebindPermissions> vWhiteBinds; + std::vector<CService> vBinds; bool m_use_addrman_outgoing = true; std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; @@ -314,15 +316,17 @@ public: private: struct ListenSocket { + public: SOCKET socket; - bool whitelisted; - - ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {} + inline void AddSocketPermissionFlags(NetPermissionFlags& flags) const { NetPermissions::AddFlag(flags, m_permissions); } + ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {} + private: + NetPermissionFlags m_permissions; }; - bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); - bool Bind(const CService &addr, unsigned int flags); - bool InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds); + bool BindListenPort(const CService& bindAddr, std::string& strError, NetPermissionFlags permissions); + bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); + bool InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds); void ThreadOpenAddedConnections(); void AddOneShot(const std::string& strDest); void ProcessOneShot(); @@ -347,7 +351,7 @@ private: bool AttemptToEvictConnection(); CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection); - bool IsWhitelistedRange(const CNetAddr &addr); + void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -380,7 +384,7 @@ private: // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). - std::vector<CSubNet> vWhitelistedRange; + std::vector<NetWhitelistPermissions> vWhitelistedRange; unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; @@ -448,7 +452,6 @@ void StartMapPort(); void InterruptMapPort(); void StopMapPort(); unsigned short GetListenPort(); -bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); struct CombinerAll { @@ -555,7 +558,8 @@ public: mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; - bool fWhitelisted; + NetPermissionFlags m_permissionFlags; + bool m_legacyWhitelisted; double dPingTime; double dPingWait; double dMinPing; @@ -657,7 +661,11 @@ public: */ std::string cleanSubVer GUARDED_BY(cs_SubVer){}; bool m_prefer_evict{false}; // This peer is preferred for eviction. - bool fWhitelisted{false}; // This peer can bypass DoS banning. + bool HasPermission(NetPermissionFlags permission) const { + return NetPermissions::HasFlag(m_permissionFlags, permission); + } + // This boolean is unusued in actual processing, only present for backward compatibility at RPC/QT level + bool m_legacyWhitelisted{false}; bool fFeeler{false}; // If true this node is being used as a short lived feeler. bool fOneShot{false}; bool m_manual_connection{false}; @@ -753,6 +761,7 @@ private: const ServiceFlags nLocalServices; const int nMyStartingHeight; int nSendVersion{0}; + NetPermissionFlags m_permissionFlags{ PF_NONE }; std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread mutable CCriticalSection cs_addrName; diff --git a/src/net_permissions.cpp b/src/net_permissions.cpp new file mode 100644 index 0000000000..736f19293a --- /dev/null +++ b/src/net_permissions.cpp @@ -0,0 +1,106 @@ +// 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 <net_permissions.h> +#include <util/system.h> +#include <util/translation.h> +#include <netbase.h> + +// The parse the following format "perm1,perm2@xxxxxx" +bool TryParsePermissionFlags(const std::string str, NetPermissionFlags& output, size_t& readen, std::string& error) +{ + NetPermissionFlags flags = PF_NONE; + const auto atSeparator = str.find('@'); + + // if '@' is not found (ie, "xxxxx"), the caller should apply implicit permissions + if (atSeparator == std::string::npos) { + NetPermissions::AddFlag(flags, PF_ISIMPLICIT); + readen = 0; + } + // else (ie, "perm1,perm2@xxxxx"), let's enumerate the permissions by splitting by ',' and calculate the flags + else { + readen = 0; + // permissions == perm1,perm2 + const auto permissions = str.substr(0, atSeparator); + while (readen < permissions.length()) { + const auto commaSeparator = permissions.find(',', readen); + const auto len = commaSeparator == std::string::npos ? permissions.length() - readen : commaSeparator - readen; + // permission == perm1 + const auto permission = permissions.substr(readen, len); + readen += len; // We read "perm1" + if (commaSeparator != std::string::npos) readen++; // We read "," + + if (permission == "bloomfilter" || permission == "bloom") NetPermissions::AddFlag(flags, PF_BLOOMFILTER); + else if (permission == "noban") NetPermissions::AddFlag(flags, PF_NOBAN); + else if (permission == "forcerelay") NetPermissions::AddFlag(flags, PF_FORCERELAY); + else if (permission == "mempool") NetPermissions::AddFlag(flags, PF_MEMPOOL); + else if (permission == "all") NetPermissions::AddFlag(flags, PF_ALL); + else if (permission == "relay") NetPermissions::AddFlag(flags, PF_RELAY); + else if (permission.length() == 0); // Allow empty entries + else { + error = strprintf(_("Invalid P2P permission: '%s'").translated, permission); + return false; + } + } + readen++; + } + + output = flags; + error = ""; + return true; +} + +std::vector<std::string> NetPermissions::ToStrings(NetPermissionFlags flags) +{ + std::vector<std::string> strings; + if (NetPermissions::HasFlag(flags, PF_BLOOMFILTER)) strings.push_back("bloomfilter"); + if (NetPermissions::HasFlag(flags, PF_NOBAN)) strings.push_back("noban"); + if (NetPermissions::HasFlag(flags, PF_FORCERELAY)) strings.push_back("forcerelay"); + if (NetPermissions::HasFlag(flags, PF_RELAY)) strings.push_back("relay"); + if (NetPermissions::HasFlag(flags, PF_MEMPOOL)) strings.push_back("mempool"); + return strings; +} + +bool NetWhitebindPermissions::TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string strBind = str.substr(offset); + CService addrBind; + if (!Lookup(strBind.c_str(), addrBind, 0, false)) { + error = strprintf(_("Cannot resolve -%s address: '%s'").translated, "whitebind", strBind); + return false; + } + if (addrBind.GetPort() == 0) { + error = strprintf(_("Need to specify a port with -whitebind: '%s'").translated, strBind); + return false; + } + + output.m_flags = flags; + output.m_service = addrBind; + error = ""; + return true; +} + +bool NetWhitelistPermissions::TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string net = str.substr(offset); + CSubNet subnet; + LookupSubNet(net.c_str(), subnet); + if (!subnet.IsValid()) { + error = strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net); + return false; + } + + output.m_flags = flags; + output.m_subnet = subnet; + error = ""; + return true; +} diff --git a/src/net_permissions.h b/src/net_permissions.h new file mode 100644 index 0000000000..b3987de65f --- /dev/null +++ b/src/net_permissions.h @@ -0,0 +1,62 @@ +// 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 <string> +#include <vector> +#include <netaddress.h> + +#ifndef BITCOIN_NET_PERMISSIONS_H +#define BITCOIN_NET_PERMISSIONS_H +enum NetPermissionFlags +{ + PF_NONE = 0, + // Can query bloomfilter even if -peerbloomfilters is false + PF_BLOOMFILTER = (1U << 1), + // Relay and accept transactions from this peer, even if -blocksonly is true + PF_RELAY = (1U << 3), + // Always relay transactions from this peer, even if already in mempool or rejected from policy + // Keep parameter interaction: forcerelay implies relay + PF_FORCERELAY = (1U << 2) | PF_RELAY, + // Can't be banned for misbehavior + PF_NOBAN = (1U << 4), + // Can query the mempool + PF_MEMPOOL = (1U << 5), + + // True if the user did not specifically set fine grained permissions + PF_ISIMPLICIT = (1U << 31), + PF_ALL = PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY | PF_NOBAN | PF_MEMPOOL, +}; +class NetPermissions +{ +public: + NetPermissionFlags m_flags; + static std::vector<std::string> ToStrings(NetPermissionFlags flags); + static inline bool HasFlag(const NetPermissionFlags& flags, NetPermissionFlags f) + { + return (flags & f) == f; + } + static inline void AddFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags | f); + } + static inline void ClearFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags & ~f); + } +}; +class NetWhitebindPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error); + CService m_service; +}; + +class NetWhitelistPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error); + CSubNet m_subnet; +}; + +#endif // BITCOIN_NET_PERMISSIONS_H
\ No newline at end of file diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 4b43b2cdf2..3db460d444 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -68,13 +68,13 @@ static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100; /** Maximum number of announced transactions from a peer */ static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ; /** How many microseconds to delay requesting transactions from inbound peers */ -static constexpr int64_t INBOUND_PEER_TX_DELAY = 2 * 1000000; // 2 seconds +static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{std::chrono::seconds{2}}; /** How long to wait (in microseconds) before downloading a transaction from an additional peer */ -static constexpr int64_t GETDATA_TX_INTERVAL = 60 * 1000000; // 1 minute +static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seconds{60}}; /** Maximum delay (in microseconds) for transaction requests to avoid biasing some peers over others. */ -static constexpr int64_t MAX_GETDATA_RANDOM_DELAY = 2 * 1000000; // 2 seconds +static constexpr std::chrono::microseconds MAX_GETDATA_RANDOM_DELAY{std::chrono::seconds{2}}; /** How long to wait (in microseconds) before expiring an in-flight getdata request to a peer */ -static constexpr int64_t TX_EXPIRY_INTERVAL = 10 * GETDATA_TX_INTERVAL; +static constexpr std::chrono::microseconds TX_EXPIRY_INTERVAL{GETDATA_TX_INTERVAL * 10}; static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY, "To preserve security, MAX_GETDATA_RANDOM_DELAY should not exceed INBOUND_PEER_DELAY"); /** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */ @@ -340,16 +340,16 @@ struct CNodeState { /* Track when to attempt download of announced transactions (process * time in micros -> txid) */ - std::multimap<int64_t, uint256> m_tx_process_time; + std::multimap<std::chrono::microseconds, uint256> m_tx_process_time; //! Store all the transactions a peer has recently announced std::set<uint256> m_tx_announced; //! Store transactions which were requested by us, with timestamp - std::map<uint256, int64_t> m_tx_in_flight; + std::map<uint256, std::chrono::microseconds> m_tx_in_flight; //! Periodically check for stuck getdata requests - int64_t m_check_expiry_timer{0}; + std::chrono::microseconds m_check_expiry_timer{0}; }; TxDownloadState m_tx_download; @@ -391,7 +391,7 @@ struct CNodeState { }; // Keeps track of the time (in microseconds) when transactions were requested last time -limitedmap<uint256, int64_t> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ); +limitedmap<uint256, std::chrono::microseconds> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ); /** Map maintaining per-node state. */ static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); @@ -408,7 +408,7 @@ static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LO nPreferredDownload -= state->fPreferredDownload; // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) && !node->fOneShot && !node->fClient; + state->fPreferredDownload = (!node->fInbound || node->HasPermission(PF_NOBAN)) && !node->fOneShot && !node->fClient; nPreferredDownload += state->fPreferredDownload; } @@ -688,16 +688,16 @@ void EraseTxRequest(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) g_already_asked_for.erase(txid); } -int64_t GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +std::chrono::microseconds GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { auto it = g_already_asked_for.find(txid); if (it != g_already_asked_for.end()) { return it->second; } - return 0; + return {}; } -void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void UpdateTxRequestTime(const uint256& txid, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { auto it = g_already_asked_for.find(txid); if (it == g_already_asked_for.end()) { @@ -707,17 +707,17 @@ void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LO } } -int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - int64_t process_time; - int64_t last_request_time = GetTxRequestTime(txid); + std::chrono::microseconds process_time; + const auto last_request_time = GetTxRequestTime(txid); // First time requesting this tx - if (last_request_time == 0) { + if (last_request_time.count() == 0) { process_time = current_time; } else { // Randomize the delay to avoid biasing some peers over others (such as due to // fixed ordering of peer processing in ThreadMessageHandler) - process_time = last_request_time + GETDATA_TX_INTERVAL + GetRand(MAX_GETDATA_RANDOM_DELAY); + process_time = last_request_time + GETDATA_TX_INTERVAL + GetRandMicros(MAX_GETDATA_RANDOM_DELAY); } // We delay processing announcements from inbound peers @@ -726,7 +726,7 @@ int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool u return process_time; } -void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void RequestTx(CNodeState* state, const uint256& txid, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CNodeState::TxDownloadState& peer_download_state = state->m_tx_download; if (peer_download_state.m_tx_announced.size() >= MAX_PEER_TX_ANNOUNCEMENTS || @@ -740,7 +740,7 @@ void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_L // Calculate the time to try requesting this transaction. Use // fPreferredDownload as a proxy for outbound peers. - int64_t process_time = CalculateTxGetDataTime(txid, nNow, !state->fPreferredDownload); + const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload); peer_download_state.m_tx_process_time.emplace(process_time, txid); } @@ -1305,10 +1305,10 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return true; } -static void RelayTransaction(const CTransaction& tx, CConnman* connman) +void RelayTransaction(const uint256& txid, const CConnman& connman) { - CInv inv(MSG_TX, tx.GetHash()); - connman->ForEachNode([&inv](CNode* pnode) + CInv inv(MSG_TX, txid); + connman.ForEachNode([&inv](CNode* pnode) { pnode->PushInventory(inv); }); @@ -1398,7 +1398,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks // never disconnect whitelisted nodes - if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) + if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom->GetId()); @@ -1407,7 +1407,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c send = false; } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (send && !pfrom->fWhitelisted && ( + if (send && !pfrom->HasPermission(PF_NOBAN) && ( (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); @@ -1811,7 +1811,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se if (setMisbehaving.count(fromPeer)) continue; if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); - RelayTransaction(orphanTx, connman); + RelayTransaction(orphanHash, *connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i)); if (it_by_prev != mapOrphanTransactionsByPrev.end()) { @@ -2217,13 +2217,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr bool fBlocksOnly = !g_relay_txes; // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) + if (pfrom->HasPermission(PF_RELAY)) fBlocksOnly = false; LOCK(cs_main); uint32_t nFetchFlags = GetFetchFlags(pfrom); - int64_t nNow = GetTimeMicros(); + const auto current_time = GetTime<std::chrono::microseconds>(); for (CInv &inv : vInv) { @@ -2255,7 +2255,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (fBlocksOnly) { LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId()); } else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) { - RequestTx(State(pfrom->GetId()), inv.hash, nNow); + RequestTx(State(pfrom->GetId()), inv.hash, current_time); } } } @@ -2412,7 +2412,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->fWhitelisted) { + if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId()); return true; } @@ -2470,7 +2470,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { // Stop processing the transaction early if // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off - if (!g_relay_txes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) + if (!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); return true; @@ -2498,7 +2498,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { mempool.check(pcoinsTip.get()); - RelayTransaction(tx, connman); + RelayTransaction(tx.GetHash(), *connman); for (unsigned int i = 0; i < tx.vout.size(); i++) { auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(inv.hash, i)); if (it_by_prev != mapOrphanTransactionsByPrev.end()) { @@ -2529,12 +2529,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (!fRejectedParents) { uint32_t nFetchFlags = GetFetchFlags(pfrom); - int64_t nNow = GetTimeMicros(); + const auto current_time = GetTime<std::chrono::microseconds>(); for (const CTxIn& txin : tx.vin) { CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash); pfrom->AddInventoryKnown(_inv); - if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, nNow); + if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, current_time); } AddOrphanTx(ptx, pfrom->GetId()); @@ -2565,7 +2565,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr AddToCompactExtraTransactions(ptx); } - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { + if (pfrom->HasPermission(PF_FORCERELAY)) { // Always relay transactions received from whitelisted peers, even // if they were already in the mempool or rejected from it due // to policy, allowing the node to function as a gateway for @@ -2577,7 +2577,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); } else { LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); - RelayTransaction(tx, connman); + RelayTransaction(tx.GetHash(), *connman); } } } @@ -3010,17 +3010,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (strCommand == NetMsgType::MEMPOOL) { - if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) + if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } - if (connman->OutboundTargetReached(false) && !pfrom->fWhitelisted) + if (connman->OutboundTargetReached(false) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } @@ -3216,7 +3222,7 @@ bool PeerLogicValidation::SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_ if (state.fShouldBan) { state.fShouldBan = false; - if (pnode->fWhitelisted) + if (pnode->HasPermission(PF_NOBAN)) LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); else if (pnode->m_manual_connection) LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); @@ -3786,7 +3792,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) pto->vInventoryBlockToSend.clear(); // Check whether periodic sends should happen - bool fSendTrickle = pto->fWhitelisted; + bool fSendTrickle = pto->HasPermission(PF_NOBAN); if (pto->nNextInvSend < nNow) { fSendTrickle = true; if (pto->fInbound) { @@ -3906,6 +3912,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto) connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling + const auto current_time = GetTime<std::chrono::microseconds>(); + // nNow is the current system time (GetTimeMicros is not mockable) and + // should be replaced by the mockable current_time eventually nNow = GetTimeMicros(); if (state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, @@ -3939,7 +3948,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Note: If all our peers are inbound, then we won't // disconnect our sync peer for stalling; we have bigger // problems if we can't get any outbound peers. - if (!pto->fWhitelisted) { + if (!pto->HasPermission(PF_NOBAN)) { LogPrintf("Timeout downloading headers from peer=%d, disconnecting\n", pto->GetId()); pto->fDisconnect = true; return true; @@ -3998,9 +4007,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // were unresponsive in the past. // Eventually we should consider disconnecting peers, but this is // conservative. - if (state.m_tx_download.m_check_expiry_timer <= nNow) { + if (state.m_tx_download.m_check_expiry_timer <= current_time) { for (auto it=state.m_tx_download.m_tx_in_flight.begin(); it != state.m_tx_download.m_tx_in_flight.end();) { - if (it->second <= nNow - TX_EXPIRY_INTERVAL) { + if (it->second <= current_time - TX_EXPIRY_INTERVAL) { LogPrint(BCLog::NET, "timeout of inflight tx %s from peer=%d\n", it->first.ToString(), pto->GetId()); state.m_tx_download.m_tx_announced.erase(it->first); state.m_tx_download.m_tx_in_flight.erase(it++); @@ -4010,11 +4019,11 @@ bool PeerLogicValidation::SendMessages(CNode* pto) } // On average, we do this check every TX_EXPIRY_INTERVAL. Randomize // so that we're not doing this for all peers at the same time. - state.m_tx_download.m_check_expiry_timer = nNow + TX_EXPIRY_INTERVAL/2 + GetRand(TX_EXPIRY_INTERVAL); + state.m_tx_download.m_check_expiry_timer = current_time + TX_EXPIRY_INTERVAL / 2 + GetRandMicros(TX_EXPIRY_INTERVAL); } auto& tx_process_time = state.m_tx_download.m_tx_process_time; - while (!tx_process_time.empty() && tx_process_time.begin()->first <= nNow && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) { + while (!tx_process_time.empty() && tx_process_time.begin()->first <= current_time && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) { const uint256 txid = tx_process_time.begin()->second; // Erase this entry from tx_process_time (it may be added back for // processing at a later time, see below) @@ -4023,22 +4032,22 @@ bool PeerLogicValidation::SendMessages(CNode* pto) if (!AlreadyHave(inv)) { // If this transaction was last requested more than 1 minute ago, // then request. - int64_t last_request_time = GetTxRequestTime(inv.hash); - if (last_request_time <= nNow - GETDATA_TX_INTERVAL) { + const auto last_request_time = GetTxRequestTime(inv.hash); + if (last_request_time <= current_time - GETDATA_TX_INTERVAL) { LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId()); vGetData.push_back(inv); if (vGetData.size() >= MAX_GETDATA_SZ) { connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); vGetData.clear(); } - UpdateTxRequestTime(inv.hash, nNow); - state.m_tx_download.m_tx_in_flight.emplace(inv.hash, nNow); + UpdateTxRequestTime(inv.hash, current_time); + state.m_tx_download.m_tx_in_flight.emplace(inv.hash, current_time); } else { // This transaction is in flight from someone else; queue // up processing to happen after the download times out // (with a slight delay for inbound peers, to prefer // requests to outbound peers). - int64_t next_process_time = CalculateTxGetDataTime(txid, nNow, !state.fPreferredDownload); + const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload); tx_process_time.emplace(next_process_time, txid); } } else { @@ -4057,7 +4066,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && - !(pto->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) { + !pto->HasPermission(PF_FORCERELAY)) { CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); int64_t timeNow = GetTimeMicros(); if (timeNow > pto->nextSendTimeFeeFilter) { diff --git a/src/net_processing.h b/src/net_processing.h index dffc3f273f..1d26164b18 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -90,4 +90,7 @@ struct CNodeStateStats { /** Get statistics from node state */ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats); +/** Relay transaction to every node */ +void RelayTransaction(const uint256&, const CConnman& connman); + #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/netbase.cpp b/src/netbase.cpp index 6d4738c835..0148aea428 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -37,8 +37,8 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP; static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); -enum Network ParseNetwork(std::string net) { - Downcase(net); +enum Network ParseNetwork(const std::string& net_in) { + std::string net = ToLower(net_in); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; if (net == "onion") return NET_ONION; diff --git a/src/netbase.h b/src/netbase.h index 708df5b8e2..313a575687 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -37,7 +37,7 @@ public: bool randomize_credentials; }; -enum Network ParseNetwork(std::string net); +enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 5ffb15ed3c..a28136a8e8 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -5,6 +5,7 @@ #include <consensus/validation.h> #include <net.h> +#include <net_processing.h> #include <txmempool.h> #include <util/validation.h> #include <validation.h> @@ -13,26 +14,33 @@ #include <future> -TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, std::string& err_string, const CAmount& highfee) +TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { + // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. + // g_connman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. g_connman should never be null here. + assert(g_connman); std::promise<void> promise; - hashTx = tx->GetHash(); + uint256 hashTx = tx->GetHash(); + bool callback_set = false; { // cs_main scope LOCK(cs_main); + // If the transaction is already confirmed in the chain, don't do anything + // and return early. CCoinsViewCache &view = *pcoinsTip; - bool fHaveChain = false; - for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) { + for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); - fHaveChain = !existingCoin.IsSpent(); + // IsSpent doesnt mean the coin is spent, it means the output doesnt' exist. + // So if the output does exist, then this transaction exists in the chain. + if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; } - bool fHaveMempool = mempool.exists(hashTx); - if (!fHaveMempool && !fHaveChain) { - // push to local node and sync with wallets + if (!mempool.exists(hashTx)) { + // Transaction is not already in the mempool. Submit it. CValidationState state; bool fMissingInputs; if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, - nullptr /* plTxnReplaced */, false /* bypass_limits */, highfee)) { + nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) { if (state.IsInvalid()) { err_string = FormatStateMessage(state); return TransactionError::MEMPOOL_REJECTED; @@ -43,36 +51,37 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, err_string = FormatStateMessage(state); return TransactionError::MEMPOOL_ERROR; } - } else { - // If wallet is enabled, ensure that the wallet has been made aware - // of the new transaction prior to returning. This prevents a race - // where a user might call sendrawtransaction with a transaction - // to/from their wallet, immediately call some wallet RPC, and get - // a stale result because callbacks have not yet been processed. + } + + // Transaction was accepted to the mempool. + + if (wait_callback) { + // For transactions broadcast from outside the wallet, make sure + // that the wallet has been notified of the transaction before + // continuing. + // + // This prevents a race where a user might call sendrawtransaction + // with a transaction to/from their wallet, immediately call some + // wallet RPC, and get a stale result because callbacks have not + // yet been processed. CallFunctionInValidationInterfaceQueue([&promise] { promise.set_value(); }); + callback_set = true; } - } else if (fHaveChain) { - return TransactionError::ALREADY_IN_CHAIN; - } else { - // Make sure we don't block forever if re-sending - // a transaction already in mempool. - promise.set_value(); } } // cs_main - promise.get_future().wait(); - - if (!g_connman) { - return TransactionError::P2P_DISABLED; + if (callback_set) { + // Wait until Validation Interface clients have been notified of the + // transaction entering the mempool. + promise.get_future().wait(); } - CInv inv(MSG_TX, hashTx); - g_connman->ForEachNode([&inv](CNode* pnode) { - pnode->PushInventory(inv); - }); + if (relay) { + RelayTransaction(hashTx, *g_connman); + } return TransactionError::OK; } diff --git a/src/node/transaction.h b/src/node/transaction.h index 51033f94e5..cf64fc28d9 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -11,14 +11,21 @@ #include <util/error.h> /** - * Broadcast a transaction + * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. + * + * Mempool submission can be synchronous (will await mempool entry notification + * over the CValidationInterface) or asynchronous (will submit and not wait for + * notification), depending on the value of wait_callback. wait_callback MUST + * NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid + * deadlock. * * @param[in] tx the transaction to broadcast - * @param[out] &txid the txid of the transaction, if successfully broadcast * @param[out] &err_string reference to std::string to fill with error string if available - * @param[in] highfee Reject txs with fees higher than this (if 0, accept any fee) + * @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee) + * @param[in] relay flag if both mempool insertion and p2p relay are requested + * @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, uint256& txid, std::string& err_string, const CAmount& highfee); +NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); #endif // BITCOIN_NODE_TRANSACTION_H diff --git a/src/noui.cpp b/src/noui.cpp index caab9f326e..c07939cc79 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -13,6 +13,12 @@ #include <string> #include <boost/signals2/connection.hpp> +#include <boost/signals2/signal.hpp> + +/** Store connections so we can disconnect them when suppressing output */ +boost::signals2::connection noui_ThreadSafeMessageBoxConn; +boost::signals2::connection noui_ThreadSafeQuestionConn; +boost::signals2::connection noui_InitMessageConn; bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style) { @@ -57,7 +63,39 @@ void noui_InitMessage(const std::string& message) void noui_connect() { - uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox); - uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion); - uiInterface.InitMessage_connect(noui_InitMessage); + noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox); + noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion); + noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessage); +} + +bool noui_ThreadSafeMessageBoxSuppressed(const std::string& message, const std::string& caption, unsigned int style) +{ + return false; +} + +bool noui_ThreadSafeQuestionSuppressed(const std::string& /* ignored interactive message */, const std::string& message, const std::string& caption, unsigned int style) +{ + return false; } + +void noui_InitMessageSuppressed(const std::string& message) +{ +} + +void noui_suppress() +{ + noui_ThreadSafeMessageBoxConn.disconnect(); + noui_ThreadSafeQuestionConn.disconnect(); + noui_InitMessageConn.disconnect(); + noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBoxSuppressed); + noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestionSuppressed); + noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessageSuppressed); +} + +void noui_reconnect() +{ + noui_ThreadSafeMessageBoxConn.disconnect(); + noui_ThreadSafeQuestionConn.disconnect(); + noui_InitMessageConn.disconnect(); + noui_connect(); +}
\ No newline at end of file diff --git a/src/noui.h b/src/noui.h index 79a79a9af2..854aeeacca 100644 --- a/src/noui.h +++ b/src/noui.h @@ -17,4 +17,10 @@ void noui_InitMessage(const std::string& message); /** Connect all bitcoind signal handlers */ void noui_connect(); +/** Suppress all bitcoind signal handlers. Used to suppress output during test runs that produce expected errors */ +void noui_suppress(); + +/** Reconnects the regular Non-GUI handlers after having used noui_suppress */ +void noui_reconnect(); + #endif // BITCOIN_NOUI_H diff --git a/src/obj-test/.gitignore b/src/obj-test/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/src/obj-test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index ed5d47cad7..5ce4f3c191 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -25,7 +25,8 @@ #ifdef ENABLE_WALLET #include <qt/paymentserver.h> #include <qt/walletcontroller.h> -#endif +#include <qt/walletmodel.h> +#endif // ENABLE_WALLET #include <interfaces/handler.h> #include <interfaces/node.h> @@ -168,8 +169,11 @@ void BitcoinCore::shutdown() } } -BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char **argv): - QApplication(argc, argv), +static int qt_argc = 1; +static const char* qt_argv = "bitcoin-qt"; + +BitcoinApplication::BitcoinApplication(interfaces::Node& node): + QApplication(qt_argc, const_cast<char **>(&qt_argv)), coreThread(nullptr), m_node(node), optionsModel(nullptr), @@ -207,12 +211,6 @@ BitcoinApplication::~BitcoinApplication() delete window; window = nullptr; -#ifdef ENABLE_WALLET - delete paymentServer; - paymentServer = nullptr; - delete m_wallet_controller; - m_wallet_controller = nullptr; -#endif delete optionsModel; optionsModel = nullptr; delete platformStyle; @@ -328,24 +326,21 @@ void BitcoinApplication::initializeResult(bool success) { // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qInfo() << "Platform customization:" << platformStyle->getName(); -#ifdef ENABLE_WALLET - m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); -#ifdef ENABLE_BIP70 - PaymentServer::LoadRootCAs(); -#endif - if (paymentServer) { - paymentServer->setOptionsModel(optionsModel); -#ifdef ENABLE_BIP70 - connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); -#endif - } -#endif - clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET - window->setWalletController(m_wallet_controller); + if (WalletModel::isWalletEnabled()) { + m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); + window->setWalletController(m_wallet_controller); + if (paymentServer) { + paymentServer->setOptionsModel(optionsModel); +#ifdef ENABLE_BIP70 + PaymentServer::LoadRootCAs(); + connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); #endif + } + } +#endif // ENABLE_WALLET // If -min option passed, start window minimized (iconified) or minimized to tray if (!gArgs.GetBoolArg("-min", false)) { @@ -399,15 +394,15 @@ WId BitcoinApplication::getMainWinId() const static void SetupUIArgs() { #if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) - gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), true, OptionsCategory::GUI); + gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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); + gArgs.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-min", "Start minimized", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-resetguisettings", "Reset all settings changed in the GUI", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-rootcertificates=<file>", "Set SSL root certificates for payment request (default: -system-)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::GUI); } int GuiMain(int argc, char* argv[]) @@ -441,7 +436,7 @@ int GuiMain(int argc, char* argv[]) QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); @@ -549,8 +544,10 @@ int GuiMain(int argc, char* argv[]) // Start up the payment server early, too, so impatient users that click on // bitcoin: links repeatedly have their payment requests routed to this process: - app.createPaymentServer(); -#endif + if (WalletModel::isWalletEnabled()) { + app.createPaymentServer(); + } +#endif // ENABLE_WALLET /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 40537c1813..3869193a3a 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -56,7 +56,7 @@ class BitcoinApplication: public QApplication { Q_OBJECT public: - explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); + explicit BitcoinApplication(interfaces::Node& node); ~BitcoinApplication(); #ifdef ENABLE_WALLET diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index c58717e21e..05157c2a4a 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -7,6 +7,7 @@ #include <qt/receivecoinsdialog.h> #include <qt/forms/ui_receivecoinsdialog.h> +#include <interfaces/node.h> #include <qt/addresstablemodel.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> @@ -92,10 +93,16 @@ 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); - if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { - ui->useLegacyAddress->setCheckState(Qt::Unchecked); + if (model->node().isAddressTypeSet()) { + // user explicitly set the type, use it + if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { + ui->useLegacyAddress->setCheckState(Qt::Unchecked); + } else { + ui->useLegacyAddress->setCheckState(Qt::Checked); + } } else { - ui->useLegacyAddress->setCheckState(Qt::Checked); + // Always fall back to bech32 in the gui + ui->useLegacyAddress->setCheckState(Qt::Unchecked); } // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 84b4a2d0d8..19b11ba1cd 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1120,7 +1120,7 @@ void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound")); ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight))); - ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No")); + ui->peerWhitelisted->setText(stats->nodeStats.m_legacyWhitelisted ? tr("Yes") : tr("No")); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. @@ -1265,11 +1265,6 @@ void RPCConsole::showOrHideBanTableIfRequired() ui->banHeading->setVisible(visible); } -RPCConsole::TabTypes RPCConsole::tabFocus() const -{ - return (TabTypes) ui->tabWidget->currentIndex(); -} - void RPCConsole::setTabFocus(enum TabTypes tabType) { ui->tabWidget->setCurrentIndex(tabType); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 79b0f3b19c..38015e38fd 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -67,7 +67,6 @@ public: std::vector<TabTypes> tabs() const { return {TAB_INFO, TAB_CONSOLE, TAB_GRAPH, TAB_PEERS}; } - TabTypes tabFocus() const; QString tabTitle(TabTypes tab_type) const; protected: diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index cb9efe9319..193fba78b1 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -392,7 +392,7 @@ void SendCoinsDialog::on_sendButton_clicked() accept(); CoinControlDialog::coinControl()->UnSelectAll(); coinControlUpdateLabels(); - Q_EMIT coinsSent(currentTransaction.getWtx()->get().GetHash()); + Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); } fNewRecipientAllowed = true; } diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 6cafe05461..eca468a6ab 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -16,6 +16,7 @@ #include <test/setup_common.h> #include <util/strencodings.h> +#include <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/x509_vfy.h> @@ -66,6 +67,7 @@ static SendCoinsRecipient handleRequest(PaymentServer* server, std::vector<unsig void PaymentServerTests::paymentServerTests() { + SSL_library_init(); BasicTestingSetup testing_setup(CBaseChainParams::MAIN); auto node = interfaces::MakeNode(); OptionsModel optionsModel(*node); diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 6bda8dc6eb..796cf24b36 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -26,8 +26,6 @@ #include <QObject> #include <QTest> -#include <openssl/ssl.h> - #if defined(QT_STATICPLUGIN) #include <QtPlugin> #if defined(QT_QPA_PLATFORM_MINIMAL) @@ -70,11 +68,9 @@ int main(int argc, char *argv[]) // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); app.setApplicationName("Bitcoin-Qt-test"); - SSL_library_init(); - AppTests app_tests(app); if (QTest::qExec(&app_tests) != 0) { fInvalid = true; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 2aedb77798..a8e7bce6b5 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -99,6 +99,9 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal // Instantiate model and register it. WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + // Handler callback runs in a different thread so fix wallet model thread affinity. + wallet_model->moveToThread(thread()); + wallet_model->setParent(this); m_wallets.push_back(wallet_model); connect(wallet_model, &WalletModel::unload, [this, wallet_model] { @@ -119,25 +122,11 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); // Notify walletAdded signal on the GUI thread. - if (QThread::currentThread() == thread()) { - addWallet(wallet_model); - } else { - // Handler callback runs in a different thread so fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - bool invoked = QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); - assert(invoked); - } + Q_EMIT walletAdded(wallet_model); return wallet_model; } -void WalletController::addWallet(WalletModel* wallet_model) -{ - // Take ownership of the wallet model and register it. - wallet_model->setParent(this); - Q_EMIT walletAdded(wallet_model); -} - void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) { // Unregister wallet model. diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 03039dd795..be1c282919 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -50,9 +50,6 @@ public: OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); -private Q_SLOTS: - void addWallet(WalletModel* wallet_model); - Q_SIGNALS: void walletAdded(WalletModel* wallet_model); void walletRemoved(WalletModel* wallet_model); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 57406179f7..49a13330ec 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -261,11 +261,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran auto& newTx = transaction.getWtx(); std::string rejectReason; - if (!newTx->commit({} /* mapValue */, std::move(vOrderForm), rejectReason)) + if (!wallet().commitTransaction(newTx, {} /* mapValue */, std::move(vOrderForm), rejectReason)) return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(rejectReason)); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << newTx->get(); + ssTx << *newTx; transaction_array.append(&(ssTx[0]), ssTx.size()); } diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 8c0dc276b0..d00ccf70d9 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -21,14 +21,14 @@ QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const return recipients; } -std::unique_ptr<interfaces::PendingWalletTx>& WalletModelTransaction::getWtx() +CTransactionRef& WalletModelTransaction::getWtx() { return wtx; } unsigned int WalletModelTransaction::getTransactionSize() { - return wtx ? GetVirtualTransactionSize(wtx->get()) : 0; + return wtx ? GetVirtualTransactionSize(*wtx) : 0; } CAmount WalletModelTransaction::getTransactionFee() const @@ -43,7 +43,7 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee) void WalletModelTransaction::reassignAmounts(int nChangePosRet) { - const CTransaction* walletTransaction = &wtx->get(); + const CTransaction* walletTransaction = wtx.get(); int i = 0; for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it) { diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 289aee847b..a41d8f2457 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -16,7 +16,6 @@ class SendCoinsRecipient; namespace interfaces { class Node; -class PendingWalletTx; } /** Data model for a walletmodel transaction. */ @@ -27,7 +26,7 @@ public: QList<SendCoinsRecipient> getRecipients() const; - std::unique_ptr<interfaces::PendingWalletTx>& getWtx(); + CTransactionRef& getWtx(); unsigned int getTransactionSize(); void setTransactionFee(const CAmount& newFee); @@ -39,7 +38,7 @@ public: private: QList<SendCoinsRecipient> recipients; - std::unique_ptr<interfaces::PendingWalletTx> wtx; + CTransactionRef wtx; CAmount fee; }; diff --git a/src/random.cpp b/src/random.cpp index de26e6de1a..675b177af3 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -667,6 +667,11 @@ uint64_t GetRand(uint64_t nMax) noexcept return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); } +std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept +{ + return std::chrono::microseconds{GetRand(duration_max.count())}; +} + int GetRandInt(int nMax) noexcept { return GetRand(nMax); diff --git a/src/random.h b/src/random.h index 75d037738d..22801ec155 100644 --- a/src/random.h +++ b/src/random.h @@ -10,7 +10,8 @@ #include <crypto/common.h> #include <uint256.h> -#include <stdint.h> +#include <chrono> // For std::chrono::microseconds +#include <cstdint> #include <limits> /** @@ -69,6 +70,7 @@ */ void GetRandBytes(unsigned char* buf, int num) noexcept; uint64_t GetRand(uint64_t nMax) noexcept; +std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept; int GetRandInt(int nMax) noexcept; uint256 GetRandHash() noexcept; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 48bc88823a..92a0e33769 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -352,7 +352,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + HelpExampleCli("getblocktemplate", "'{\"rules\": [\"segwit\"]}'") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") }, }.Check(request); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 16b59e3d58..25dda924a4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -9,6 +9,7 @@ #include <core_io.h> #include <net.h> #include <net_processing.h> +#include <net_permissions.h> #include <netbase.h> #include <policy/policy.h> #include <policy/settings.h> @@ -177,7 +178,12 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) } obj.pushKV("inflight", heights); } - obj.pushKV("whitelisted", stats.fWhitelisted); + obj.pushKV("whitelisted", stats.m_legacyWhitelisted); + UniValue permissions(UniValue::VARR); + for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) { + permissions.push_back(permission); + } + obj.pushKV("permissions", permissions); obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter)); UniValue sendPerMsgCmd(UniValue::VOBJ); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 532765b3d8..966c159f0f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -406,7 +406,11 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + bool rbf = false; + if (!request.params[3].isNull()) { + rbf = request.params[3].isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); return EncodeHexTx(CTransaction(rawTx)); } @@ -760,7 +764,10 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) static UniValue sendrawtransaction(const JSONRPCRequest& request) { RPCHelpMan{"sendrawtransaction", - "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nNote that the transaction will be sent unconditionally to all peers, so using this\n" + "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" + "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n" "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, @@ -807,14 +814,14 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) max_raw_tx_fee = fr.GetFee((weight+3)/4); } - uint256 txid; std::string err_string; - const TransactionError err = BroadcastTransaction(tx, txid, err_string, max_raw_tx_fee); + AssertLockNotHeld(cs_main); + const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); if (TransactionError::OK != err) { throw JSONRPCTransactionError(err, err_string); } - return txid.GetHex(); + return tx->GetHash().GetHex(); } static UniValue testmempoolaccept(const JSONRPCRequest& request) @@ -1362,7 +1369,11 @@ UniValue createpsbt(const JSONRPCRequest& request) }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + bool rbf = false; + if (!request.params[3].isNull()) { + rbf = request.params[3].isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); // Make a blank psbt PartiallySignedTransaction psbtx; diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 1c96d01232..55425cca35 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -19,7 +19,7 @@ #include <util/rbf.h> #include <util/strencodings.h> -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf) { if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); @@ -37,8 +37,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.nLockTime = nLockTime; } - bool rbfOptIn = rbf.isTrue(); - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -53,7 +51,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); uint32_t nSequence; - if (rbfOptIn) { + if (rbf) { nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { nSequence = CTxIn::SEQUENCE_FINAL - 1; @@ -125,7 +123,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } } - if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) { + if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index d198887b93..c85593e71e 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -27,6 +27,6 @@ class COutPoint; UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf); #endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 50119ba184..327af62a4f 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -335,10 +335,12 @@ public: /** Base class for all Descriptor implementations. */ class DescriptorImpl : public Descriptor { - //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size of Multisig). + //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig). const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; //! The sub-descriptor argument (nullptr for everything but SH and WSH). - const std::unique_ptr<DescriptorImpl> m_script_arg; + //! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT) + //! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions. + const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg; //! The string name of the descriptor function. const std::string m_name; @@ -349,10 +351,10 @@ protected: /** A helper function to construct the scripts for this descriptor. * * This function is invoked once for every CScript produced by evaluating - * m_script_arg, or just once in case m_script_arg is nullptr. + * m_subdescriptor_arg, or just once in case m_subdescriptor_arg is nullptr. * @param pubkeys The evaluations of the m_pubkey_args field. - * @param script The evaluation of m_script_arg (or nullptr when m_script_arg is nullptr). + * @param script The evaluation of m_subdescriptor_arg (or nullptr when m_subdescriptor_arg is nullptr). * @param out A FlatSigningProvider to put scripts or public keys in that are necessary to the solver. * The script arguments to this function are automatically added, as is the origin info of the provided pubkeys. * @return A vector with scriptPubKeys for this descriptor. @@ -360,12 +362,12 @@ protected: virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0; public: - DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_script_arg(std::move(script)), m_name(name) {} + DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_subdescriptor_arg(std::move(script)), m_name(name) {} bool IsSolvable() const override { - if (m_script_arg) { - if (!m_script_arg->IsSolvable()) return false; + if (m_subdescriptor_arg) { + if (!m_subdescriptor_arg->IsSolvable()) return false; } return true; } @@ -375,8 +377,8 @@ public: for (const auto& pubkey : m_pubkey_args) { if (pubkey->IsRange()) return true; } - if (m_script_arg) { - if (m_script_arg->IsRange()) return true; + if (m_subdescriptor_arg) { + if (m_subdescriptor_arg->IsRange()) return true; } return false; } @@ -396,10 +398,10 @@ public: } ret += std::move(tmp); } - if (m_script_arg) { + if (m_subdescriptor_arg) { if (pos++) ret += ","; std::string tmp; - if (!m_script_arg->ToStringHelper(arg, tmp, priv)) return false; + if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false; ret += std::move(tmp); } out = std::move(ret) + ")"; @@ -428,6 +430,8 @@ public: // Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure. for (const auto& p : m_pubkey_args) { entries.emplace_back(); + // If we have a cache, we don't need GetPubKey to compute the public key. + // Pass in nullptr to signify only origin info is desired. if (!p->GetPubKey(pos, arg, cache_read ? nullptr : &entries.back().first, entries.back().second)) return false; if (cache_read) { // Cached expanded public key exists, use it. @@ -444,9 +448,9 @@ public: } } std::vector<CScript> subscripts; - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - if (!m_script_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; + if (!m_subdescriptor_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; out = Merge(out, subprovider); } @@ -456,7 +460,7 @@ public: pubkeys.push_back(entry.first); out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second))); } - if (m_script_arg) { + if (m_subdescriptor_arg) { for (const auto& subscript : subscripts) { out.scripts.emplace(CScriptID(subscript), subscript); std::vector<CScript> addscripts = MakeScripts(pubkeys, &subscript, out); @@ -488,9 +492,9 @@ public: if (!p->GetPrivKey(pos, provider, key)) continue; out.keys.emplace(key.GetPubKey().GetID(), key); } - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - m_script_arg->ExpandPrivate(pos, provider, subprovider); + m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider); out = Merge(out, subprovider); } } diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 29915c6c92..a34e9f0d8a 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -47,9 +47,9 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * provider: the provider to query for private keys in case of hardened derivation. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). - * cache: vector which will be overwritten with cache data necessary to-evaluate the descriptor at this point without access to private keys. + * cache: vector which will be overwritten with cache data necessary to evaluate the descriptor at this point without access to private keys. */ virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0; @@ -57,7 +57,7 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * cache: vector from which cached expansion data will be read. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). */ virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index dac201a35f..5ce8e6feb0 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); size_t poolSize = pool.size(); - pool.removeRecursive(*block.vtx[2]); + pool.removeRecursive(*block.vtx[2], MemPoolRemovalReason::REPLACED); BOOST_CHECK_EQUAL(pool.size(), poolSize - 1); CBlock block2; diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 8a42344642..77304fe918 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -7,6 +7,7 @@ #include <test/setup_common.h> #include <string> +#include <utility> #include <vector> #include <boost/algorithm/string.hpp> @@ -32,17 +33,18 @@ static void ResetArgs(const std::string& strArg) BOOST_CHECK(gArgs.ParseParameters(vecChar.size(), vecChar.data(), error)); } -static void SetupArgs(const std::vector<std::string>& args) +static void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) { gArgs.ClearArgs(); - for (const std::string& arg : args) { - gArgs.AddArg(arg, "", false, OptionsCategory::OPTIONS); + for (const auto& arg : args) { + gArgs.AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } BOOST_AUTO_TEST_CASE(boolarg) { - SetupArgs({"-foo"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + SetupArgs({foo}); ResetArgs("-foo"); BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); @@ -95,7 +97,9 @@ BOOST_AUTO_TEST_CASE(boolarg) BOOST_AUTO_TEST_CASE(stringarg) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_STRING); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_STRING); + SetupArgs({foo, bar}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); @@ -120,7 +124,9 @@ BOOST_AUTO_TEST_CASE(stringarg) BOOST_AUTO_TEST_CASE(intarg) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_INT); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_INT); + SetupArgs({foo, bar}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0); @@ -140,7 +146,9 @@ BOOST_AUTO_TEST_CASE(intarg) BOOST_AUTO_TEST_CASE(doubledash) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); + SetupArgs({foo, bar}); ResetArgs("--foo"); BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true); @@ -151,7 +159,9 @@ BOOST_AUTO_TEST_CASE(doubledash) BOOST_AUTO_TEST_CASE(boolargno) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); + SetupArgs({foo, bar}); ResetArgs("-nofoo"); BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index c6a3de2285..fe5d31b7d3 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -14,6 +14,8 @@ BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup) +static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED; + BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { // Test CTxMemPool::remove functionality @@ -59,13 +61,13 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) // Nothing in pool, remove should do nothing: unsigned int poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Just the parent: testPool.addUnchecked(entry.FromTx(txParent)); poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1); // Parent, children, grandchildren: @@ -77,18 +79,18 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) } // Remove Child[0], GrandChild[0] should be removed: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txChild[0])); + testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2); // ... make sure grandchild and child are gone: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txGrandChild[0])); + testPool.removeRecursive(CTransaction(txGrandChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txChild[0])); + testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Remove parent, all children/grandchildren should go: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5); BOOST_CHECK_EQUAL(testPool.size(), 0U); @@ -101,7 +103,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be // put into the mempool (maybe because it is non-standard): poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6); BOOST_CHECK_EQUAL(testPool.size(), 0U); } @@ -283,11 +285,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) BOOST_CHECK_EQUAL(pool.size(), 10U); // Now try removing tx10 and verify the sort order returns to normal - pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx()); + pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); CheckSort<descendant_score>(pool, snapshotOrder); - pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx()); - pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx()); + pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); + pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); } BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 4bd40687a6..05d7f76983 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -158,7 +158,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - mempool.removeRecursive(CTransaction(tx)); + mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); mempool.addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 86c0cecbf1..a3d0831624 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <netbase.h> +#include <net_permissions.h> #include <test/setup_common.h> #include <util/strencodings.h> @@ -321,4 +322,82 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork) BOOST_CHECK_EQUAL(ParseNetwork(""), NET_UNROUTABLE); } +BOOST_AUTO_TEST_CASE(netpermissions_test) +{ + std::string error; + NetWhitebindPermissions whitebindPermissions; + NetWhitelistPermissions whitelistPermissions; + + // Detect invalid white bind + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + BOOST_CHECK(error.find("Cannot resolve -whitebind address") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("127.0.0.1", whitebindPermissions, error)); + BOOST_CHECK(error.find("Need to specify a port with -whitebind") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + + // If no permission flags, assume backward compatibility + BOOST_CHECK(NetWhitebindPermissions::TryParse("1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + NetPermissions::ClearFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(!NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + NetPermissions::AddFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + + // Can set one permission + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER); + BOOST_CHECK(NetWhitebindPermissions::TryParse("@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Happy path, can parse flags + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay@1.2.3.4:32", whitebindPermissions, error)); + // forcerelay should also activate the relay permission + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(NetWhitebindPermissions::TryParse("all@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ALL); + + // Allow dups + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + + // Allow empty + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",,@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Detect invalid flag + BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.find("Invalid P2P permission") != std::string::npos); + + // Check whitelist error + BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error)); + BOOST_CHECK(error.find("Invalid netmask specified in -whitelist") != std::string::npos); + + // Happy path for whitelist parsing + BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_NOBAN); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_NOBAN | PF_RELAY); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitelistPermissions.m_subnet.ToString(), "1.2.3.4/32"); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, error)); + + const auto strings = NetPermissions::ToStrings(PF_ALL); + BOOST_CHECK_EQUAL(strings.size(), 5); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "bloomfilter") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "forcerelay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "relay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "noban") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "mempool") != strings.end()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp index b4c0e6a0f4..7b00222ab7 100644 --- a/src/test/timedata_tests.cpp +++ b/src/test/timedata_tests.cpp @@ -2,8 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#include <timedata.h> + +#include <netaddress.h> +#include <noui.h> #include <test/setup_common.h> +#include <timedata.h> +#include <warnings.h> + +#include <string> #include <boost/test/unit_test.hpp> @@ -34,4 +40,61 @@ BOOST_AUTO_TEST_CASE(util_MedianFilter) BOOST_CHECK_EQUAL(filter.median(), 7); } +static void MultiAddTimeData(int n, int64_t offset) +{ + static int cnt = 0; + for (int i = 0; i < n; ++i) { + CNetAddr addr; + addr.SetInternal(std::to_string(++cnt)); + AddTimeData(addr, offset); + } +} + + +BOOST_AUTO_TEST_CASE(addtimedata) +{ + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + //Part 1: Add large offsets to test a warning message that our clock may be wrong. + MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1); + // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0) + + noui_suppress(); + MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 + noui_reconnect(); + + BOOST_CHECK(GetWarnings("gui").find("clock is wrong") != std::string::npos); + + // nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // Part 2: Test positive and negative medians by adding more offsets + MultiAddTimeData(4, 100); // filter size 9 + BOOST_CHECK_EQUAL(GetTimeOffset(), 100); + MultiAddTimeData(10, -100); //filter size 19 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + // Part 3: Test behaviour when filter has reached maximum number of offsets + const int MAX_SAMPLES = 200; + int nfill = (MAX_SAMPLES - 3 - 19) / 2; //89 + MultiAddTimeData(nfill, 100); + MultiAddTimeData(nfill, -100); //filter size MAX_SAMPLES - 3 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + MultiAddTimeData(2, 100); + //filter size MAX_SAMPLES -1, median is the initial 0 offset + //since we added same number of positive/negative offsets + + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // After the number of offsets has reached MAX_SAMPLES -1 (=199), nTimeOffset will never change + // because it is only updated when the number of elements in the filter becomes odd. It was decided + // not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521 + // for a more detailed explanation. + MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail. +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 9960573b33..7119f56fc3 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -14,6 +14,7 @@ #include <stdint.h> #include <thread> +#include <utility> #include <vector> #ifndef WIN32 #include <signal.h> @@ -154,10 +155,10 @@ struct TestArgsManager : public ArgsManager LOCK(cs_args); m_network_only_args.insert(arg); } - void SetupArgs(int argv, const char* args[]) + void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) { - for (int i = 0; i < argv; ++i) { - AddArg(args[i], "", false, OptionsCategory::OPTIONS); + for (const auto& arg : args) { + AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } using ArgsManager::ReadConfigStream; @@ -168,11 +169,15 @@ struct TestArgsManager : public ArgsManager BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; - const char* avail_args[] = {"-a", "-b", "-ccc", "-d"}; + const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); + const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; std::string error; - testArgs.SetupArgs(4, avail_args); + testArgs.SetupArgs({a, b, ccc, d}); BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); @@ -200,11 +205,17 @@ 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 auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); + const auto c = std::make_pair("-c", ArgsManager::ALLOW_BOOL); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_BOOL); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_BOOL); + const auto f = std::make_pair("-f", ArgsManager::ALLOW_BOOL); + const char *argv_test[] = { "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; std::string error; - testArgs.SetupArgs(6, avail_args); + testArgs.SetupArgs({a, b, c, d, e, f}); BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); // Each letter should be set. @@ -237,9 +248,10 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) TestArgsManager testArgs; // Params test - const char* avail_args[] = {"-foo", "-bar"}; + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; - testArgs.SetupArgs(2, avail_args); + testArgs.SetupArgs({foo, bar}); std::string error; BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error)); @@ -308,8 +320,17 @@ 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); + const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_STRING); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_STRING); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); + const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_BOOL); + const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_BOOL); + const auto h = std::make_pair("-h", ArgsManager::ALLOW_BOOL); + const auto i = std::make_pair("-i", ArgsManager::ALLOW_BOOL); + const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_INT); + test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii}); test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map @@ -507,8 +528,9 @@ 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 auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_BOOL); + const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_BOOL); + test_args.SetupArgs({testnet, regtest}); const char* argv_testnet[] = {"cmd", "-testnet"}; const char* argv_regtest[] = {"cmd", "-regtest"}; @@ -682,7 +704,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) const std::string& name = net_specific ? "wallet" : "server"; const std::string key = "-" + name; - parser.AddArg(key, name, false, OptionsCategory::OPTIONS); + parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); if (net_specific) parser.SetNetworkOnlyArg(key); auto args = GetValues(arg_actions, section, name, "a"); @@ -809,8 +831,8 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) { TestArgsManager parser; LOCK(parser.cs_args); - parser.AddArg("-regtest", "regtest", false, OptionsCategory::OPTIONS); - parser.AddArg("-testnet", "testnet", false, OptionsCategory::OPTIONS); + parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" : action == DISABLE_TEST ? "-testnet=0" : @@ -1510,17 +1532,9 @@ BOOST_AUTO_TEST_CASE(test_ToLower) BOOST_CHECK_EQUAL(ToLower(0), 0); BOOST_CHECK_EQUAL(ToLower('\xff'), '\xff'); - std::string testVector; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, ""); - - testVector = "#HODL"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "#hodl"); - - testVector = "\x00\xfe\xff"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); + BOOST_CHECK_EQUAL(ToLower(""), ""); + BOOST_CHECK_EQUAL(ToLower("#HODL"), "#hodl"); + BOOST_CHECK_EQUAL(ToLower("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_ToUpper) @@ -1531,6 +1545,10 @@ BOOST_AUTO_TEST_CASE(test_ToUpper) BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); BOOST_CHECK_EQUAL(ToUpper('\xff'), '\xff'); + + BOOST_CHECK_EQUAL(ToUpper(""), ""); + BOOST_CHECK_EQUAL(ToUpper("#hodl"), "#HODL"); + BOOST_CHECK_EQUAL(ToUpper("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_Capitalize) diff --git a/src/txmempool.h b/src/txmempool.h index 565dd61f0f..7169e80da2 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -345,7 +345,6 @@ struct TxMempoolInfo * this is passed to the notification signal. */ enum class MemPoolRemovalReason { - UNKNOWN = 0, //!< Manually removed or unknown reason EXPIRY, //!< Expired from mempool SIZELIMIT, //!< Removed in size limiting REORG, //!< Removed for reorganization @@ -574,7 +573,7 @@ public: void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); - void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs); void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -613,7 +612,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) EXCLUSIVE_LOCKS_REQUIRED(cs); + void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) 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 @@ -735,7 +734,7 @@ private: * transactions in a chain before we've updated all the state for the * removal. */ - void removeUnchecked(txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeUnchecked(txiter entry, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); }; /** diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 0acbb4f117..1e7d24c71c 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -546,9 +546,18 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -void Downcase(std::string& str) +std::string ToLower(const std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);}); + std::string r; + for (auto ch : str) r += ToLower((unsigned char)ch); + return r; +} + +std::string ToUpper(const std::string& str) +{ + std::string r; + for (auto ch : str) r += ToUpper((unsigned char)ch); + return r; } std::string Capitalize(std::string str) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 7c4364a082..e35b2ab857 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -199,6 +199,8 @@ bool ConvertBits(const O& outfn, I it, I end) { * Converts the given character to its lowercase equivalent. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to lowercase. * @return the lowercase equivalent of c; or the argument * if no conversion is possible. @@ -209,17 +211,22 @@ constexpr char ToLower(char c) } /** - * Converts the given string to its lowercase equivalent. + * Returns the lowercase equivalent of the given string. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. - * @param[in,out] str the string to convert to lowercase. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to lowercase. + * @returns lowercased equivalent of str */ -void Downcase(std::string& str); +std::string ToLower(const std::string& str); /** * Converts the given character to its uppercase equivalent. * This function is locale independent. It only converts lowercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to uppercase. * @return the uppercase equivalent of c; or the argument * if no conversion is possible. @@ -230,12 +237,24 @@ constexpr char ToUpper(char c) } /** + * Returns the uppercase equivalent of the given string. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to uppercase. + * @returns UPPERCASED EQUIVALENT OF str + */ +std::string ToUpper(const std::string& str); + +/** * Capitalizes the first character of the given string. - * This function is locale independent. It only capitalizes the - * first character of the argument if it has an uppercase equivalent - * in the standard 7-bit ASCII range. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] str the string to capitalize. - * @return string with the first letter capitalized. + * @returns string with the first letter capitalized. */ std::string Capitalize(std::string str); diff --git a/src/util/system.cpp b/src/util/system.cpp index c27b0cc105..f8bcc45a6a 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -268,22 +268,24 @@ public: * This method also tracks when the -no form was supplied, and if so, * checks whether there was a double-negative (-nofoo=0 -> -foo=1). * - * If there was not a double negative, it removes the "no" from the key, - * and returns true, indicating the caller should clear the args vector - * to indicate a negated option. + * If there was not a double negative, it removes the "no" from the key + * and clears the args vector to indicate a negated option. * * If there was a double negative, it removes "no" from the key, sets the - * value to "1" and returns false. + * value to "1" and pushes the key and the updated value to the args vector. * - * If there was no "no", it leaves key and value untouched and returns - * false. + * If there was no "no", it leaves key and value untouched and pushes them + * to the args vector. * * Where an option was negated can be later checked using the * IsArgNegated() method. One use case for this is to have a way to disable * options that are not normally boolean (e.g. using -nodebuglogfile to request * that debug log output is not sent to any file at all). */ -static bool InterpretNegatedOption(std::string& key, std::string& val) + +NODISCARD static bool InterpretOption(std::string key, std::string val, unsigned int flags, + std::map<std::string, std::vector<std::string>>& args, + std::string& error) { assert(key[0] == '-'); @@ -294,31 +296,25 @@ static bool InterpretNegatedOption(std::string& key, std::string& val) ++option_index; } if (key.substr(option_index, 2) == "no") { - bool bool_val = InterpretBool(val); key.erase(option_index, 2); - if (!bool_val ) { + if (flags & ArgsManager::ALLOW_BOOL) { + if (InterpretBool(val)) { + args[key].clear(); + return true; + } // Double negatives like -nofoo=0 are supported (but discouraged) LogPrintf("Warning: parsed potentially confusing double-negative %s=%s\n", key, val); val = "1"; } else { - return true; + error = strprintf("Negating of %s is meaningless and therefore forbidden", key.c_str()); + return false; } } - return false; + args[key].push_back(val); + return true; } -ArgsManager::ArgsManager() : - /* These options would cause cross-contamination if values for - * mainnet were used while running on regtest/testnet (or vice-versa). - * Setting them as section_only_args ensures that sharing a config file - * between mainnet and regtest/testnet won't cause problems due to these - * parameters by accident. */ - m_network_only_args{ - "-addnode", "-connect", - "-port", "-bind", - "-rpcport", "-rpcbind", - "-wallet", - } +ArgsManager::ArgsManager() { // nothing to do } @@ -384,6 +380,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin for (int i = 1; i < argc; i++) { std::string key(argv[i]); + if (key == "-") break; //bitcoin-tx using stdin std::string val; size_t is_index = key.find('='); if (is_index != std::string::npos) { @@ -391,7 +388,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(is_index); } #ifdef WIN32 - std::transform(key.begin(), key.end(), key.begin(), ToLower); + key = ToLower(key); if (key[0] == '/') key[0] = '-'; #endif @@ -403,19 +400,14 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin if (key.length() > 1 && key[1] == '-') key.erase(0, 1); - // Check for -nofoo - if (InterpretNegatedOption(key, val)) { - m_override_args[key].clear(); - } 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 = strprintf("Invalid parameter %s", key.c_str()); + const unsigned int flags = FlagsOfKnownArg(key); + if (flags) { + if (!InterpretOption(key, val, flags, m_override_args, error)) { return false; } + } else { + error = strprintf("Invalid parameter %s", key.c_str()); + return false; } } @@ -432,21 +424,30 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return true; } -bool ArgsManager::IsArgKnown(const std::string& key) const +unsigned int ArgsManager::FlagsOfKnownArg(const std::string& key) const { + assert(key[0] == '-'); + size_t option_index = key.find('.'); - std::string arg_no_net; if (option_index == std::string::npos) { - arg_no_net = key; + option_index = 1; } else { - arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); + ++option_index; } + if (key.substr(option_index, 2) == "no") { + option_index += 2; + } + + const std::string base_arg_name = '-' + key.substr(option_index); LOCK(cs_args); for (const auto& arg_map : m_available_args) { - if (arg_map.second.count(arg_no_net)) return true; + const auto search = arg_map.second.find(base_arg_name); + if (search != arg_map.second.end()) { + return search->second.m_flags; + } } - return false; + return ArgsManager::NONE; } std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const @@ -538,24 +539,29 @@ 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) +void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, 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::string arg_name = name.substr(0, eq_index); LOCK(cs_args); 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)); + auto ret = arg_map.emplace(arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags}); assert(ret.second); // Make sure an insertion actually happened + + if (flags & ArgsManager::NETWORK_ONLY) { + m_network_only_args.emplace(arg_name); + } } void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names) { for (const std::string& name : names) { - AddArg(name, "", false, OptionsCategory::HIDDEN); + AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); } } @@ -614,7 +620,7 @@ std::string ArgsManager::GetHelpMessage() const if (arg_map.first == OptionsCategory::HIDDEN) break; for (const auto& arg : arg_map.second) { - if (show_debug || !arg.second.m_debug_only) { + if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; @@ -635,7 +641,7 @@ bool HelpRequested(const ArgsManager& args) void SetupHelpOptions(ArgsManager& args) { - args.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); + args.AddArg("-?", "Print this help message and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); args.AddHiddenArgs({"-h", "-help"}); } @@ -839,22 +845,18 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file return false; } for (const std::pair<std::string, std::string>& option : options) { - std::string strKey = std::string("-") + option.first; - std::string strValue = option.second; - - if (InterpretNegatedOption(strKey, strValue)) { - m_config_args[strKey].clear(); + const std::string strKey = std::string("-") + option.first; + const unsigned int flags = FlagsOfKnownArg(strKey); + if (flags) { + if (!InterpretOption(strKey, option.second, flags, m_config_args, error)) { + return false; + } } else { - m_config_args[strKey].push_back(strValue); - } - - // Check that the arg is known - if (!IsArgKnown(strKey)) { - if (!ignore_invalid_keys) { + if (ignore_invalid_keys) { + LogPrintf("Ignoring unknown configuration value %s\n", option.first); + } else { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; - } else { - LogPrintf("Ignoring unknown configuration value %s\n", option.first); } } } diff --git a/src/util/system.h b/src/util/system.h index 66a9eb4612..75e8096826 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -127,6 +127,23 @@ struct SectionInfo class ArgsManager { +public: + enum Flags { + NONE = 0x00, + // Boolean options can accept negation syntax -noOPTION or -noOPTION=1 + ALLOW_BOOL = 0x01, + ALLOW_INT = 0x02, + ALLOW_STRING = 0x04, + ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING, + DEBUG_ONLY = 0x100, + /* Some options would cause cross-contamination if values for + * mainnet were used while running on regtest/testnet (or vice-versa). + * Setting them as NETWORK_ONLY ensures that sharing a config file + * between mainnet and regtest/testnet won't cause problems due to these + * parameters by accident. */ + NETWORK_ONLY = 0x200, + }; + protected: friend class ArgsManagerHelper; @@ -134,9 +151,7 @@ protected: { 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) {}; + unsigned int m_flags; }; mutable CCriticalSection cs_args; @@ -256,7 +271,7 @@ public: /** * Add argument */ - void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat); + void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat); /** * Add many hidden arguments @@ -269,6 +284,7 @@ public: void ClearArgs() { LOCK(cs_args); m_available_args.clear(); + m_network_only_args.clear(); } /** @@ -277,9 +293,10 @@ public: std::string GetHelpMessage() const; /** - * Check whether we know of this arg + * Return Flags for known arg. + * Return ArgsManager::NONE for unknown arg. */ - bool IsArgKnown(const std::string& key) const; + unsigned int FlagsOfKnownArg(const std::string& key) const; }; extern ArgsManager gArgs; diff --git a/src/validation.cpp b/src/validation.cpp index 19f4f098d7..b4677df62f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -618,6 +618,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { setAncestors.clear(); + // If CalculateMemPoolAncestors fails second time, we want the original error string. + std::string dummy_err_string; // If the new transaction is relatively small (up to 40k weight) // and has at most one ancestor (ie ancestor limit of 2, including // the new transaction), allow it if its parent has exactly the @@ -629,7 +631,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // outputs - one for each counterparty. For more info on the uses for // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || - !pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, errString)) { + !pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) { return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); } } diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 60bce66839..14513bc9e9 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -20,5 +20,7 @@ void CCoinControl::SetNull() m_confirm_target.reset(); m_signal_bip125_rbf.reset(); m_fee_mode = FeeEstimateMode::UNSET; + m_min_depth = DEFAULT_MIN_DEPTH; + m_max_depth = DEFAULT_MAX_DEPTH; } diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 249c402e4d..92a290530c 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -12,6 +12,9 @@ #include <boost/optional.hpp> +const int DEFAULT_MIN_DEPTH = 0; +const int DEFAULT_MAX_DEPTH = 9999999; + /** Coin Control Features. */ class CCoinControl { @@ -39,7 +42,9 @@ public: //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; //! Minimum chain depth value for coin availability - int m_min_depth{0}; + int m_min_depth = DEFAULT_MIN_DEPTH; + //! Maximum chain depth value for coin availability + int m_max_depth = DEFAULT_MAX_DEPTH; CCoinControl() { diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 852b194386..26aeb754ad 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -585,7 +585,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo if (fCreate && !Exists(std::string("version"))) { bool fTmp = fReadOnly; fReadOnly = false; - WriteVersion(CLIENT_VERSION); + Write(std::string("version"), CLIENT_VERSION); fReadOnly = fTmp; } } diff --git a/src/wallet/db.h b/src/wallet/db.h index b3856fbaf9..94f41eaf16 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -399,17 +399,6 @@ public: return (ret == 0); } - bool ReadVersion(int& nVersion) - { - nVersion = 0; - return Read(std::string("version"), nVersion); - } - - bool WriteVersion(int nVersion) - { - return Write(std::string("version"), nVersion); - } - bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr); }; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index cb94799d9e..e766deadb7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -34,41 +34,41 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions() const { - gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), false, OptionsCategory::WALLET); - gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); - gArgs.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", false, OptionsCategory::WALLET); - gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET); + gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, 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)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, 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); + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), ArgsManager::ALLOW_ANY, 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); + CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)", - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), false, OptionsCategory::DEBUG_TEST); + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); 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); + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), ArgsManager::ALLOW_ANY, 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); + CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, 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), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, 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>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); + gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #if HAVE_SYSTEM - gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", false, OptionsCategory::WALLET); + gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif - gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), false, OptionsCategory::WALLET); + gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, 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. payment request information, 2 = drop tx meta data)", false, OptionsCategory::WALLET); + " (1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)", ArgsManager::ALLOW_ANY, 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); + gArgs.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, 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), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); } bool WalletInit::ParameterInteraction() const diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 04e9bb40c5..a905cc0c55 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -185,19 +185,15 @@ UniValue importprivkey(const JSONRPCRequest& request) } } - // Don't throw error in case a key is already there - if (pwallet->HaveKey(vchAddress)) { - return NullUniValue; + // Use timestamp of 1 to scan the whole chain + if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - // whenever a key is imported, we need to scan the whole chain - pwallet->UpdateTimeFirstKey(1); - pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; - - if (!pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + // Add the wpkh script for this key if possible + if (pubkey.IsCompressed()) { + pwallet->ImportScripts({GetScriptForDestination(WitnessV0KeyHash(vchAddress))}, 0 /* timestamp */); } - pwallet->LearnAllRelatedScripts(pubkey); } } if (fRescan) { @@ -235,42 +231,6 @@ UniValue abortrescan(const JSONRPCRequest& request) return true; } -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"); - } - - pwallet->MarkDirty(); - - if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (isRedeemScript) { - const CScriptID id(script); - if (!pwallet->HaveCScript(id) && !pwallet->AddCScript(script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); - } - ImportAddress(pwallet, ScriptHash(id), strLabel); - } else { - CTxDestination destination; - if (ExtractDestination(script, destination)) { - pwallet->SetAddressBook(destination, strLabel, "receive"); - } - } -} - -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); - // add to address book or update label - if (IsValidDestination(dest)) - pwallet->SetAddressBook(dest, strLabel, "receive"); -} - UniValue importaddress(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -341,10 +301,22 @@ UniValue importaddress(const JSONRPCRequest& request) if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); } - ImportAddress(pwallet, dest, strLabel); + + pwallet->MarkDirty(); + + pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else if (IsHex(request.params[0].get_str())) { std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); - ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH); + CScript redeem_script(data.begin(), data.end()); + + std::set<CScript> scripts = {redeem_script}; + pwallet->ImportScripts(scripts, 0 /* timestamp */); + + if (fP2SH) { + scripts.insert(GetScriptForDestination(ScriptHash(CScriptID(redeem_script)))); + } + + pwallet->ImportScriptPubKeys(strLabel, scripts, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } @@ -529,11 +501,16 @@ UniValue importpubkey(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); + std::set<CScript> script_pub_keys; for (const auto& dest : GetAllDestinationsForKey(pubKey)) { - ImportAddress(pwallet, dest, strLabel); + script_pub_keys.insert(GetScriptForDestination(dest)); } - ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); - pwallet->LearnAllRelatedScripts(pubKey); + + pwallet->MarkDirty(); + + pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, true /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); + + pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , {} /* key_origins */, false /* add_keypool */, false /* internal */, 1 /* timestamp */); } if (fRescan) { @@ -664,18 +641,18 @@ UniValue importwallet(const JSONRPCRequest& request) CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); - if (pwallet->HaveKey(keyid)) { - pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(PKHash(keyid))); - continue; - } + pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid))); - if (!pwallet->AddKeyPubKey(key, pubkey)) { + + if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { + pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(PKHash(keyid))); fGood = false; continue; } - pwallet->mapKeyMetadata[keyid].nCreateTime = time; + if (has_label) pwallet->SetAddressBook(PKHash(keyid), label, "receive"); + nTimeBegin = std::min(nTimeBegin, time); progress++; } @@ -683,24 +660,19 @@ UniValue importwallet(const JSONRPCRequest& request) pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); const CScript& script = script_pair.first; int64_t time = script_pair.second; - CScriptID id(script); - if (pwallet->HaveCScript(id)) { - pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", HexStr(script)); - continue; - } - if(!pwallet->AddCScript(script)) { + + if (!pwallet->ImportScripts({script}, time)) { pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); fGood = false; continue; } if (time > 0) { - pwallet->m_script_metadata[id].nCreateTime = time; nTimeBegin = std::min(nTimeBegin, time); } + progress++; } pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI - pwallet->UpdateTimeFirstKey(nTimeBegin); } pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); @@ -1255,7 +1227,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con // All good, time to import pwallet->MarkDirty(); - if (!pwallet->ImportScripts(import_data.import_scripts)) { + if (!pwallet->ImportScripts(import_data.import_scripts, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { @@ -1264,7 +1236,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, internal, timestamp)) { + if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ab732dc0d8..cbab73d612 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -309,10 +309,6 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - // Parse Bitcoin address CScript scriptPubKey = GetScriptForDestination(address); @@ -359,8 +355,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) " transaction, just kept in your wallet."}, {"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n" " The recipient will receive less bitcoins than you enter in the amount field."}, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -815,8 +811,8 @@ static UniValue sendmany(const JSONRPCRequest& request) {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -845,10 +841,6 @@ static UniValue sendmany(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } @@ -2676,11 +2668,12 @@ static UniValue createwallet(const JSONRPCRequest& request) } SecureString passphrase; passphrase.reserve(100); + std::string warning; if (!request.params[3].isNull()) { passphrase = request.params[3].get_str().c_str(); if (passphrase.empty()) { - // Empty string is invalid - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password"); + // Empty string means unencrypted + warning = "Empty string given as passphrase, wallet will not be encrypted."; } } @@ -2689,15 +2682,23 @@ static UniValue createwallet(const JSONRPCRequest& request) } std::string error; - std::string warning; - WalletCreationStatus status; - std::shared_ptr<CWallet> wallet = CreateWallet(*g_rpc_interfaces->chain, request.params[0].get_str(), error, warning, status, passphrase, flags); - if (status == WalletCreationStatus::CREATION_FAILED) { - throw JSONRPCError(RPC_WALLET_ERROR, error); - } else if (status == WalletCreationStatus::ENCRYPTION_FAILED) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); - } else if (status != WalletCreationStatus::SUCCESS) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed"); + std::string create_warning; + std::shared_ptr<CWallet> wallet; + WalletCreationStatus status = CreateWallet(*g_rpc_interfaces->chain, passphrase, flags, request.params[0].get_str(), error, create_warning, wallet); + switch (status) { + case WalletCreationStatus::CREATION_FAILED: + throw JSONRPCError(RPC_WALLET_ERROR, error); + case WalletCreationStatus::ENCRYPTION_FAILED: + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); + case WalletCreationStatus::SUCCESS: + break; + // no default case, so the compiler can warn about missing cases + } + + if (warning.empty()) { + warning = create_warning; + } else if (!warning.empty() && !create_warning.empty()){ + warning += "; " + create_warning; } UniValue obj(UniValue::VOBJ); @@ -2877,9 +2878,11 @@ static UniValue listunspent(const JSONRPCRequest& request) { CCoinControl cctl; cctl.m_avoid_address_reuse = false; + cctl.m_min_depth = nMinDepth; + cctl.m_max_depth = nMaxDepth; auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); @@ -3110,9 +3113,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -3263,20 +3266,20 @@ static UniValue bumpfee(const JSONRPCRequest& request) "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" "The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n" - "If `totalFee` is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" + "If `totalFee` (DEPRECATED) is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" "All inputs in the original transaction will be included in the replacement transaction.\n" "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" "By default, the new fee will be calculated automatically using estimatesmartfee.\n" "The user can specify a confirmation target for estimatesmartfee.\n" - "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" + "Alternatively, the user can specify totalFee (DEPRECATED), or use RPC settxfee to set a higher fee rate.\n" "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" "returned by getnetworkinfo) to enter the node's mempool.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"confTarget", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n" + {"confTarget", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, + {"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis. (DEPRECATED)\n" " In rare cases, the actual fee paid might be slightly higher than the specified\n" " totalFee if the tx change output has to be removed because it is too close to\n" " the dust threshold."}, @@ -3331,6 +3334,9 @@ static UniValue bumpfee(const JSONRPCRequest& request) } else if (options.exists("confTarget")) { // TODO: alias this to conf_target coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"], pwallet->chain().estimateMaxBlocks()); } else if (options.exists("totalFee")) { + if (!pwallet->chain().rpcEnableDeprecated("totalFee")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "totalFee argument has been deprecated and will be removed in 0.20. Please use -deprecatedrpc=totalFee to continue using this argument until removal."); + } totalFee = options["totalFee"].get_int64(); if (totalFee <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee))); @@ -4052,7 +4058,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, {"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" @@ -4087,7 +4093,13 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) CAmount fee; int change_position; - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"]); + bool rbf = pwallet->m_signal_rbf; + const UniValue &replaceable_arg = request.params[3]["replaceable"]; + if (!replaceable_arg.isNull()) { + RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL); + rbf = replaceable_arg.isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 1816fca257..279542ffad 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -4,6 +4,7 @@ #include <boost/test/unit_test.hpp> +#include <noui.h> #include <test/setup_common.h> #include <util/system.h> #include <wallet/test/init_test_fixture.h> @@ -33,21 +34,27 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) { SetWalletDir(m_walletdir_path_cases["nonexistent"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) { SetWalletDir(m_walletdir_path_cases["file"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) { SetWalletDir(m_walletdir_path_cases["relative"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4d2faa8d6c..03acf23508 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -161,7 +161,7 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& return LoadWallet(chain, WalletLocation(name), error, warning); } -std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning, WalletCreationStatus& status, const SecureString& passphrase, uint64_t wallet_creation_flags) +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result) { // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); @@ -175,39 +175,40 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin WalletLocation location(name); if (location.Exists()) { error = "Wallet " + location.GetName() + " already exists."; - status = WalletCreationStatus::CREATION_FAILED; - return nullptr; + return WalletCreationStatus::CREATION_FAILED; } // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. std::string wallet_error; if (!CWallet::Verify(chain, location, false, wallet_error, warning)) { error = "Wallet file verification failed: " + wallet_error; - status = WalletCreationStatus::CREATION_FAILED; - return nullptr; + return WalletCreationStatus::CREATION_FAILED; + } + + // Do not allow a passphrase when private keys are disabled + if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + error = "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."; + return WalletCreationStatus::CREATION_FAILED; } // Make the wallet std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, wallet_creation_flags); if (!wallet) { error = "Wallet creation failed"; - status = WalletCreationStatus::CREATION_FAILED; - return nullptr; + return WalletCreationStatus::CREATION_FAILED; } // Encrypt the wallet if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!wallet->EncryptWallet(passphrase)) { error = "Error: Wallet created but failed to encrypt."; - status = WalletCreationStatus::ENCRYPTION_FAILED; - return nullptr; + return WalletCreationStatus::ENCRYPTION_FAILED; } if (!create_blank) { // Unlock the wallet if (!wallet->Unlock(passphrase)) { error = "Error: Wallet was encrypted but could not be unlocked"; - status = WalletCreationStatus::ENCRYPTION_FAILED; - return nullptr; + return WalletCreationStatus::ENCRYPTION_FAILED; } // Set a seed for the wallet @@ -221,13 +222,13 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin } AddWallet(wallet); wallet->postInitProcess(); - status = WalletCreationStatus::SUCCESS; - return wallet; + result = wallet; + return WalletCreationStatus::SUCCESS; } const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; -const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); +const uint256 CWalletTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet * @@ -1777,14 +1778,27 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> return true; } -bool CWallet::ImportScripts(const std::set<CScript> scripts) +bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) { WalletBatch batch(*database); for (const auto& entry : scripts) { - if (!HaveCScript(CScriptID(entry)) && !AddCScriptWithDB(batch, entry)) { + CScriptID id(entry); + if (HaveCScript(id)) { + WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); + continue; + } + if (!AddCScriptWithDB(batch, entry)) { return false; } + + if (timestamp > 0) { + m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; + } } + if (timestamp > 0) { + UpdateTimeFirstKey(timestamp); + } + return true; } @@ -1796,9 +1810,14 @@ bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const in CPubKey pubkey = key.GetPubKey(); const CKeyID& id = entry.first; assert(key.VerifyPubKey(pubkey)); + // Skip if we already have the key + if (HaveKey(id)) { + WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); + continue; + } mapKeyMetadata[id].nCreateTime = timestamp; // If the private key is not present in the wallet, insert it. - if (!HaveKey(id) && !AddKeyPubKeyWithDB(batch, key, pubkey)) { + if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { return false; } UpdateTimeFirstKey(timestamp); @@ -1819,7 +1838,12 @@ bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const st } const CPubKey& pubkey = entry->second; CPubKey temp; - if (!GetPubKey(id, temp) && !AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { + if (GetPubKey(id, temp)) { + // Already have pubkey, skipping + WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); + continue; + } + if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { return false; } mapKeyMetadata[id].nCreateTime = timestamp; @@ -1833,7 +1857,7 @@ bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const st return true; } -bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp) +bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) { WalletBatch batch(*database); for (const CScript& script : script_pub_keys) { @@ -1844,7 +1868,7 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri } CTxDestination dest; ExtractDestination(script, dest); - if (!internal && IsValidDestination(dest)) { + if (apply_label && IsValidDestination(dest)) { SetAddressBookWithDB(batch, dest, label, "receive"); } } @@ -2110,8 +2134,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order - for (std::pair<const uint256, CWalletTx>& item : mapWallet) - { + for (std::pair<const uint256, CWalletTx>& item : mapWallet) { const uint256& wtxid = item.first; CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); @@ -2126,32 +2149,37 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) // Try to add wallet transactions to memory pool for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); - CValidationState state; - wtx.AcceptToMemoryPool(locked_chain, state); + std::string unused_err_string; + wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain); } } -bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain) +bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) return false; - // Don't relay coinbase transactions outside blocks - if (IsCoinBase()) return false; // Don't relay abandoned transactions if (isAbandoned()) return false; - // Don't relay conflicted or already confirmed transactions + // Don't try to submit coinbase transactions. These would fail anyway but would + // cause log spam. + if (IsCoinBase()) return false; + // Don't try to submit conflicted or confirmed transactions. if (GetDepthInMainChain(locked_chain) != 0) return false; - // Don't relay transactions that aren't accepted to the mempool - CValidationState unused_state; - if (!InMempool() && !AcceptToMemoryPool(locked_chain, unused_state)) return false; - // Don't try to relay if the node is not connected to the p2p network - if (!pwallet->chain().p2pEnabled()) return false; - - // Try to relay the transaction - pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); - pwallet->chain().relayTransaction(GetHash()); - return true; + // Submit transaction to mempool for relay + pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); + // We must set fInMempool here - while it will be re-set to true by the + // entered-mempool callback, if we did not there would be a race where a + // user could call sendmoney in a loop and hit spurious out of funds errors + // because we think that this newly generated transaction's change is + // unavailable as we're not yet aware that it is in the mempool. + // + // Irrespective of the failure reason, un-marking fInMempool + // out-of-order is incorrect - it should be unmarked when + // TransactionRemovedFromMempool fires. + bool ret = pwallet->chain().broadcastTransaction(tx, err_string, pwallet->m_default_max_tx_fee, relay); + fInMempool |= ret; + return ret; } std::set<uint256> CWalletTx::GetConflicts() const @@ -2342,7 +2370,7 @@ void CWallet::ResendWalletTransactions() if (m_best_block_time < nLastResend) return; nLastResend = GetTime(); - int relayed_tx_count = 0; + int submitted_tx_count = 0; { // locked_chain and cs_wallet scope auto locked_chain = chain().lock(); @@ -2351,15 +2379,17 @@ void CWallet::ResendWalletTransactions() // Relay transactions for (std::pair<const uint256, CWalletTx>& item : mapWallet) { CWalletTx& wtx = item.second; - // only rebroadcast unconfirmed txes older than 5 minutes before the - // last block was found + // Attempt to rebroadcast all txes more than 5 minutes older than + // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast + // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; - if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count; + std::string unused_err_string; + if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count; } } // locked_chain and cs_wallet - if (relayed_tx_count > 0) { - WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count); + if (submitted_tx_count > 0) { + WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count); } } @@ -2424,7 +2454,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const +void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const { AssertLockHeld(cs_wallet); @@ -2433,6 +2463,8 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); + const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; + const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; for (const auto& entry : mapWallet) { @@ -2492,8 +2524,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - if (nDepth < nMinDepth || nDepth > nMaxDepth) + if (nDepth < min_depth || nDepth > max_depth) { continue; + } for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) @@ -2935,7 +2968,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0, coin_control.m_min_depth); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -3295,12 +3328,10 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { - // Broadcast - if (!wtx.AcceptToMemoryPool(*locked_chain, state)) { - WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); + std::string err_string; + if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) { + WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. - } else { - wtx.RelayWalletTransaction(*locked_chain); } } } @@ -4386,7 +4417,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->m_min_fee = CFeeRate(n); } - walletInstance->m_allow_fallback_fee = Params().IsFallbackFeeEnabled(); + walletInstance->m_allow_fallback_fee = Params().IsTestChain(); if (gArgs.IsArgSet("-fallbackfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { @@ -4603,13 +4634,7 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) m_pre_split = false; } -CWalletKey::CWalletKey(int64_t nExpires) -{ - nTimeCreated = (nExpires ? GetTime() : 0); - nTimeExpires = nExpires; -} - -void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) +void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) { // Update the tx's hashBlock hashBlock = block_hash; @@ -4618,7 +4643,7 @@ void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { if (hashUnset()) return 0; @@ -4626,7 +4651,7 @@ int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); } -int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const { if (!IsCoinBase()) return 0; @@ -4635,24 +4660,12 @@ int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const +bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state) -{ - // We must set fInMempool here - while it will be re-set to true by the - // entered-mempool callback, if we did not there would be a race where a - // user could call sendmoney in a loop and hit spurious out of funds errors - // because we think that this newly generated transaction's change is - // unavailable as we're not yet aware that it is in the mempool. - bool ret = locked_chain.submitToMemoryPool(tx, pwallet->m_default_max_tx_fee, state); - fInMempool |= ret; - return ret; -} - void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) { if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6a7097bf44..cf388ad827 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -12,7 +12,6 @@ #include <outputtype.h> #include <policy/feerate.h> #include <script/sign.h> -#include <streams.h> #include <tinyformat.h> #include <ui_interface.h> #include <util/strencodings.h> @@ -51,13 +50,13 @@ std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning); -enum WalletCreationStatus { +enum class WalletCreationStatus { SUCCESS, CREATION_FAILED, ENCRYPTION_FAILED }; -std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning, WalletCreationStatus& status, const SecureString& passphrase, uint64_t wallet_creation_flags); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result); //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; @@ -365,82 +364,24 @@ struct COutputEntry int vout; }; -/** A transaction with a merkle branch linking it to the block chain. */ +/** Legacy class used for deserializing vtxPrev for backwards compatibility. + * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, + * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. + * These need to get deserialized for field alignment when deserializing + * a CWalletTx, but the deserialized values are discarded.**/ class CMerkleTx { -private: - /** Constant used in hashBlock to indicate tx has been abandoned */ - static const uint256 ABANDON_HASH; - public: - CTransactionRef tx; - uint256 hashBlock; - - /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest - * block in the chain we know this or any in-wallet dependency conflicts - * with. Older clients interpret nIndex == -1 as unconfirmed for backward - * compatibility. - */ - int nIndex; - - CMerkleTx() - { - SetTx(MakeTransactionRef()); - Init(); - } - - explicit CMerkleTx(CTransactionRef arg) - { - SetTx(std::move(arg)); - Init(); - } - - void Init() + template<typename Stream> + void Unserialize(Stream& s) { - hashBlock = uint256(); - nIndex = -1; - } + CTransactionRef tx; + uint256 hashBlock; + std::vector<uint256> vMerkleBranch; + int nIndex; - void SetTx(CTransactionRef arg) - { - tx = std::move(arg); + s >> tx >> hashBlock >> vMerkleBranch >> nIndex; } - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - std::vector<uint256> vMerkleBranch; // For compatibility with older versions. - READWRITE(tx); - READWRITE(hashBlock); - READWRITE(vMerkleBranch); - READWRITE(nIndex); - } - - void SetMerkleBranch(const uint256& block_hash, int posInBlock); - - /** - * Return depth of transaction in blockchain: - * <0 : conflicts with a transaction this deep in the blockchain - * 0 : in memory pool, waiting to be included in a block - * >=1 : this many blocks deep in the main chain - */ - int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; - bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } - - /** - * @return number of blocks to maturity for this transaction: - * 0 : is not a coinbase transaction, or is a mature coinbase transaction - * >0 : is a coinbase transaction which matures in this many blocks - */ - int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; - bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } - bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } - void setAbandoned() { hashBlock = ABANDON_HASH; } - - const uint256& GetHash() const { return tx->GetHash(); } - bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; //Get the marginal bytes of spending the specified output @@ -450,11 +391,14 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, * A transaction with a bunch of additional info that only the owner cares about. * It includes any unrecorded transactions needed to link it back to the block chain. */ -class CWalletTx : public CMerkleTx +class CWalletTx { private: const CWallet* pwallet; + /** Constant used in hashBlock to indicate tx has been abandoned */ + static const uint256 ABANDON_HASH; + public: /** * Key/value map with information about the transaction. @@ -512,7 +456,10 @@ public: mutable bool fInMempool; mutable CAmount nChangeCached; - CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) + CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) + : tx(std::move(arg)), + hashBlock(uint256()), + nIndex(-1) { Init(pwalletIn); } @@ -532,10 +479,18 @@ public: nOrderPos = -1; } + CTransactionRef tx; + uint256 hashBlock; + /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest + * block in the chain we know this or any in-wallet dependency conflicts + * with. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility. + */ + int nIndex; + template<typename Stream> void Serialize(Stream& s) const { - char fSpent = false; mapValue_t mapValueCopy = mapValue; mapValueCopy["fromaccount"] = ""; @@ -544,20 +499,21 @@ public: mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); } - s << static_cast<const CMerkleTx&>(*this); - std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev - s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; + std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<char> dummy_vector2; //!< Used to be vtxPrev + char dummy_char = false; //!< Used to be fSpent + s << tx << hashBlock << dummy_vector1 << nIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_char; } template<typename Stream> void Unserialize(Stream& s) { Init(nullptr); - char fSpent; - s >> static_cast<CMerkleTx&>(*this); - std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev - s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; + std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev + char dummy_char; //! Used to be fSpent + s >> tx >> hashBlock >> dummy_vector1 >> nIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_char; ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; @@ -568,6 +524,11 @@ public: mapValue.erase("timesmart"); } + void SetTx(CTransactionRef arg) + { + tx = std::move(arg); + } + //! make sure balances are recalculated void MarkDirty() { @@ -618,11 +579,8 @@ public: int64_t GetTxTime() const; - // Pass this transaction to the node to relay to its peers - bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain); - - /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state); + // Pass this transaction to node for mempool insertion and relay to peers if flag set to true + bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -631,6 +589,31 @@ public: // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" // in place. std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; + + void SetMerkleBranch(const uint256& block_hash, int posInBlock); + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; + bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } + + /** + * @return number of blocks to maturity for this transaction: + * 0 : is not a coinbase transaction, or is a mature coinbase transaction + * >0 : is a coinbase transaction which matures in this many blocks + */ + int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; + bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } + bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } + void setAbandoned() { hashBlock = ABANDON_HASH; } + + const uint256& GetHash() const { return tx->GetHash(); } + bool IsCoinBase() const { return tx->IsCoinBase(); } + bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; class COutput @@ -677,33 +660,6 @@ public: } }; -/** Private key that includes an expiration date in case it never gets used. */ -class CWalletKey -{ -public: - CPrivKey vchPrivKey; - int64_t nTimeCreated; - int64_t nTimeExpires; - std::string strComment; - // todo: add something to note what created it (user, getnewaddress, change) - // maybe should have a map<string, string> property map - - explicit CWalletKey(int64_t nExpires=0); - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - int nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(nVersion); - READWRITE(vchPrivKey); - READWRITE(nTimeCreated); - READWRITE(nTimeExpires); - READWRITE(LIMITED_STRING(strComment, 65536)); - } -}; - struct CoinSelectionParams { bool use_bnb = true; @@ -970,7 +926,7 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe = true, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. @@ -1150,10 +1106,10 @@ public: bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const; bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; - bool ImportScripts(const std::set<CScript> scripts) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET}; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index ece97e2a75..635997afc9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -21,45 +21,71 @@ #include <boost/thread.hpp> +namespace DBKeys { +const std::string ACENTRY{"acentry"}; +const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"}; +const std::string BESTBLOCK{"bestblock"}; +const std::string CRYPTED_KEY{"ckey"}; +const std::string CSCRIPT{"cscript"}; +const std::string DEFAULTKEY{"defaultkey"}; +const std::string DESTDATA{"destdata"}; +const std::string FLAGS{"flags"}; +const std::string HDCHAIN{"hdchain"}; +const std::string KEYMETA{"keymeta"}; +const std::string KEY{"key"}; +const std::string MASTER_KEY{"mkey"}; +const std::string MINVERSION{"minversion"}; +const std::string NAME{"name"}; +const std::string OLD_KEY{"wkey"}; +const std::string ORDERPOSNEXT{"orderposnext"}; +const std::string POOL{"pool"}; +const std::string PURPOSE{"purpose"}; +const std::string SETTINGS{"settings"}; +const std::string TX{"tx"}; +const std::string VERSION{"version"}; +const std::string WATCHMETA{"watchmeta"}; +const std::string WATCHS{"watchs"}; +} // namespace DBKeys + // // WalletBatch // bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName) { - return WriteIC(std::make_pair(std::string("name"), strAddress), strName); + return WriteIC(std::make_pair(DBKeys::NAME, strAddress), strName); } bool WalletBatch::EraseName(const std::string& strAddress) { // This should only be used for sending addresses, never for receiving addresses, // receiving addresses must always have an address book entry if they're not change return. - return EraseIC(std::make_pair(std::string("name"), strAddress)); + return EraseIC(std::make_pair(DBKeys::NAME, strAddress)); } bool WalletBatch::WritePurpose(const std::string& strAddress, const std::string& strPurpose) { - return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose); + return WriteIC(std::make_pair(DBKeys::PURPOSE, strAddress), strPurpose); } bool WalletBatch::ErasePurpose(const std::string& strAddress) { - return EraseIC(std::make_pair(std::string("purpose"), strAddress)); + return EraseIC(std::make_pair(DBKeys::PURPOSE, strAddress)); } bool WalletBatch::WriteTx(const CWalletTx& wtx) { - return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx); + return WriteIC(std::make_pair(DBKeys::TX, wtx.GetHash()), wtx); } bool WalletBatch::EraseTx(uint256 hash) { - return EraseIC(std::make_pair(std::string("tx"), hash)); + return EraseIC(std::make_pair(DBKeys::TX, hash)); } bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) { - return WriteIC(std::make_pair(std::string("keymeta"), pubkey), meta, overwrite); + return WriteIC(std::make_pair(DBKeys::KEYMETA, pubkey), meta, overwrite); } bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) @@ -74,7 +100,7 @@ bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end()); - return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); + return WriteIC(std::make_pair(DBKeys::KEY, vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); } bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, @@ -85,75 +111,74 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, return false; } - if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) { + if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey), vchCryptedSecret, false)) { return false; } - EraseIC(std::make_pair(std::string("key"), vchPubKey)); - EraseIC(std::make_pair(std::string("wkey"), vchPubKey)); + EraseIC(std::make_pair(DBKeys::KEY, vchPubKey)); return true; } bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) { - return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true); + return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true); } bool WalletBatch::WriteCScript(const uint160& hash, const CScript& redeemScript) { - return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false); + return WriteIC(std::make_pair(DBKeys::CSCRIPT, hash), redeemScript, false); } bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta) { - if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) { + if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) { return false; } - return WriteIC(std::make_pair(std::string("watchs"), dest), '1'); + return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1'); } bool WalletBatch::EraseWatchOnly(const CScript &dest) { - if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) { + if (!EraseIC(std::make_pair(DBKeys::WATCHMETA, dest))) { return false; } - return EraseIC(std::make_pair(std::string("watchs"), dest)); + return EraseIC(std::make_pair(DBKeys::WATCHS, dest)); } bool WalletBatch::WriteBestBlock(const CBlockLocator& locator) { - WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan - return WriteIC(std::string("bestblock_nomerkle"), locator); + WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan + return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator); } bool WalletBatch::ReadBestBlock(CBlockLocator& locator) { - if (m_batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true; - return m_batch.Read(std::string("bestblock_nomerkle"), locator); + if (m_batch.Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true; + return m_batch.Read(DBKeys::BESTBLOCK_NOMERKLE, locator); } bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext) { - return WriteIC(std::string("orderposnext"), nOrderPosNext); + return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext); } bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool) { - return m_batch.Read(std::make_pair(std::string("pool"), nPool), keypool); + return m_batch.Read(std::make_pair(DBKeys::POOL, nPool), keypool); } bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool) { - return WriteIC(std::make_pair(std::string("pool"), nPool), keypool); + return WriteIC(std::make_pair(DBKeys::POOL, nPool), keypool); } bool WalletBatch::ErasePool(int64_t nPool) { - return EraseIC(std::make_pair(std::string("pool"), nPool)); + return EraseIC(std::make_pair(DBKeys::POOL, nPool)); } bool WalletBatch::WriteMinVersion(int nVersion) { - return WriteIC(std::string("minversion"), nVersion); + return WriteIC(DBKeys::MINVERSION, nVersion); } class CWalletScanState { @@ -165,7 +190,6 @@ public: unsigned int m_unknown_records{0}; bool fIsEncrypted{false}; bool fAnyUnordered{false}; - int nFileVersion{0}; std::vector<uint256> vWalletUpgrade; CWalletScanState() { @@ -181,20 +205,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, // Taking advantage of the fact that pair serialization // is just the two items serialized one after the other ssKey >> strType; - if (strType == "name") - { + if (strType == DBKeys::NAME) { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name; - } - else if (strType == "purpose") - { + } else if (strType == DBKeys::PURPOSE) { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; - } - else if (strType == "tx") - { + } else if (strType == DBKeys::TX) { uint256 hash; ssKey >> hash; CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); @@ -228,9 +247,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, wss.fAnyUnordered = true; pwallet->LoadToWallet(wtx); - } - else if (strType == "watchs") - { + } else if (strType == DBKeys::WATCHS) { wss.nWatchKeys++; CScript script; ssKey >> script; @@ -238,9 +255,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> fYes; if (fYes == '1') pwallet->LoadWatchOnly(script); - } - else if (strType == "key" || strType == "wkey") - { + } else if (strType == DBKeys::KEY) { CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) @@ -252,20 +267,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CPrivKey pkey; uint256 hash; - if (strType == "key") - { - wss.nKeys++; - ssValue >> pkey; - } else { - CWalletKey wkey; - ssValue >> wkey; - pkey = wkey.vchPrivKey; - } + wss.nKeys++; + ssValue >> pkey; - // Old wallets store keys as "key" [pubkey] => [privkey] + // Old wallets store keys as DBKeys::KEY [pubkey] => [privkey] // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key // using EC operations as a checksum. - // Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while + // Newer wallets store keys as DBKeys::KEY [pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while // remaining backwards-compatible. try { @@ -302,9 +310,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadKey failed"; return false; } - } - else if (strType == "mkey") - { + } else if (strType == DBKeys::MASTER_KEY) { unsigned int nID; ssKey >> nID; CMasterKey kMasterKey; @@ -317,9 +323,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, pwallet->mapMasterKeys[nID] = kMasterKey; if (pwallet->nMasterKeyMaxID < nID) pwallet->nMasterKeyMaxID = nID; - } - else if (strType == "ckey") - { + } else if (strType == DBKeys::CRYPTED_KEY) { CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) @@ -337,27 +341,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } wss.fIsEncrypted = true; - } - else if (strType == "keymeta") - { + } else if (strType == DBKeys::KEYMETA) { CPubKey vchPubKey; ssKey >> vchPubKey; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); - } - else if (strType == "watchmeta") - { + } else if (strType == DBKeys::WATCHMETA) { CScript script; ssKey >> script; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); - } - else if (strType == "defaultkey") - { + } else if (strType == DBKeys::DEFAULTKEY) { // We don't want or need the default key, but if there is one set, // we want to make sure that it is valid so that we can detect corruption CPubKey vchPubKey; @@ -366,24 +364,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: Default Key corrupt"; return false; } - } - else if (strType == "pool") - { + } else if (strType == DBKeys::POOL) { int64_t nIndex; ssKey >> nIndex; CKeyPool keypool; ssValue >> keypool; pwallet->LoadKeyPool(nIndex, keypool); - } - else if (strType == "version") - { - ssValue >> wss.nFileVersion; - if (wss.nFileVersion == 10300) - wss.nFileVersion = 300; - } - else if (strType == "cscript") - { + } else if (strType == DBKeys::CSCRIPT) { uint160 hash; ssKey >> hash; CScript script; @@ -393,33 +381,31 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadCScript failed"; return false; } - } - else if (strType == "orderposnext") - { + } else if (strType == DBKeys::ORDERPOSNEXT) { ssValue >> pwallet->nOrderPosNext; - } - else if (strType == "destdata") - { + } else if (strType == DBKeys::DESTDATA) { std::string strAddress, strKey, strValue; ssKey >> strAddress; ssKey >> strKey; ssValue >> strValue; pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue); - } - else if (strType == "hdchain") - { + } else if (strType == DBKeys::HDCHAIN) { CHDChain chain; ssValue >> chain; pwallet->SetHDChain(chain, true); - } else if (strType == "flags") { + } else if (strType == DBKeys::FLAGS) { uint64_t flags; ssValue >> flags; if (!pwallet->SetWalletFlags(flags, true)) { strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found"; return false; } - } else if (strType != "bestblock" && strType != "bestblock_nomerkle" && - strType != "minversion" && strType != "acentry") { + } else if (strType == DBKeys::OLD_KEY) { + strErr = "Found unsupported 'wkey' record, try loading with version 0.18"; + return false; + } else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE && + strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY && + strType != DBKeys::VERSION && strType != DBKeys::SETTINGS) { wss.m_unknown_records++; } } catch (const std::exception& e) { @@ -438,8 +424,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, bool WalletBatch::IsKeyType(const std::string& strType) { - return (strType== "key" || strType == "wkey" || - strType == "mkey" || strType == "ckey"); + return (strType == DBKeys::KEY || + strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY); } DBErrors WalletBatch::LoadWallet(CWallet* pwallet) @@ -451,8 +437,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) LOCK(pwallet->cs_wallet); try { int nMinVersion = 0; - if (m_batch.Read((std::string)"minversion", nMinVersion)) - { + if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; pwallet->LoadMinVersion(nMinVersion); @@ -486,15 +471,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) { // losing keys is considered a catastrophic error, anything else // we assume the user can live with: - if (IsKeyType(strType) || strType == "defaultkey") { + if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) { result = DBErrors::CORRUPT; - } else if(strType == "flags") { + } else if (strType == DBKeys::FLAGS) { // reading the wallet flags can only fail if unknown flags are present result = DBErrors::TOO_NEW; } else { // Leave other errors alone, if we try to fix them we might make things worse. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. - if (strType == "tx") + if (strType == DBKeys::TX) // Rescan if there is a bad transaction record: gArgs.SoftSetBoolArg("-rescan", true); } @@ -519,7 +504,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (result != DBErrors::LOAD_OK) return result; - pwallet->WalletLogPrintf("nFileVersion = %d\n", wss.nFileVersion); + // Last client version to open this wallet, was previously the file version number + int last_client = CLIENT_VERSION; + m_batch.Read(DBKeys::VERSION, last_client); + + int wallet_version = pwallet->GetVersion(); + pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client); 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); @@ -532,11 +522,11 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) WriteTx(pwallet->mapWallet.at(hash)); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: - if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) + if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000)) return DBErrors::NEED_REWRITE; - if (wss.nFileVersion < CLIENT_VERSION) // Update - WriteVersion(CLIENT_VERSION); + if (last_client < CLIENT_VERSION) // Update + m_batch.Write(DBKeys::VERSION, CLIENT_VERSION); if (wss.fAnyUnordered) result = pwallet->ReorderTransactions(); @@ -558,8 +548,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW try { int nMinVersion = 0; - if (m_batch.Read((std::string)"minversion", nMinVersion)) - { + if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; } @@ -588,7 +577,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW std::string strType; ssKey >> strType; - if (strType == "tx") { + if (strType == DBKeys::TX) { uint256 hash; ssKey >> hash; @@ -723,8 +712,9 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, dummyWss, strType, strErr); } - if (!IsKeyType(strType) && strType != "hdchain") + if (!IsKeyType(strType) && strType != DBKeys::HDCHAIN) { return false; + } if (!fReadOK) { LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr); @@ -746,23 +736,23 @@ bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::string& w bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value) { - return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value); + return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)), value); } bool WalletBatch::EraseDestData(const std::string &address, const std::string &key) { - return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key))); + return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key))); } bool WalletBatch::WriteHDChain(const CHDChain& chain) { - return WriteIC(std::string("hdchain"), chain); + return WriteIC(DBKeys::HDCHAIN, chain); } bool WalletBatch::WriteWalletFlags(const uint64_t flags) { - return WriteIC(std::string("flags"), flags); + return WriteIC(DBKeys::FLAGS, flags); } bool WalletBatch::TxnBegin() @@ -779,13 +769,3 @@ bool WalletBatch::TxnAbort() { return m_batch.TxnAbort(); } - -bool WalletBatch::ReadVersion(int& nVersion) -{ - return m_batch.ReadVersion(nVersion); -} - -bool WalletBatch::WriteVersion(int nVersion) -{ - return m_batch.WriteVersion(nVersion); -} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d4a3bba97a..0fee35934d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -55,6 +55,32 @@ enum class DBErrors NEED_REWRITE }; +namespace DBKeys { +extern const std::string ACENTRY; +extern const std::string BESTBLOCK; +extern const std::string BESTBLOCK_NOMERKLE; +extern const std::string CRYPTED_KEY; +extern const std::string CSCRIPT; +extern const std::string DEFAULTKEY; +extern const std::string DESTDATA; +extern const std::string FLAGS; +extern const std::string HDCHAIN; +extern const std::string KEY; +extern const std::string KEYMETA; +extern const std::string MASTER_KEY; +extern const std::string MINVERSION; +extern const std::string NAME; +extern const std::string OLD_KEY; +extern const std::string ORDERPOSNEXT; +extern const std::string POOL; +extern const std::string PURPOSE; +extern const std::string SETTINGS; +extern const std::string TX; +extern const std::string VERSION; +extern const std::string WATCHMETA; +extern const std::string WATCHS; +} // namespace DBKeys + /* simple HD chain data model */ class CHDChain { @@ -249,10 +275,6 @@ public: bool TxnCommit(); //! Abort current transaction bool TxnAbort(); - //! Read wallet version - bool ReadVersion(int& nVersion); - //! Write wallet version - bool WriteVersion(int nVersion); private: BerkeleyBatch m_batch; WalletDatabase& m_database; diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 45ecaabe14..5364ac4b8c 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -8,6 +8,7 @@ If no argument is provided, the most recent test directory will be used.""" import argparse from collections import defaultdict, namedtuple +import glob import heapq import itertools import os @@ -76,9 +77,17 @@ def read_logs(tmp_dir): Delegates to generator function get_log_events() to provide individual log events for each of the input log files.""" + # Find out what the folder is called that holds the debug.log file + chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir)) + if chain: + chain = chain[0] # pick the first one if more than one chain was found (should never happen) + chain = re.search('node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name + else: + chain = 'regtest' # fallback to regtest (should only happen when none exists) + files = [("test", "%s/test_framework.log" % tmp_dir)] for i in itertools.count(): - logfile = "{}/node{}/regtest/debug.log".format(tmp_dir, i) + logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain) if not os.path.isfile(logfile): break files.append(("node%d" % i, logfile)) @@ -164,25 +173,26 @@ def get_log_events(source, logfile): def print_logs_plain(log_events, colors): - """Renders the iterator of log events into text.""" - for event in log_events: - lines = event.event.splitlines() - print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) - if len(lines) > 1: - for line in lines[1:]: - print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) + """Renders the iterator of log events into text.""" + for event in log_events: + lines = event.event.splitlines() + print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) + if len(lines) > 1: + for line in lines[1:]: + print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) def print_logs_html(log_events): - """Renders the iterator of log events into html.""" - try: - import jinja2 - except ImportError: - print("jinja2 not found. Try `pip install jinja2`") - sys.exit(1) - print(jinja2.Environment(loader=jinja2.FileSystemLoader('./')) + """Renders the iterator of log events into html.""" + try: + import jinja2 + except ImportError: + print("jinja2 not found. Try `pip install jinja2`") + sys.exit(1) + print(jinja2.Environment(loader=jinja2.FileSystemLoader('./')) .get_template('combined_log_template.html') .render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events])) + if __name__ == '__main__': main() diff --git a/test/functional/example_test.py b/test/functional/example_test.py index a2726763d0..70dfe81d4e 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -186,12 +186,15 @@ class ExampleTest(BitcoinTestFramework): self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) + self.log.info("Wait for node2 to receive all the blocks from node1") + self.sync_all() + self.log.info("Add P2P connection to node2") self.nodes[0].disconnect_p2ps() self.nodes[2].add_p2p_connection(BaseNode()) - self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us") + self.log.info("Test that node2 propagates all the blocks to us") getdata_request = msg_getdata() for block in blocks: diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index b7814bf33e..420a3a7688 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -72,7 +72,7 @@ class AssumeValidTest(BitcoinTestFramework): break try: p2p_conn.send_message(msg_block(self.blocks[i])) - except IOError as e: + except IOError: assert not p2p_conn.is_connected break diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index b5eac88ba7..12a52935ef 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -74,6 +74,10 @@ class CBrokenBlock(CBlock): def normal_serialize(self): return super().serialize() + +DUPLICATE_COINBASE_SCRIPT_SIG = b'\x01\x78' # Valid for block at height 120 + + class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -96,6 +100,13 @@ class FullBlockTest(BitcoinTestFramework): self.spendable_outputs = [] # Create a new block + b_dup_cb = self.next_block('dup_cb') + b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_cb.vtx[0].rehash() + duplicate_tx = b_dup_cb.vtx[0] + b_dup_cb = self.update_block('dup_cb', []) + self.send_blocks([b_dup_cb]) + b0 = self.next_block(0) self.save_spendable_output() self.send_blocks([b0]) @@ -486,6 +497,14 @@ class FullBlockTest(BitcoinTestFramework): tx_last = tx_new b39_outputs += 1 + # The accounting in the loop above can be off, because it misses the + # compact size encoding of the number of transactions in the block. + # Make sure we didn't accidentally make too big a block. Note that the + # size of the block has non-determinism due to the ECDSA signature in + # the first transaction. + while (len(b39.serialize()) >= MAX_BLOCK_BASE_SIZE): + del b39.vtx[-1] + b39 = self.update_block(39, []) self.send_blocks([b39], True) self.save_spendable_output() @@ -750,7 +769,7 @@ class FullBlockTest(BitcoinTestFramework): # Test a few invalid tx types # - # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () # \-> ??? (17) # @@ -776,14 +795,14 @@ class FullBlockTest(BitcoinTestFramework): # reset to good chain self.move_tip(57) - b60 = self.next_block(60, spend=out[17]) + b60 = self.next_block(60) self.send_blocks([b60], True) self.save_spendable_output() - # Test BIP30 + # Test BIP30 (reject duplicate) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b61 (18) + # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () + # \-> b61 () # # Blocks are not allowed to contain a transaction whose id matches that of an earlier, # not-fully-spent transaction in the same chain. To test, make identical coinbases; @@ -791,20 +810,44 @@ class FullBlockTest(BitcoinTestFramework): # self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") self.move_tip(60) - b61 = self.next_block(61, spend=out[18]) - b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases + b61 = self.next_block(61) + b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG b61.vtx[0].rehash() b61 = self.update_block(61, []) - assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize()) + assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize()) self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True) + # Test BIP30 (allow duplicate if spent) + # + # -> b57 (16) -> b60 () + # \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # + self.move_tip(57) + b_spend_dup_cb = self.next_block('spend_dup_cb') + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0))) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) + self.sign_tx(tx, duplicate_tx) + tx.rehash() + b_spend_dup_cb = self.update_block('spend_dup_cb', [tx]) + + b_dup_2 = self.next_block('dup_2') + b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_2.vtx[0].rehash() + b_dup_2 = self.update_block('dup_2', []) + assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize()) + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 119) + self.send_blocks([b_spend_dup_cb, b_dup_2], success=True) + # The duplicate has less confirmations + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 1) + # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b62 (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b62 (18) # self.log.info("Reject a block with a transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b62 = self.next_block(62) tx = CTransaction() tx.nLockTime = 0xffffffff # this locktime is non-final @@ -817,11 +860,11 @@ class FullBlockTest(BitcoinTestFramework): # Test a non-final coinbase is also rejected # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b63 (-) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b63 (-) # self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b63 = self.next_block(63) b63.vtx[0].nLockTime = 0xffffffff b63.vtx[0].vin[0].nSequence = 0xDEADBEEF @@ -837,14 +880,14 @@ class FullBlockTest(BitcoinTestFramework): # What matters is that the receiving node should not reject the bloated block, and then reject the canonical # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) - # \ - # b64a (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18) + # \ + # b64a (18) # b64a is a bloated block (non-canonical varint) # b64 is a good block (same as b64 but w/ canonical varint) # self.log.info("Accept a valid block even if a bloated version of the block has previously been sent") - self.move_tip(60) + self.move_tip('dup_2') regular_block = self.next_block("64a", spend=out[18]) # make it a "broken_block," with non-canonical serialization @@ -870,7 +913,7 @@ class FullBlockTest(BitcoinTestFramework): node.disconnect_p2ps() self.reconnect_p2p() - self.move_tip(60) + self.move_tip('dup_2') b64 = CBlock(b64a) b64.vtx = copy.deepcopy(b64a.vtx) assert_equal(b64.hash, b64a.hash) @@ -882,7 +925,7 @@ class FullBlockTest(BitcoinTestFramework): # Spend an output created in the block itself # - # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) + # -> b_dup_2 () -> b64 (18) -> b65 (19) # self.log.info("Accept a block with a transaction spending an output created in the same block") self.move_tip(64) @@ -895,8 +938,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to spend an output created later in the same block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b66 (20) + # -> b64 (18) -> b65 (19) + # \-> b66 (20) self.log.info("Reject a block with a transaction spending an output created later in the same block") self.move_tip(65) b66 = self.next_block(66) @@ -907,8 +950,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to double-spend a transaction created in a block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b67 (20) + # -> b64 (18) -> b65 (19) + # \-> b67 (20) # # self.log.info("Reject a block with a transaction double spending a transaction created in the same block") @@ -922,8 +965,8 @@ class FullBlockTest(BitcoinTestFramework): # More tests of block subsidy # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b68 (20) + # -> b64 (18) -> b65 (19) -> b69 (20) + # \-> b68 (20) # # b68 - coinbase with an extra 10 satoshis, # creates a tx that has 9 satoshis from out[20] go to fees @@ -949,8 +992,8 @@ class FullBlockTest(BitcoinTestFramework): # Test spending the outpoint of a non-existent transaction # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b70 (21) + # -> b65 (19) -> b69 (20) + # \-> b70 (21) # self.log.info("Reject a block containing a transaction spending from a non-existent input") self.move_tip(69) @@ -965,8 +1008,8 @@ class FullBlockTest(BitcoinTestFramework): # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b71 (21) + # -> b65 (19) -> b69 (20) -> b72 (21) + # \-> b71 (21) # # b72 is a good block. # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72. @@ -994,8 +1037,8 @@ class FullBlockTest(BitcoinTestFramework): # Test some invalid scripts and MAX_BLOCK_SIGOPS # - # -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b** (22) + # -> b69 (20) -> b72 (21) + # \-> b** (22) # # b73 - tx with excessive sigops that are placed after an excessively large script element. diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py index 3a4889bbe9..6f01f97ea2 100755 --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -18,10 +18,10 @@ class BlocksdirTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks")) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks")) assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks")) shutil.rmtree(self.nodes[0].datadir) - initialize_datadir(self.options.tmpdir, 0) + initialize_datadir(self.options.tmpdir, 0, self.chain) self.log.info("Starting with nonexistent blocksdir ...") blocksdir_path = os.path.join(self.options.tmpdir, 'blocksdir') self.nodes[0].assert_start_raises_init_error(["-blocksdir=" + blocksdir_path], 'Error: Specified blocks directory "{}" does not exist.'.format(blocksdir_path)) @@ -30,8 +30,8 @@ class BlocksdirTest(BitcoinTestFramework): self.start_node(0, ["-blocksdir=" + blocksdir_path]) self.log.info("mining blocks..") self.nodes[0].generatetoaddress(10, self.nodes[0].get_deterministic_priv_key().address) - assert os.path.isfile(os.path.join(blocksdir_path, "regtest", "blocks", "blk00000.dat")) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks", "index")) + assert os.path.isfile(os.path.join(blocksdir_path, self.chain, "blocks", "blk00000.dat")) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks", "index")) if __name__ == '__main__': diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index aae262344d..70a824b863 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -35,9 +35,10 @@ class ConfArgsTest(BitcoinTestFramework): conf.write('-dash=1\n') self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -') - with open(inc_conf_file_path, 'w', encoding='utf8') as conf: - conf.write("wallet=foo\n") - self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') + if self.is_wallet_compiled(): + with open(inc_conf_file_path, 'w', encoding='utf8') as conf: + conf.write("wallet=foo\n") + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('regtest=0\n') # mainnet diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 62062926a6..ff014de0e0 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -30,17 +30,27 @@ import http.client import random import time -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + ToHex, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, create_confirmed_utxos, hex_str_to_bytes +from test_framework.util import ( + assert_equal, + create_confirmed_utxos, + hex_str_to_bytes, +) class ChainstateWriteCrashTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = False - # Need a bit of extra time for the nodes to start up for this test - self.rpc_timeout = 90 + self.rpc_timeout = 180 # Set -maxmempool=0 to turn off mempool memory sharing with dbcache # Set -rpcservertimeout=900 to reduce socket disconnects in this @@ -54,7 +64,8 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args # Node3 is a normal node with default args, except will mine full blocks - self.node3_args = ["-blockmaxweight=4000000"] + # and non-standard txs (e.g. txs with "dust" outputs) + self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"] self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args] def skip_test_if_missing_module(self): @@ -267,7 +278,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # Warn if any of the nodes escaped restart. for i in range(3): if self.restart_counts[i] == 0: - self.log.warn("Node %d never crashed during utxo flush!", i) + self.log.warning("Node %d never crashed during utxo flush!", i) if __name__ == "__main__": ChainstateWriteCrashTest().main() diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index a4b9f213a1..d2d41b1206 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -28,6 +28,7 @@ P2SH_2 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_2), OP_EQUAL]) # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])] + def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment): """Create and send a transaction with a random fee. @@ -69,6 +70,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee return (ToHex(tx), fee) + def split_inputs(from_node, txins, txouts, initial_split=False): """Generate a lot of inputs so we can generate a ton of transactions. @@ -97,6 +99,7 @@ def split_inputs(from_node, txins, txouts, initial_split=False): txouts.append({"txid": txid, "vout": 0, "amount": half_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) + def check_estimates(node, fees_seen): """Call estimatesmartfee and verify that the estimates meet certain invariants.""" @@ -120,9 +123,17 @@ def check_estimates(node, fees_seen): else: assert_greater_than_or_equal(i + 1, e["blocks"]) + class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 + # mine non-standard txs (e.g. txs with "dust" outputs) + # Force fSendTrickle to true (via whitelist) + self.extra_args = [ + ["-acceptnonstdtxn", "-whitelist=127.0.0.1"], + ["-acceptnonstdtxn", "-whitelist=127.0.0.1", "-blockmaxweight=68000"], + ["-acceptnonstdtxn", "-whitelist=127.0.0.1", "-blockmaxweight=32000"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -133,9 +144,7 @@ class EstimateFeeTest(BitcoinTestFramework): But first we need to use one node to create a lot of outputs which we will use to generate our transactions. """ - self.add_nodes(3, extra_args=[["-maxorphantx=1000", "-whitelist=127.0.0.1"], - ["-blockmaxweight=68000", "-maxorphantx=1000"], - ["-blockmaxweight=32000", "-maxorphantx=1000"]]) + self.add_nodes(3, extra_args=self.extra_args) # Use node0 to mine blocks for input splitting # Node1 mines small blocks but that are bigger than the expected transaction rate. # NOTE: the CreateNewBlock code starts counting block weight at 4,000 weight, @@ -160,9 +169,9 @@ class EstimateFeeTest(BitcoinTestFramework): self.memutxo, Decimal("0.005"), min_fee, min_fee) tx_kbytes = (len(txhex) // 2) / 1000.0 self.fees_per_kb.append(float(fee) / tx_kbytes) - self.sync_mempools(self.nodes[0:3], wait=.1) + self.sync_mempools(wait=.1) mined = mining_node.getblock(mining_node.generate(1)[0], True)["tx"] - self.sync_blocks(self.nodes[0:3], wait=.1) + self.sync_blocks(wait=.1) # update which txouts are confirmed newmem = [] for utx in self.memutxo: @@ -184,22 +193,22 @@ class EstimateFeeTest(BitcoinTestFramework): split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True) # Mine - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) # Repeatedly split those 2 outputs, doubling twice for each rep # Use txouts to monitor the available utxo, since these won't be tracked in wallet reps = 0 - while (reps < 5): + while reps < 5: # Double txouts to txouts2 - while (len(self.txouts) > 0): + while len(self.txouts) > 0: split_inputs(self.nodes[0], self.txouts, self.txouts2) - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) # Double txouts2 to txouts - while (len(self.txouts2) > 0): + while len(self.txouts2) > 0: split_inputs(self.nodes[0], self.txouts2, self.txouts) - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) reps += 1 self.log.info("Finished splitting") @@ -239,5 +248,6 @@ class EstimateFeeTest(BitcoinTestFramework): self.log.info("Final estimates after emptying mempools") check_estimates(self.nodes[1], self.fees_per_kb) + if __name__ == '__main__': EstimateFeeTest().main() diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 66c395d7a2..727f4b9589 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -204,6 +204,7 @@ class PruneTest(BitcoinTestFramework): self.log.info("Mine 220 more large blocks so we have requisite history") mine_large_blocks(self.nodes[0], 220) + self.sync_blocks(self.nodes[0:3], timeout=120) usage = calc_usage(self.prunedir) self.log.info("Usage should be below target: %d" % usage) diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 7bb7044cc0..0a378c5ef5 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -29,7 +29,7 @@ class TestBitcoinCli(BitcoinTestFramework): rpc_response = self.nodes[0].getblockchaininfo() assert_equal(cli_response, rpc_response) - user, password = get_auth_cookie(self.nodes[0].datadir) + user, password = get_auth_cookie(self.nodes[0].datadir, self.chain) self.log.info("Test -stdinrpcpass option") assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount()) diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 8e58c85c15..1ba781c539 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -7,15 +7,15 @@ import struct from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import CTransaction -from test_framework.util import ( - assert_equal, - hash256, -) +from test_framework.messages import CTransaction, hash256 +from test_framework.util import assert_equal from io import BytesIO ADDRESS = "tcp://127.0.0.1:28332" +def hash256_reversed(byte_str): + return hash256(byte_str)[::-1] + class ZMQSubscriber: def __init__(self, socket, topic): self.sequence = 0 @@ -103,7 +103,7 @@ class ZMQTest (BitcoinTestFramework): # Should receive the generated raw block. block = self.rawblock.receive() - assert_equal(genhashes[x], hash256(block[:80]).hex()) + assert_equal(genhashes[x], hash256_reversed(block[:80]).hex()) if self.is_wallet_compiled(): self.log.info("Wait for tx from second node") @@ -116,7 +116,7 @@ class ZMQTest (BitcoinTestFramework): # Should receive the broadcasted raw transaction. hex = self.rawtx.receive() - assert_equal(payment_txid, hash256(hex).hex()) + assert_equal(payment_txid, hash256_reversed(hex).hex()) self.log.info("Test the getzmqnotifications RPC") diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index f955c1a77f..30f851fb8e 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -66,14 +66,14 @@ class MempoolPackagesTest(BitcoinTestFramework): assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1) # Adding one more transaction on to the chain should fail. - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) # ...even if it chains on from some point in the middle of the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) # ...even if it chains on to two parent transactions with one in the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) # ...especially if its > 40k weight - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) # But not if it chains directly off the first transaction self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) # and the second chain should work just fine diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py new file mode 100644 index 0000000000..1013055420 --- /dev/null +++ b/test/functional/p2p_permissions.py @@ -0,0 +1,97 @@ +#!/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 p2p permission message. + +Test that permissions are correctly calculated and applied +""" + +from test_framework.test_node import ErrorMatch +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + p2p_port, +) + +class P2PPermissionsTests(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + self.extra_args = [[],[]] + + def run_test(self): + self.checkpermission( + # relay permission added + ["-whitelist=127.0.0.1", "-whitelistrelay"], + ["relay", "noban", "mempool"], + True) + + self.checkpermission( + # forcerelay and relay permission added + # Legacy parameter interaction which set whitelistrelay to true + # if whitelistforcerelay is true + ["-whitelist=127.0.0.1", "-whitelistforcerelay"], + ["forcerelay", "relay", "noban", "mempool"], + True) + + # Let's make sure permissions are merged correctly + # For this, we need to use whitebind instead of bind + # by modifying the configuration file. + ip_port = "127.0.0.1:{}".format(p2p_port(1)) + self.replaceinconfig(1, "bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port) + self.checkpermission( + ["-whitelist=noban@127.0.0.1" ], + # Check parameter interaction forcerelay should activate relay + ["noban", "bloomfilter", "forcerelay", "relay" ], + False) + self.replaceinconfig(1, "whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1") + + self.checkpermission( + # legacy whitelistrelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # legacy whitelistforcerelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # missing mempool permission to be considered legacy whitelisted + ["-whitelist=noban@127.0.0.1"], + ["noban"], + False) + + self.checkpermission( + # all permission added + ["-whitelist=all@127.0.0.1"], + ["forcerelay", "noban", "mempool", "bloomfilter", "relay"], + False) + + self.stop_node(1) + self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) + + def checkpermission(self, args, expectedPermissions, whitelisted): + self.restart_node(1, args) + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert_equal(peerinfo['whitelisted'], whitelisted) + assert_equal(len(expectedPermissions), len(peerinfo['permissions'])) + for p in expectedPermissions: + if not p in peerinfo['permissions']: + raise AssertionError("Expected permissions %r is not granted." % p) + + def replaceinconfig(self, nodeid, old, new): + with open(self.nodes[nodeid].bitcoinconf, encoding="utf8") as f: + newText=f.read().replace(old, new) + with open(self.nodes[nodeid].bitcoinconf, 'w', encoding="utf8") as f: + f.write(newText) + +if __name__ == '__main__': + P2PPermissionsTests().main() diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py new file mode 100755 index 0000000000..19d78ff303 --- /dev/null +++ b/test/functional/p2p_tx_download.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test transaction download behavior +""" + +from test_framework.messages import ( + CInv, + CTransaction, + FromHex, + MSG_TX, + MSG_TYPE_MASK, + msg_inv, + msg_notfound, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + wait_until, +) +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE + +import time + + +class TestP2PConn(P2PInterface): + def __init__(self): + super().__init__() + self.tx_getdata_count = 0 + + def on_getdata(self, message): + for i in message.inv: + if i.type & MSG_TYPE_MASK == MSG_TX: + self.tx_getdata_count += 1 + + +# Constants from net_processing +GETDATA_TX_INTERVAL = 60 # seconds +MAX_GETDATA_RANDOM_DELAY = 2 # seconds +INBOUND_PEER_TX_DELAY = 2 # seconds +MAX_GETDATA_IN_FLIGHT = 100 +TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10 + +# Python test constants +NUM_INBOUND = 10 +MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY + + +class TxDownloadTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 2 + + def test_tx_requests(self): + self.log.info("Test that we request transactions from all our peers, eventually") + + txid = 0xdeadbeef + + self.log.info("Announce the txid from each incoming peer to node 0") + msg = msg_inv([CInv(t=1, h=txid)]) + for p in self.nodes[0].p2ps: + p.send_message(msg) + p.sync_with_ping() + + outstanding_peer_index = [i for i in range(len(self.nodes[0].p2ps))] + + def getdata_found(peer_index): + p = self.nodes[0].p2ps[peer_index] + with mininode_lock: + return p.last_message.get("getdata") and p.last_message["getdata"].inv[-1].hash == txid + + node_0_mocktime = int(time.time()) + while outstanding_peer_index: + node_0_mocktime += MAX_GETDATA_INBOUND_WAIT + self.nodes[0].setmocktime(node_0_mocktime) + wait_until(lambda: any(getdata_found(i) for i in outstanding_peer_index)) + for i in outstanding_peer_index: + if getdata_found(i): + outstanding_peer_index.remove(i) + + self.nodes[0].setmocktime(0) + self.log.info("All outstanding peers received a getdata") + + def test_inv_block(self): + self.log.info("Generate a transaction on node 0") + tx = self.nodes[0].createrawtransaction( + inputs=[{ # coinbase + "txid": self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0], + "vout": 0 + }], + outputs={ADDRESS_BCRT1_UNSPENDABLE: 50 - 0.00025}, + ) + tx = self.nodes[0].signrawtransactionwithkey( + hexstring=tx, + privkeys=[self.nodes[0].get_deterministic_priv_key().key], + )['hex'] + ctx = FromHex(CTransaction(), tx) + txid = int(ctx.rehash(), 16) + + self.log.info( + "Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND)) + msg = msg_inv([CInv(t=1, h=txid)]) + for p in self.peers: + p.send_message(msg) + p.sync_with_ping() + + self.log.info("Put the tx in node 0's mempool") + self.nodes[0].sendrawtransaction(tx) + + # Since node 1 is connected outbound to an honest peer (node 0), it + # should get the tx within a timeout. (Assuming that node 0 + # announced the tx within the timeout) + # The timeout is the sum of + # * the worst case until the tx is first requested from an inbound + # peer, plus + # * the first time it is re-requested from the outbound peer, plus + # * 2 seconds to avoid races + timeout = 2 + (MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY) + ( + GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY) + self.log.info("Tx should be received at node 1 after {} seconds".format(timeout)) + self.sync_mempools(timeout=timeout) + + def test_in_flight_max(self): + self.log.info("Test that we don't request more than {} transactions from any peer, every {} minutes".format( + MAX_GETDATA_IN_FLIGHT, TX_EXPIRY_INTERVAL / 60)) + txids = [i for i in range(MAX_GETDATA_IN_FLIGHT + 2)] + + p = self.nodes[0].p2ps[0] + + with mininode_lock: + p.tx_getdata_count = 0 + + p.send_message(msg_inv([CInv(t=1, h=i) for i in txids])) + wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT, lock=mininode_lock) + with mininode_lock: + assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT) + + self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request") + p.send_message(msg_notfound(vec=[CInv(t=1, h=txids[0])])) + wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10, lock=mininode_lock) + with mininode_lock: + assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1) + + WAIT_TIME = TX_EXPIRY_INTERVAL // 2 + TX_EXPIRY_INTERVAL + self.log.info("if we wait about {} minutes, we should eventually get more requests".format(WAIT_TIME / 60)) + self.nodes[0].setmocktime(int(time.time() + WAIT_TIME)) + wait_until(lambda: p.tx_getdata_count == MAX_GETDATA_IN_FLIGHT + 2) + self.nodes[0].setmocktime(0) + + def run_test(self): + # Setup the p2p connections + self.peers = [] + for node in self.nodes: + for i in range(NUM_INBOUND): + self.peers.append(node.add_p2p_connection(TestP2PConn())) + + self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND)) + + # Test the in-flight max first, because we want no transactions in + # flight ahead of this test. + self.test_in_flight_max() + + self.test_inv_block() + + self.test_tx_requests() + + +if __name__ == '__main__': + TxDownloadTest().main() diff --git a/test/functional/rpc_bind.py b/test/functional/rpc_bind.py index acc7cd811c..8979251a26 100755 --- a/test/functional/rpc_bind.py +++ b/test/functional/rpc_bind.py @@ -55,7 +55,7 @@ class RPCBindTest(BitcoinTestFramework): self.nodes[0].rpchost = None self.start_nodes([node_args]) # connect to node through non-loopback interface - node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir) + node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, self.chain, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir) node.getnetworkinfo() self.stop_nodes() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index cdf636e200..b621081752 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -41,11 +41,11 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes_bi(self.nodes, 0, 3) def run_test(self): - min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] + self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate for node in self.nodes: - node.settxfee(min_relay_tx_fee) + node.settxfee(self.min_relay_tx_fee) # if the fee's positive delta is higher than this value tests will fail, # neg. delta always fail the tests. @@ -53,13 +53,43 @@ class RawTransactionsTest(BitcoinTestFramework): # than a minimum sized signature. # = 2 bytes * minRelayTxFeePerByte - feeTolerance = 2 * min_relay_tx_fee/1000 + self.fee_tolerance = 2 * self.min_relay_tx_fee / 1000 self.nodes[2].generate(1) self.sync_all() self.nodes[0].generate(121) self.sync_all() + self.test_change_position() + self.test_simple() + self.test_simple_two_coins() + self.test_simple_two_outputs() + self.test_change() + self.test_no_change() + self.test_invalid_option() + self.test_invalid_change_address() + self.test_valid_change_address() + self.test_change_type() + self.test_coin_selection() + self.test_two_vin() + self.test_two_vin_two_vout() + self.test_invalid_input() + self.test_fee_p2pkh() + self.test_fee_p2pkh_multi_out() + self.test_fee_p2sh() + self.test_fee_4of5() + self.test_spend_2of2() + self.test_locked_wallet() + self.test_many_inputs_fee() + self.test_many_inputs_send() + self.test_op_return() + self.test_watchonly() + self.test_all_watched_funds() + self.test_option_feerate() + self.test_address_reuse() + self.test_option_subtract_fee_from_outputs() + + def test_change_position(self): # ensure that setting changePosition in fundraw with an exact match is handled properly rawmatch = self.nodes[2].createrawtransaction([], {self.nodes[2].getnewaddress():50}) rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]}) @@ -67,15 +97,15 @@ class RawTransactionsTest(BitcoinTestFramework): watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"] - watchonly_amount = Decimal(200) + self.watchonly_amount = Decimal(200) self.nodes[3].importpubkey(watchonly_pubkey, "", True) - watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount) + self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.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.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address) + self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) - self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10) + self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), self.watchonly_amount / 10) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) @@ -84,6 +114,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + def test_simple(self): ############### # simple test # ############### @@ -92,10 +123,10 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test that we have enough inputs + def test_simple_two_coins(self): ############################## # simple test with two coins # ############################## @@ -105,25 +136,11 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test if we have enough inputs - - ############################## - # simple test with two coins # - ############################## - inputs = [ ] - outputs = { self.nodes[0].getnewaddress() : 2.6 } - rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) - - rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] - dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - + def test_simple_two_outputs(self): ################################ # simple test with two outputs # ################################ @@ -133,7 +150,6 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: @@ -142,7 +158,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - + def test_change(self): ######################################################################### # test a fundrawtransaction with a VIN greater than the required amount # ######################################################################### @@ -156,6 +172,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] + self.test_no_change_fee = fee # Use the same fee for the next tx dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: @@ -163,14 +180,14 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee - + def test_no_change(self): ##################################################################### # test a fundrawtransaction with which will not get a change output # ##################################################################### utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] - outputs = { self.nodes[0].getnewaddress() : Decimal(5.0) - fee - feeTolerance } + outputs = {self.nodes[0].getnewaddress(): Decimal(5.0) - self.test_no_change_fee - self.fee_tolerance} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) @@ -185,7 +202,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(rawtxfund['changepos'], -1) assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee - + def test_invalid_option(self): #################################################### # test a fundrawtransaction with an invalid option # #################################################### @@ -202,6 +219,7 @@ class RawTransactionsTest(BitcoinTestFramework): # reserveChangeKey was deprecated and is now removed assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawtx, options={'reserveChangeKey': True})) + def test_invalid_change_address(self): ############################################################ # test a fundrawtransaction with an invalid change address # ############################################################ @@ -215,6 +233,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':'foobar'}) + def test_valid_change_address(self): ############################################################ # test a fundrawtransaction with a provided change address # ############################################################ @@ -233,6 +252,7 @@ class RawTransactionsTest(BitcoinTestFramework): out = dec_tx['vout'][0] assert_equal(change, out['scriptPubKey']['addresses'][0]) + def test_change_type(self): ######################################################### # test a fundrawtransaction with a provided change type # ######################################################### @@ -247,6 +267,7 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex']) assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type']) + def test_coin_selection(self): ######################################################################### # test a fundrawtransaction with a VIN smaller than the required amount # ######################################################################### @@ -264,7 +285,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -281,7 +301,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 1) assert_equal(len(dec_tx['vout']), 2) - + def test_two_vin(self): ########################################### # test a fundrawtransaction with two VINs # ########################################### @@ -295,7 +315,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -315,6 +334,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params + def test_two_vin_two_vout(self): ######################################################### # test a fundrawtransaction with two VINs and two vOUTs # ######################################################### @@ -328,7 +348,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -340,16 +359,16 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 2) assert_equal(len(dec_tx['vout']), 3) + def test_invalid_input(self): ############################################## # test a fundrawtransaction with invalid vin # ############################################## inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) + def test_fee_p2pkh(self): ############################################################ #compare fee of a standard pubkeyhash transaction inputs = [] @@ -363,9 +382,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ + def test_fee_p2pkh_multi_out(self): ############################################################ #compare fee of a standard pubkeyhash transaction with multiple outputs inputs = [] @@ -378,10 +398,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_fee_p2sh(self): ############################################################ #compare fee of a 2of2 multisig p2sh transaction @@ -405,10 +425,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_fee_4of5(self): ############################################################ #compare fee of a standard pubkeyhash transaction @@ -438,10 +458,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_spend_2of2(self): ############################################################ # spend a 2of2 multisig transaction over fundraw @@ -456,7 +476,7 @@ class RawTransactionsTest(BitcoinTestFramework): # send 1.2 BTC to msig addr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) + self.nodes[0].sendtoaddress(mSigObj, 1.2) self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -468,7 +488,7 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[2].fundrawtransaction(rawtx) signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[2].sendrawtransaction(signedTx['hex']) + self.nodes[2].sendrawtransaction(signedTx['hex']) self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -476,6 +496,7 @@ class RawTransactionsTest(BitcoinTestFramework): # make sure funds are received at node1 assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance()) + def test_locked_wallet(self): ############################################################ # locked wallet test self.nodes[1].encryptwallet("test") @@ -485,7 +506,7 @@ class RawTransactionsTest(BitcoinTestFramework): # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate for node in self.nodes: - node.settxfee(min_relay_tx_fee) + node.settxfee(self.min_relay_tx_fee) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) @@ -493,7 +514,7 @@ class RawTransactionsTest(BitcoinTestFramework): 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.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) self.sync_all() # drain the keypool @@ -523,14 +544,14 @@ class RawTransactionsTest(BitcoinTestFramework): #now we need to unlock self.nodes[1].walletpassphrase("test", 600) signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[1].sendrawtransaction(signedTx['hex']) + self.nodes[1].sendrawtransaction(signedTx['hex']) self.nodes[1].generate(1) self.sync_all() # make sure funds are received at node1 assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) - + def test_many_inputs_fee(self): ############################################### # multiple (~19) inputs tx test | Compare fee # ############################################### @@ -558,9 +579,9 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance*19 #~19 inputs - + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance * 19 #~19 inputs + def test_many_inputs_send(self): ############################################# # multiple (~19) inputs tx test | sign/send # ############################################# @@ -584,12 +605,13 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawtx) fundedAndSignedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) + self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) self.sync_all() self.nodes[0].generate(1) self.sync_all() assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward + def test_op_return(self): ##################################################### # test fundrawtransaction with OP_RETURN and no vin # ##################################################### @@ -606,40 +628,41 @@ class RawTransactionsTest(BitcoinTestFramework): assert_greater_than(len(dec_tx['vin']), 0) # at least one vin assert_equal(len(dec_tx['vout']), 2) # one change output added - + def test_watchonly(self): ################################################## # test a fundrawtransaction using only watchonly # ################################################## inputs = [] - outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2} + outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True }) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) - assert_equal(res_dec["vin"][0]["txid"], watchonly_txid) + assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid) assert "fee" in result.keys() assert_greater_than(result["changepos"], -1) + def test_all_watched_funds(self): ############################################################### # test fundrawtransaction using the entirety of watched funds # ############################################################### inputs = [] - outputs = {self.nodes[2].getnewaddress() : watchonly_amount} + outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) # Backward compatibility test (2nd param is includeWatching) result = self.nodes[3].fundrawtransaction(rawtx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 2) - assert res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid + assert res_dec["vin"][0]["txid"] == self.watchonly_txid or res_dec["vin"][1]["txid"] == self.watchonly_txid assert_greater_than(result["fee"], 0) assert_greater_than(result["changepos"], -1) - assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10) + assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], self.watchonly_amount / 10) signedtx = self.nodes[3].signrawtransactionwithwallet(result["hex"]) assert not signedtx["complete"] @@ -649,6 +672,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + def test_option_feerate(self): ####################### # Test feeRate option # ####################### @@ -659,18 +683,20 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {self.nodes[3].getnewaddress() : 1} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee) - result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee}) - result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10*min_relay_tx_fee}) + result = self.nodes[3].fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee) + result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}) + result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee}) assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1}) result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex']) assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate) assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate) + def test_address_reuse(self): ################################ # Test no address reuse occurs # ################################ + rawtx = self.nodes[3].createrawtransaction(inputs=[], outputs={self.nodes[3].getnewaddress(): 1}) result3 = self.nodes[3].fundrawtransaction(rawtx) res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) changeaddress = "" @@ -682,6 +708,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Now the change address key should be removed from the keypool assert changeaddress != nextaddr + def test_option_subtract_fee_from_outputs(self): ###################################### # Test subtractFeeFromOutputs option # ###################################### @@ -693,11 +720,11 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = {self.nodes[2].getnewaddress(): 1} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = [self.nodes[3].fundrawtransaction(rawtx), # uses min_relay_tx_fee (set by settxfee) - self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list - self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses min_relay_tx_fee (set by settxfee) - self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee}), - self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee, "subtractFeeFromOutputs": [0]})] + result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee) + self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list + self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee) + self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}), + self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),] dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result] output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)] diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index b3d8696208..5a04e0c8d8 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -27,6 +27,11 @@ class PSBTTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 + self.extra_args = [ + ["-walletrbf=1"], + ["-walletrbf=0"], + [] + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -207,18 +212,18 @@ class PSBTTest(BitcoinTestFramework): # replaceable arg block_height = self.nodes[0].getblockcount() unspent = self.nodes[0].listunspent()[0] - psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":True}, False) + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False}, False) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): - assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) + assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert "bip32_derivs" not in psbt_in assert_equal(decoded_psbt["tx"]["locktime"], block_height+2) - # Same construction with only locktime set - psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {}, True) + # Same construction with only locktime set and RBF explicitly enabled + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True}, True) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): - assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE + assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert "bip32_derivs" in psbt_in assert_equal(decoded_psbt["tx"]["locktime"], block_height) @@ -226,9 +231,16 @@ class PSBTTest(BitcoinTestFramework): psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in in decoded_psbt["tx"]["vin"]: - assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE + assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_equal(decoded_psbt["tx"]["locktime"], 0) + # Same construction without optional arguments, for a node with -walletrbf=0 + unspent1 = self.nodes[1].listunspent()[0] + psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height) + decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"]) + for tx_in in decoded_psbt["tx"]["vin"]: + assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) + # Make sure change address wallet does not have P2SH innerscript access to results in success # when attempting BnB coin selection self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 7ac044d0d0..d741b00ba0 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -22,13 +22,14 @@ from .messages import ( ToHex, hash256, hex_str_to_bytes, - ser_string, ser_uint256, sha256, uint256_from_str, ) from .script import ( CScript, + CScriptNum, + CScriptOp, OP_0, OP_1, OP_CHECKMULTISIG, @@ -89,20 +90,14 @@ def add_witness_commitment(block, nonce=0): block.hashMerkleRoot = block.calc_merkle_root() block.rehash() -def serialize_script_num(value): - r = bytearray(0) - if value == 0: - return r - neg = value < 0 - absvalue = -value if neg else value - while (absvalue): - r.append(int(absvalue & 0xff)) - absvalue >>= 8 - if r[-1] & 0x80: - r.append(0x80 if neg else 0) - elif neg: - r[-1] |= 0x80 - return r + +def script_BIP34_coinbase_height(height): + if height <= 16: + res = CScriptOp.encode_op_n(height) + # Append dummy to increase scriptSig size above 2 (see bad-cb-length consensus rule) + return CScript([res, OP_1]) + return CScript([CScriptNum(height)]) + def create_coinbase(height, pubkey=None): """Create a coinbase transaction, assuming no miner fees. @@ -110,8 +105,7 @@ def create_coinbase(height, pubkey=None): If pubkey is passed in, the coinbase output will be a P2PK output; otherwise an anyone-can-spend output.""" coinbase = CTransaction() - coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), - ser_string(serialize_script_num(height)), 0xffffffff)) + coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 50 * COIN halvings = int(height / 150) # regtest diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index cc3a4cc72a..779863df79 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -363,6 +363,7 @@ class P2PInterface(P2PConnection): def wait_for_tx(self, txid, timeout=60): def test_function(): + assert self.is_connected if not self.last_message.get('tx'): return False return self.last_message['tx'].tx.rehash() == txid @@ -370,11 +371,15 @@ class P2PInterface(P2PConnection): wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_block(self, blockhash, timeout=60): - test_function = lambda: self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash + def test_function(): + assert self.is_connected + return self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_header(self, blockhash, timeout=60): def test_function(): + assert self.is_connected last_headers = self.last_message.get('headers') if not last_headers: return False @@ -389,7 +394,11 @@ class P2PInterface(P2PConnection): value must be explicitly cleared before calling this method, or this will return immediately with success. TODO: change this method to take a hash value and only return true if the correct block/tx has been requested.""" - test_function = lambda: self.last_message.get("getdata") + + def test_function(): + assert self.is_connected + return self.last_message.get("getdata") + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_getheaders(self, timeout=60): @@ -399,20 +408,30 @@ class P2PInterface(P2PConnection): value must be explicitly cleared before calling this method, or this will return immediately with success. TODO: change this method to take a hash value and only return true if the correct block header has been requested.""" - test_function = lambda: self.last_message.get("getheaders") + + def test_function(): + assert self.is_connected + return self.last_message.get("getheaders") + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_inv(self, expected_inv, timeout=60): """Waits for an INV message and checks that the first inv object in the message was as expected.""" if len(expected_inv) > 1: raise NotImplementedError("wait_for_inv() will only verify the first inv object") - test_function = lambda: self.last_message.get("inv") and \ + + def test_function(): + assert self.is_connected + return self.last_message.get("inv") and \ self.last_message["inv"].inv[0].type == expected_inv[0].type and \ self.last_message["inv"].inv[0].hash == expected_inv[0].hash + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_verack(self, timeout=60): - test_function = lambda: self.message_count["verack"] + def test_function(): + return self.message_count["verack"] + wait_until(test_function, timeout=timeout, lock=mininode_lock) # Message sending helper functions @@ -424,7 +443,11 @@ class P2PInterface(P2PConnection): # Sync up with the node def sync_with_ping(self, timeout=60): self.send_message(msg_ping(nonce=self.ping_counter)) - test_function = lambda: self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter + + def test_function(): + assert self.is_connected + return self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter + wait_until(test_function, timeout=timeout, lock=mininode_lock) self.ping_counter += 1 diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index aa674f9ebe..9aff08fdc7 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -91,6 +91,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def __init__(self): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" + self.chain = 'regtest' self.setup_clean_chain = False self.nodes = [] self.network_thread = None @@ -192,18 +193,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.setup_network() self.run_test() success = TestStatus.PASSED - except JSONRPCException as e: + except JSONRPCException: self.log.exception("JSONRPC error") except SkipTest as e: self.log.warning("Test Skipped: %s" % e.message) success = TestStatus.SKIPPED - except AssertionError as e: + except AssertionError: self.log.exception("Assertion failed") - except KeyError as e: + except KeyError: self.log.exception("Key error") - except Exception as e: + except Exception: self.log.exception("Unexpected exception caught during testing") - except KeyboardInterrupt as e: + except KeyboardInterrupt: self.log.warning("Exiting after keyboard interrupt") if success == TestStatus.FAILED and self.options.pdbonfailure: @@ -342,6 +343,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.nodes.append(TestNode( i, get_datadir_path(self.options.tmpdir, i), + chain=self.chain, rpchost=rpchost, timewait=self.rpc_timeout, bitcoind=binary[i], @@ -477,11 +479,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if not os.path.isdir(cache_node_dir): self.log.debug("Creating cache directory {}".format(cache_node_dir)) - initialize_datadir(self.options.cachedir, CACHE_NODE_ID) + initialize_datadir(self.options.cachedir, CACHE_NODE_ID, self.chain) self.nodes.append( TestNode( CACHE_NODE_ID, cache_node_dir, + chain=self.chain, extra_conf=["bind=127.0.0.1"], extra_args=['-disablewallet'], rpchost=None, @@ -515,7 +518,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.nodes = [] def cache_path(*paths): - return os.path.join(cache_node_dir, "regtest", *paths) + return os.path.join(cache_node_dir, self.chain, *paths) os.rmdir(cache_path('wallets')) # Remove empty wallets dir for entry in os.listdir(cache_path()): @@ -526,7 +529,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.log.debug("Copy cache directory {} to node {}".format(cache_node_dir, i)) to_dir = get_datadir_path(self.options.tmpdir, i) shutil.copytree(cache_node_dir, to_dir) - initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf + initialize_datadir(self.options.tmpdir, i, self.chain) # Overwrite port/rpcport in bitcoin.conf def _initialize_chain_clean(self): """Initialize empty blockchain for use by the test. @@ -534,7 +537,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): Create an empty blockchain and num_nodes wallets. Useful if a test case wants complete control over initialization.""" for i in range(self.num_nodes): - initialize_datadir(self.options.tmpdir, i) + initialize_datadir(self.options.tmpdir, i, self.chain) def skip_if_no_py3_zmq(self): """Attempt to import the zmq package and skip the test if the import fails.""" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 3311377090..df027397d2 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -59,7 +59,7 @@ 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, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False): + def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -68,8 +68,10 @@ class TestNode(): self.index = i self.datadir = datadir + self.bitcoinconf = os.path.join(self.datadir, "bitcoin.conf") self.stdout_dir = os.path.join(self.datadir, "stdout") self.stderr_dir = os.path.join(self.datadir, "stderr") + self.chain = chain self.rpchost = rpchost self.rpc_timeout = timewait self.binary = bitcoind @@ -197,7 +199,7 @@ class TestNode(): # 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) + delete_cookie_file(self.datadir, self.chain) # 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") @@ -219,7 +221,7 @@ class TestNode(): raise FailedToStartError(self._node_msg( 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) try: - rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) + rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.chain, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) rpc.getblockcount() # If the call to getblockcount() succeeds then the RPC connection is up self.log.debug("RPC successfully started") @@ -306,7 +308,7 @@ class TestNode(): @contextlib.contextmanager def assert_debug_log(self, expected_msgs): - debug_log = os.path.join(self.datadir, 'regtest', 'debug.log') + debug_log = os.path.join(self.datadir, self.chain, 'debug.log') with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) prev_size = dl.tell() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index efd962ea93..7beddc6407 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -7,7 +7,6 @@ from base64 import b64encode from binascii import unhexlify from decimal import Decimal, ROUND_DOWN -import hashlib import inspect import json import logging @@ -183,12 +182,6 @@ def check_json_precision(): def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) -def hash256(byte_str): - sha256 = hashlib.sha256() - sha256.update(byte_str) - sha256d = hashlib.sha256() - sha256d.update(sha256.digest()) - return sha256d.digest()[::-1] def hex_str_to_bytes(hex_str): return unhexlify(hex_str.encode('ascii')) @@ -271,8 +264,8 @@ def p2p_port(n): def rpc_port(n): return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) -def rpc_url(datadir, i, rpchost=None): - rpc_u, rpc_p = get_auth_cookie(datadir) +def rpc_url(datadir, i, chain, rpchost): + rpc_u, rpc_p = get_auth_cookie(datadir, chain) host = '127.0.0.1' port = rpc_port(i) if rpchost: @@ -286,13 +279,13 @@ def rpc_url(datadir, i, rpchost=None): # Node functions ################ -def initialize_datadir(dirname, n): +def initialize_datadir(dirname, n, chain): datadir = get_datadir_path(dirname, n) if not os.path.isdir(datadir): os.makedirs(datadir) with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f: - f.write("regtest=1\n") - f.write("[regtest]\n") + f.write("{}=1\n".format(chain)) + f.write("[{}]\n".format(chain)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") f.write("server=1\n") @@ -312,7 +305,7 @@ def append_config(datadir, options): for option in options: f.write(option + "\n") -def get_auth_cookie(datadir): +def get_auth_cookie(datadir, chain): user = None password = None if os.path.isfile(os.path.join(datadir, "bitcoin.conf")): @@ -325,7 +318,7 @@ def get_auth_cookie(datadir): assert password is None # Ensure that there is only one rpcpassword line password = line.split("=")[1].strip("\n") try: - with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f: + with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f: userpass = f.read() split_userpass = userpass.split(':') user = split_userpass[0] @@ -337,10 +330,10 @@ def get_auth_cookie(datadir): return user, password # If a cookie file exists in the given datadir, delete it. -def delete_cookie_file(datadir): - if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")): +def delete_cookie_file(datadir, chain): + if os.path.isfile(os.path.join(datadir, chain, ".cookie")): logger.debug("Deleting leftover cookie file") - os.remove(os.path.join(datadir, "regtest", ".cookie")) + os.remove(os.path.join(datadir, chain, ".cookie")) def get_bip9_status(node, key): info = node.getblockchaininfo() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 0db8382733..ea8dbbaf95 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -76,7 +76,6 @@ EXTENDED_SCRIPTS = [ BASE_SCRIPTS = [ # Scripts that are run by default. # Longest test should go first, to favor running tests in parallel - 'feature_fee_estimation.py', 'wallet_hd.py', 'wallet_backup.py', # vv Tests less than 5m vv @@ -91,6 +90,7 @@ BASE_SCRIPTS = [ 'wallet_labels.py', 'p2p_segwit.py', 'p2p_timeouts.py', + 'p2p_tx_download.py', 'wallet_dump.py', 'wallet_listtransactions.py', # vv Tests less than 60s vv @@ -110,6 +110,7 @@ BASE_SCRIPTS = [ 'feature_abortnode.py', # vv Tests less than 30s vv 'wallet_keypool_topup.py', + 'feature_fee_estimation.py', 'interface_zmq.py', 'interface_bitcoin_cli.py', 'mempool_resurrect.py', @@ -175,6 +176,7 @@ BASE_SCRIPTS = [ 'rpc_bind.py --nonloopback', 'mining_basic.py', 'wallet_bumpfee.py', + 'wallet_bumpfee_totalfee_deprecation.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'p2p_leak.py', @@ -198,6 +200,7 @@ BASE_SCRIPTS = [ 'rpc_scantxoutset.py', 'feature_logging.py', 'p2p_node_network_limited.py', + 'p2p_permissions.py', 'feature_blocksdir.py', 'feature_config_args.py', 'rpc_help.py', @@ -225,6 +228,7 @@ def main(): epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('--ansi', action='store_true', default=sys.stdout.isatty(), help="Use ANSI colors and dots in output (enabled by default when standard output is a TTY)") parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.') parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment') @@ -237,7 +241,14 @@ def main(): parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs") parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure') parser.add_argument('--filter', help='filter scripts to run by regular expression') + args, unknown_args = parser.parse_known_args() + if not args.ansi: + global BOLD, GREEN, RED, GREY + BOLD = ("", "") + GREEN = ("", "") + RED = ("", "") + GREY = ("", "") # args to be passed on always start with two dashes; tests are the remaining unknown args tests = [arg for arg in unknown_args if arg[:2] != "--"] @@ -339,9 +350,10 @@ def main(): combined_logs_len=args.combinedlogslen, failfast=args.failfast, runs_ci=args.ci, + use_term_control=args.ansi, ) -def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci, use_term_control): args = args or [] # Warn if bitcoind is already running (unix only) @@ -383,6 +395,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= test_list=test_list, flags=flags, timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds + use_term_control=use_term_control, ) start_time = time.time() test_results = [] @@ -466,7 +479,7 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration): + def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration, use_term_control): assert num_tests_parallel >= 1 self.num_jobs = num_tests_parallel self.tests_dir = tests_dir @@ -476,6 +489,7 @@ class TestHandler: self.flags = flags self.num_running = 0 self.jobs = [] + self.use_term_control = use_term_control def get_next(self): while self.num_running < self.num_jobs and self.test_list: @@ -527,11 +541,13 @@ class TestHandler: status = "Failed" self.num_running -= 1 self.jobs.remove(job) - clearline = '\r' + (' ' * dot_count) + '\r' - print(clearline, end='', flush=True) + if self.use_term_control: + clearline = '\r' + (' ' * dot_count) + '\r' + print(clearline, end='', flush=True) dot_count = 0 return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr - print('.', end='', flush=True) + if self.use_term_control: + print('.', end='', flush=True) dot_count += 1 def kill_and_join(self): diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 030eb50791..bfc01e3f5e 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -37,6 +37,7 @@ class BumpFeeTest(BitcoinTestFramework): self.extra_args = [[ "-walletrbf={}".format(i), "-mintxfee=0.00002", + "-deprecatedrpc=totalFee", ] for i in range(self.num_nodes)] def skip_test_if_missing_module(self): diff --git a/test/functional/wallet_bumpfee_totalfee_deprecation.py b/test/functional/wallet_bumpfee_totalfee_deprecation.py new file mode 100755 index 0000000000..b8e097c32e --- /dev/null +++ b/test/functional/wallet_bumpfee_totalfee_deprecation.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test deprecation of passing `totalFee` to the bumpfee RPC.""" +from decimal import Decimal + +from test_framework.messages import BIP125_SEQUENCE_NUMBER +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + +class BumpFeeWithTotalFeeArgumentDeprecationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [[ + "-walletrbf={}".format(i), + "-mintxfee=0.00002", + ] for i in range(self.num_nodes)] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + peer_node, rbf_node = self.nodes + peer_node.generate(110) + self.sync_all() + peer_node.sendtoaddress(rbf_node.getnewaddress(), 0.001) + self.sync_all() + peer_node.generate(1) + self.sync_all() + rbfid = spend_one_input(rbf_node, peer_node.getnewaddress()) + + self.log.info("Testing bumpfee with totalFee argument raises RPC error with deprecation message") + assert_raises_rpc_error( + -8, + "totalFee argument has been deprecated and will be removed in 0.20. " + + "Please use -deprecatedrpc=totalFee to continue using this argument until removal.", + rbf_node.bumpfee, rbfid, {"totalFee": 2000}) + + self.log.info("Testing bumpfee without totalFee argument does not raise") + rbf_node.bumpfee(rbfid) + +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): + tx_input = dict(sequence=BIP125_SEQUENCE_NUMBER, + **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) + destinations = {dest_address: Decimal("0.00050000")} + destinations[node.getrawchangeaddress()] = change_size + rawtx = node.createrawtransaction([tx_input], destinations) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx["hex"]) + return txid + +if __name__ == "__main__": + BumpFeeWithTotalFeeArgumentDeprecationTest().main() diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index c17949a2f6..e302e499f4 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -116,8 +116,20 @@ class CreateWalletTest(BitcoinTestFramework): walletinfo = w6.getwalletinfo() assert_equal(walletinfo['keypoolsize'], 1) assert_equal(walletinfo['keypoolsize_hd_internal'], 1) - # Empty passphrase, error - assert_raises_rpc_error(-16, 'Cannot encrypt a wallet with a blank password', self.nodes[0].createwallet, 'w7', False, False, '') + # Allow empty passphrase, but there should be a warning + resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='') + assert_equal(resp['warning'], 'Empty string given as passphrase, wallet will not be encrypted.') + w7 = node.get_wallet_rpc('w7') + assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 10) + + self.log.info('Test making a wallet with avoid reuse flag') + self.nodes[0].createwallet('w8', False, False, '', True) # Use positional arguments to check for bug where avoid_reuse could not be set for wallets without needing them to be encrypted + w8 = node.get_wallet_rpc('w8') + assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 10) + assert_equal(w8.getwalletinfo()["avoid_reuse"], True) + + self.log.info('Using a passphrase with private keys disabled returns error') + assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w9', disable_private_keys=True, passphrase='thisisapassphrase') if __name__ == '__main__': CreateWalletTest().main() |