aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml24
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--build-aux/m4/bitcoin_qt.m42
-rwxr-xr-xci/lint/06_script.sh13
-rwxr-xr-xci/test/00_setup_env_win64.sh4
-rw-r--r--configure.ac8
-rw-r--r--contrib/builder-keys/README.md2
-rw-r--r--contrib/builder-keys/keys.txt3
-rwxr-xr-xcontrib/devtools/test-symbol-check.py9
-rwxr-xr-xcontrib/guix/guix-attest28
-rwxr-xr-xcontrib/guix/guix-verify6
-rw-r--r--contrib/guix/libexec/prelude.bash2
-rwxr-xr-xcontrib/linearize/linearize-data.py3
-rwxr-xr-xcontrib/signet/getcoins.py52
-rwxr-xr-xcontrib/signet/miner5
-rw-r--r--contrib/verify-commits/README.md2
-rwxr-xr-xcontrib/zmq/zmq_sub.py11
-rw-r--r--depends/packages/bdb.mk2
-rw-r--r--depends/packages/boost.mk2
-rw-r--r--depends/packages/zeromq.mk2
-rw-r--r--doc/README.md1
-rw-r--r--doc/descriptors.md2
-rw-r--r--doc/i2p.md52
-rw-r--r--doc/managing-wallets.md125
-rw-r--r--doc/release-notes.md6
-rw-r--r--doc/release-process.md36
-rw-r--r--doc/tor.md10
-rw-r--r--doc/tracing.md2
-rwxr-xr-xshare/rpcauth/rpcauth.py3
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/addrdb.cpp7
-rw-r--r--src/addrdb.h33
-rw-r--r--src/addrman.cpp343
-rw-r--r--src/addrman.h408
-rw-r--r--src/bench/addrman.cpp18
-rw-r--r--src/bitcoin-cli-res.rc6
-rw-r--r--src/bitcoin-cli.cpp35
-rw-r--r--src/bitcoin-tx-res.rc6
-rw-r--r--src/bitcoin-tx.cpp7
-rw-r--r--src/bitcoin-util-res.rc6
-rw-r--r--src/bitcoin-wallet-res.rc6
-rw-r--r--src/bitcoind-res.rc6
-rw-r--r--src/chainparams.cpp4
-rw-r--r--src/clientversion.cpp9
-rw-r--r--src/clientversion.h1
-rw-r--r--src/crypto/chacha_poly_aead.cpp5
-rw-r--r--src/dummywallet.cpp1
-rw-r--r--src/fs.cpp12
-rw-r--r--src/init.cpp51
-rw-r--r--src/interfaces/chain.h16
-rw-r--r--src/interfaces/wallet.h5
-rw-r--r--src/key.cpp1
-rw-r--r--src/key.h13
-rw-r--r--src/miner.cpp8
-rw-r--r--src/net.cpp76
-rw-r--r--src/net.h16
-rw-r--r--src/net_processing.cpp56
-rw-r--r--src/node/interfaces.cpp13
-rw-r--r--src/policy/rbf.cpp35
-rw-r--r--src/policy/rbf.h19
-rw-r--r--src/protocol.h13
-rw-r--r--src/pubkey.cpp18
-rw-r--r--src/pubkey.h5
-rw-r--r--src/qt/bitcoin.cpp64
-rw-r--r--src/qt/bitcoingui.cpp18
-rw-r--r--src/qt/clientmodel.cpp4
-rw-r--r--src/qt/createwalletdialog.cpp5
-rw-r--r--src/qt/createwalletdialog.h1
-rw-r--r--src/qt/forms/optionsdialog.ui80
-rw-r--r--src/qt/guiutil.cpp2
-rw-r--r--src/qt/locale/bitcoin_en.ts8
-rw-r--r--src/qt/optionsdialog.cpp1
-rw-r--r--src/qt/optionsmodel.cpp11
-rw-r--r--src/qt/optionsmodel.h3
-rw-r--r--src/qt/peertablemodel.cpp10
-rw-r--r--src/qt/peertablemodel.h4
-rw-r--r--src/qt/peertablesortproxy.cpp4
-rw-r--r--src/qt/psbtoperationsdialog.cpp33
-rw-r--r--src/qt/recentrequeststablemodel.cpp2
-rw-r--r--src/qt/recentrequeststablemodel.h6
-rw-r--r--src/qt/rpcconsole.cpp11
-rw-r--r--src/qt/sendcoinsentry.cpp4
-rw-r--r--src/qt/test/addressbooktests.cpp7
-rw-r--r--src/qt/test/wallettests.cpp7
-rw-r--r--src/qt/transactionfilterproxy.cpp19
-rw-r--r--src/qt/transactionfilterproxy.h13
-rw-r--r--src/qt/transactiontablemodel.cpp4
-rw-r--r--src/qt/transactionview.cpp14
-rw-r--r--src/qt/walletframe.cpp45
-rw-r--r--src/qt/walletframe.h2
-rw-r--r--src/qt/walletview.cpp53
-rw-r--r--src/qt/walletview.h7
-rw-r--r--src/qt/winshutdownmonitor.h2
-rw-r--r--src/rpc/blockchain.cpp3
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/rpc/net.cpp6
-rw-r--r--src/rpc/rawtransaction.cpp2
-rw-r--r--src/rpc/rawtransaction_util.cpp9
-rw-r--r--src/rpc/rawtransaction_util.h3
-rw-r--r--src/script/descriptor.cpp8
-rw-r--r--src/script/interpreter.cpp4
-rw-r--r--src/script/interpreter.h7
-rw-r--r--src/script/sign.cpp45
-rw-r--r--src/script/sign.h7
-rw-r--r--src/script/signingprovider.h24
-rw-r--r--src/script/standard.cpp1
-rw-r--r--src/script/standard.h9
-rw-r--r--src/test/addrman_tests.cpp419
-rw-r--r--src/test/bip32_tests.cpp34
-rw-r--r--src/test/crypto_tests.cpp4
-rw-r--r--src/test/fuzz/addrdb.cpp37
-rw-r--r--src/test/fuzz/addrman.cpp240
-rw-r--r--src/test/fuzz/banman.cpp14
-rw-r--r--src/test/fuzz/blockfilter.cpp5
-rw-r--r--src/test/fuzz/connman.cpp2
-rw-r--r--src/test/fuzz/crypto.cpp7
-rw-r--r--src/test/fuzz/data_stream.cpp2
-rw-r--r--src/test/fuzz/deserialize.cpp6
-rw-r--r--src/test/fuzz/fuzz.h7
-rw-r--r--src/test/fuzz/integer.cpp10
-rw-r--r--src/test/fuzz/net.cpp4
-rw-r--r--src/test/fuzz/parse_numbers.cpp3
-rw-r--r--src/test/fuzz/prevector.cpp7
-rw-r--r--src/test/fuzz/rolling_bloom_filter.cpp7
-rw-r--r--src/test/fuzz/script_sign.cpp3
-rw-r--r--src/test/fuzz/system.cpp3
-rw-r--r--src/test/fuzz/tx_pool.cpp14
-rw-r--r--src/test/net_tests.cpp180
-rw-r--r--src/test/script_tests.cpp2
-rw-r--r--src/test/serfloat_tests.cpp5
-rw-r--r--src/test/transaction_tests.cpp2
-rw-r--r--src/test/util/setup_common.cpp4
-rw-r--r--src/test/util/wallet.cpp3
-rw-r--r--src/test/util_tests.cpp113
-rw-r--r--src/txmempool.cpp116
-rw-r--r--src/txmempool.h42
-rw-r--r--src/util/hasher.h4
-rw-r--r--src/util/moneystr.cpp29
-rw-r--r--src/util/moneystr.h3
-rw-r--r--src/util/rbf.h11
-rw-r--r--src/util/string.h8
-rw-r--r--src/util/system.cpp9
-rw-r--r--src/util/system.h2
-rw-r--r--src/util/translation.h6
-rw-r--r--src/validation.cpp89
-rw-r--r--src/validation.h11
-rw-r--r--src/wallet/coinselection.cpp31
-rw-r--r--src/wallet/coinselection.h15
-rw-r--r--src/wallet/context.h16
-rw-r--r--src/wallet/init.cpp1
-rw-r--r--src/wallet/interfaces.cpp33
-rw-r--r--src/wallet/load.cpp63
-rw-r--r--src/wallet/load.h13
-rw-r--r--src/wallet/rpcdump.cpp20
-rw-r--r--src/wallet/rpcwallet.cpp147
-rw-r--r--src/wallet/scriptpubkeyman.cpp37
-rw-r--r--src/wallet/scriptpubkeyman.h31
-rw-r--r--src/wallet/spend.cpp49
-rw-r--r--src/wallet/test/coinselector_tests.cpp153
-rw-r--r--src/wallet/test/db_tests.cpp4
-rw-r--r--src/wallet/test/init_test_fixture.cpp4
-rw-r--r--src/wallet/test/wallet_tests.cpp58
-rw-r--r--src/wallet/wallet.cpp252
-rw-r--r--src/wallet/wallet.h38
-rw-r--r--src/wallet/walletdb.cpp4
-rw-r--r--src/wallet/walletdb.h3
-rwxr-xr-xtest/functional/feature_asmap.py14
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py4
-rwxr-xr-xtest/functional/feature_blocksdir.py4
-rwxr-xr-xtest/functional/feature_cltv.py15
-rwxr-xr-xtest/functional/feature_config_args.py30
-rwxr-xr-xtest/functional/feature_csv_activation.py2
-rwxr-xr-xtest/functional/feature_dbcrash.py37
-rwxr-xr-xtest/functional/feature_dersig.py11
-rwxr-xr-xtest/functional/feature_fee_estimation.py11
-rwxr-xr-xtest/functional/feature_filelock.py8
-rwxr-xr-xtest/functional/feature_help.py6
-rwxr-xr-xtest/functional/feature_loadblock.py24
-rwxr-xr-xtest/functional/feature_logging.py12
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py4
-rwxr-xr-xtest/functional/feature_minchainwork.py10
-rwxr-xr-xtest/functional/feature_notifications.py12
-rwxr-xr-xtest/functional/feature_nulldummy.py25
-rwxr-xr-xtest/functional/feature_proxy.py28
-rwxr-xr-xtest/functional/feature_pruning.py45
-rwxr-xr-xtest/functional/feature_rbf.py109
-rwxr-xr-xtest/functional/feature_segwit.py2
-rwxr-xr-xtest/functional/feature_settings.py2
-rwxr-xr-xtest/functional/feature_versionbits_warning.py4
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py16
-rwxr-xr-xtest/functional/interface_http.py20
-rwxr-xr-xtest/functional/interface_rest.py67
-rwxr-xr-xtest/functional/interface_rpc.py2
-rwxr-xr-xtest/functional/interface_zmq.py14
-rwxr-xr-xtest/functional/mempool_accept_wtxid.py2
-rwxr-xr-xtest/functional/mempool_compatibility.py3
-rwxr-xr-xtest/functional/mempool_limit.py2
-rwxr-xr-xtest/functional/mempool_package_limits.py475
-rwxr-xr-xtest/functional/mempool_package_onemore.py4
-rwxr-xr-xtest/functional/mempool_packages.py63
-rwxr-xr-xtest/functional/mempool_unbroadcast.py6
-rwxr-xr-xtest/functional/mempool_updatefromblock.py11
-rwxr-xr-xtest/functional/mining_basic.py4
-rwxr-xr-xtest/functional/mining_getblocktemplate_longpoll.py6
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py2
-rwxr-xr-xtest/functional/p2p_addr_relay.py22
-rwxr-xr-xtest/functional/p2p_addrfetch.py33
-rwxr-xr-xtest/functional/p2p_invalid_tx.py6
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py2
-rwxr-xr-xtest/functional/rpc_blockchain.py6
-rwxr-xr-xtest/functional/rpc_createmultisig.py5
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py69
-rwxr-xr-xtest/functional/rpc_packages.py59
-rwxr-xr-xtest/functional/rpc_rawtransaction.py614
-rwxr-xr-xtest/functional/rpc_signmessagewithprivkey.py (renamed from test/functional/rpc_signmessage.py)27
-rwxr-xr-xtest/functional/rpc_signrawtransaction.py6
-rw-r--r--test/functional/test_framework/bdb.py8
-rw-r--r--test/functional/test_framework/blocktools.py6
-rwxr-xr-xtest/functional/test_framework/messages.py3
-rw-r--r--test/functional/test_framework/netutil.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py21
-rw-r--r--test/functional/test_framework/util.py14
-rw-r--r--test/functional/test_framework/wallet.py77
-rwxr-xr-xtest/functional/test_runner.py4
-rwxr-xr-xtest/functional/wallet_backup.py45
-rwxr-xr-xtest/functional/wallet_descriptor.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py4
-rwxr-xr-xtest/functional/wallet_listdescriptors.py28
-rwxr-xr-xtest/functional/wallet_signmessagewithaddress.py45
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh1
-rwxr-xr-xtest/util/bitcoin-util-test.py3
231 files changed, 4300 insertions, 2624 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 26bd27754f..4a7c4eaf55 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -21,16 +21,17 @@ persistent_worker_template: &PERSISTENT_WORKER_TEMPLATE
base_template: &BASE_TEMPLATE
skip: $CIRRUS_REPO_FULL_NAME == "bitcoin-core/gui" && $CIRRUS_PR == "" # No need to run on the read-only mirror, unless it is a PR. https://cirrus-ci.org/guide/writing-tasks/#conditional-task-execution
merge_base_script:
- - if [ "$CIRRUS_PR" = "" ]; then exit 0; fi
+ # Unconditionally install git (used in fingerprint_script) and set the
+ # default git author name (used in verify-commits.py)
- bash -c "$PACKAGE_MANAGER_INSTALL git"
- - git fetch $CIRRUS_REPO_CLONE_URL $CIRRUS_BASE_BRANCH
- git config --global user.email "ci@ci.ci"
- git config --global user.name "ci"
+ - if [ "$CIRRUS_PR" = "" ]; then exit 0; fi
+ - git fetch $CIRRUS_REPO_CLONE_URL $CIRRUS_BASE_BRANCH
- git merge FETCH_HEAD # Merge base to detect silent merge conflicts
stateful: false # https://cirrus-ci.org/guide/writing-tasks/#stateful-tasks
-global_task_template: &GLOBAL_TASK_TEMPLATE
- << : *BASE_TEMPLATE
+main_template: &MAIN_TEMPLATE
timeout_in: 120m # https://cirrus-ci.org/faq/#instance-timed-out
container:
# https://cirrus-ci.org/faq/#are-there-any-limits
@@ -41,9 +42,14 @@ global_task_template: &GLOBAL_TASK_TEMPLATE
folder: "/tmp/ccache_dir"
depends_built_cache:
folder: "depends/built"
+ fingerprint_script: echo $CIRRUS_TASK_NAME $(git rev-list -1 HEAD ./depends)
ci_script:
- ./ci/test_run_all.sh
+global_task_template: &GLOBAL_TASK_TEMPLATE
+ << : *BASE_TEMPLATE
+ << : *MAIN_TEMPLATE
+
depends_sdk_cache_template: &DEPENDS_SDK_CACHE_TEMPLATE
depends_sdk_cache:
folder: "depends/sdk-sources"
@@ -86,11 +92,14 @@ task:
task:
name: 'ARM [unit tests, no functional tests] [buster]'
<< : *GLOBAL_TASK_TEMPLATE
- container:
+ arm_container:
image: debian:buster
+ cpu: 2
+ memory: 8G
env:
<< : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
FILE_ENV: "./ci/test/00_setup_env_arm.sh"
+ QEMU_USER_CMD: "" # Disable qemu and run the test natively
task:
name: 'Win64 [unit tests, no gui tests, no boost::process, no functional tests] [focal]'
@@ -153,6 +162,7 @@ task:
task:
name: '[no depends, sanitizers: fuzzer,address,undefined,integer] [focal]'
+ only_if: $CIRRUS_BRANCH == $CIRRUS_DEFAULT_BRANCH || $CIRRUS_BASE_BRANCH == $CIRRUS_DEFAULT_BRANCH
<< : *GLOBAL_TASK_TEMPLATE
container:
image: ubuntu:focal
@@ -211,9 +221,11 @@ task:
task:
name: 'ARM64 Android APK [focal]'
<< : *DEPENDS_SDK_CACHE_TEMPLATE
+ << : *BASE_TEMPLATE
depends_sources_cache:
folder: "depends/sources"
- << : *GLOBAL_TASK_TEMPLATE
+ fingerprint_script: git rev-list -1 HEAD ./depends
+ << : *MAIN_TEMPLATE
container:
image: ubuntu:focal
env:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5cd4715ef0..e0d3671b07 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -81,7 +81,7 @@ facilitates social contribution, easy testing and peer review.
To contribute a patch, the workflow is as follows:
- 1. Fork repository ([only for the first time](https://help.github.com/en/articles/fork-a-repo))
+ 1. Fork repository ([only for the first time](https://docs.github.com/en/get-started/quickstart/fork-a-repo))
1. Create topic branch
1. Commit patches
@@ -182,7 +182,7 @@ for more information on helping with translations.
### Work in Progress Changes and Requests for Comments
If a pull request is not to be considered for merging (yet), please
-prefix the title with [WIP] or use [Tasks Lists](https://help.github.com/articles/basic-writing-and-formatting-syntax/#task-lists)
+prefix the title with [WIP] or use [Tasks Lists](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#task-lists)
in the body of the pull request to indicate tasks are pending.
### Address Feedback
@@ -386,7 +386,7 @@ about:
- It may be because your code is too complex for all but a few people, and those people
may not have realized your pull request even exists. A great way to find people who
are qualified and care about the code you are touching is the
- [Git Blame feature](https://help.github.com/articles/tracing-changes-in-a-file/). Simply
+ [Git Blame feature](https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/tracking-changes-in-a-file). Simply
look up who last modified the code you are changing and see if you can find
them and give them a nudge. Don't be incessant about the nudging, though.
- Finally, if all else fails, ask on IRC or elsewhere for someone to give your pull request
diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4
index 5b5a8ed16e..1e979edf0f 100644
--- a/build-aux/m4/bitcoin_qt.m4
+++ b/build-aux/m4/bitcoin_qt.m4
@@ -350,7 +350,7 @@ AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_LIBS], [
PKG_CHECK_MODULES([QT_FONTDATABASE], [${qt_lib_prefix}FontDatabaseSupport${qt_lib_suffix}], [QT_LIBS="$QT_FONTDATABASE_LIBS $QT_LIBS"])
PKG_CHECK_MODULES([QT_THEME], [${qt_lib_prefix}ThemeSupport${qt_lib_suffix}], [QT_LIBS="$QT_THEME_LIBS $QT_LIBS"])
if test "x$TARGET_OS" = xlinux; then
- PKG_CHECK_MODULES([QT_INPUT], [${qt_lib_prefix}XcbQpa], [QT_LIBS="$QT_INPUT_LIBS $QT_LIBS"])
+ PKG_CHECK_MODULES([QT_INPUT], [${qt_lib_prefix}InputSupport], [QT_LIBS="$QT_INPUT_LIBS $QT_LIBS"])
PKG_CHECK_MODULES([QT_SERVICE], [${qt_lib_prefix}ServiceSupport], [QT_LIBS="$QT_SERVICE_LIBS $QT_LIBS"])
PKG_CHECK_MODULES([QT_XCBQPA], [${qt_lib_prefix}XcbQpa], [QT_LIBS="$QT_XCBQPA_LIBS $QT_LIBS"])
elif test "x$TARGET_OS" = xdarwin; then
diff --git a/ci/lint/06_script.sh b/ci/lint/06_script.sh
index e38cfe8eef..f7dacd8512 100755
--- a/ci/lint/06_script.sh
+++ b/ci/lint/06_script.sh
@@ -23,10 +23,15 @@ test/lint/git-subtree-check.sh src/crc32c
test/lint/check-doc.py
test/lint/lint-all.sh
-if [ "$CIRRUS_REPO_FULL_NAME" = "bitcoin/bitcoin" ] && [ -n "$CIRRUS_CRON" ]; then
- git log --merges --before="2 days ago" -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit
- ${CI_RETRY_EXE} gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys $(<contrib/verify-commits/trusted-keys) &&
- ./contrib/verify-commits/verify-commits.py --clean-merge=2;
+if [ "$CIRRUS_REPO_FULL_NAME" = "bitcoin/bitcoin" ] && [ "$CIRRUS_PR" = "" ] ; then
+ # Sanity check only the last few commits to get notified of missing sigs,
+ # missing keys, or expired keys. Usually there is only one new merge commit
+ # per push on the master branch and a few commits on release branches, so
+ # sanity checking only a few (10) commits seems sufficient and cheap.
+ git log HEAD~10 -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit
+ git log HEAD~10 -1 --format='%H' > ./contrib/verify-commits/trusted-git-root
+ ${CI_RETRY_EXE} gpg --keyserver hkps://keys.openpgp.org --recv-keys $(<contrib/verify-commits/trusted-keys) &&
+ ./contrib/verify-commits/verify-commits.py;
fi
echo
diff --git a/ci/test/00_setup_env_win64.sh b/ci/test/00_setup_env_win64.sh
index 4d5bde13fd..c5219cdd58 100755
--- a/ci/test/00_setup_env_win64.sh
+++ b/ci/test/00_setup_env_win64.sh
@@ -14,7 +14,3 @@ export PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine-binfmt wine64 wine32 fil
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
export BITCOIN_CONFIG="--enable-reduce-exports --disable-gui-tests --disable-external-signer"
-
-# Compiler for MinGW-w64 causes false -Wreturn-type warning.
-# See https://sourceforge.net/p/mingw-w64/bugs/306/
-export NO_WERROR=1
diff --git a/configure.ac b/configure.ac
index 753e716d03..6c867986c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -417,7 +417,13 @@ if test "x$enable_werror" = "xyes"; then
AX_CHECK_COMPILE_FLAG([-Werror=range-loop-analysis],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=range-loop-analysis"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=unused-variable],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=unused-variable"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=date-time],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=date-time"],,[[$CXXFLAG_WERROR]])
- AX_CHECK_COMPILE_FLAG([-Werror=return-type],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=return-type"],,[[$CXXFLAG_WERROR]])
+
+ dnl -Wreturn-type is broken in GCC for MinGW-w64.
+ dnl https://sourceforge.net/p/mingw-w64/bugs/306/
+ AX_CHECK_COMPILE_FLAG([-Werror=return-type], [ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=return-type"], [], [$CXXFLAG_WERROR],
+ [AC_LANG_SOURCE([[#include <cassert>
+ int f(){ assert(false); }]])])
+
AX_CHECK_COMPILE_FLAG([-Werror=conditional-uninitialized],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=conditional-uninitialized"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Werror=sign-compare],[ERROR_CXXFLAGS="$ERROR_CXXFLAGS -Werror=sign-compare"],,[[$CXXFLAG_WERROR]])
dnl -Wsuggest-override is broken with GCC before 9.2
diff --git a/contrib/builder-keys/README.md b/contrib/builder-keys/README.md
index a7c1d5ae0a..56bd87d0af 100644
--- a/contrib/builder-keys/README.md
+++ b/contrib/builder-keys/README.md
@@ -20,7 +20,7 @@ To fetch keys of builders and active developers, feed the list of fingerprints
of the primary keys into gpg:
```sh
-while read fingerprint keyholder_name; do gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys ${fingerprint}; done < ./keys.txt
+while read fingerprint keyholder_name; do gpg --keyserver hkps://keys.openpgp.org --recv-keys ${fingerprint}; done < ./keys.txt
```
Add your key to the list if you provided Guix attestations for two major or
diff --git a/contrib/builder-keys/keys.txt b/contrib/builder-keys/keys.txt
index db28cd07a0..e8032f66ee 100644
--- a/contrib/builder-keys/keys.txt
+++ b/contrib/builder-keys/keys.txt
@@ -5,6 +5,7 @@ E944AE667CF960B1004BC32FCA662BE18B877A60 Andreas Schildbach (aschildbach)
590B7292695AFFA5B672CBB2E13FC145CD3F4304 Antoine Poinsot (darosior)
0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8 Ben Carman (benthecarman)
912FD3228387123DC97E0E57D5566241A0295FA9 BtcDrak (btcdrak)
+04017A2A6D9A0CCDC81D8EC296AB007F1A7ED999 Carl Dong (dongcarl)
C519EBCF3B926298946783EFF6430754120EC2F4 Christian Decker (cdecker)
18AE2F798E0D239755DA4FD24B79F986CBDF8736 Chun Kuan Le (ken2812221)
101598DC823C1B5F9A6624ABA5E0907A0380E6C3 CoinForensics (CoinForensics)
@@ -19,6 +20,7 @@ D35176BE9264832E4ACA8986BF0792FBE95DC863 fivepiece (fivepiece)
01CDF4627A3B88AAE4A571C87588242FBE38D3A8 Gavin Andresen (gavinandresen)
D1DBF2C4B96F2DEBF4C16654410108112E7EA81F Hennadii Stepanov (hebasto)
A2FD494D0021AA9B4FA58F759102B7AE654A4A5A Ilyas Ridhuan (IlyasRidhuan)
+2688F5A9A4BE0F295E921E8A25F27A38A47AD566 James O'Beirne (jamesob)
D3F22A3A4C366C2DCB66D3722DA9C5A7FA81EA35 Jarol Rodriguez (jarolrod)
7480909378D544EA6B6DCEB7535B12980BB8A4D3 Jeffri H Frontz (jhfrontz)
D3CC177286005BB8FF673294C5242A1AB3936517 jl2012 (jl2012)
@@ -26,6 +28,7 @@ D3CC177286005BB8FF673294C5242A1AB3936517 jl2012 (jl2012)
32EE5C4C3FA15CCADB46ABE529D4BCB6416F53EC Jonas Schnelli (jonasschnelli)
4B4E840451149DD7FB0D633477DFAB5C3108B9A8 Jorge Timon (jtimon)
C42AFF7C61B3E44A1454CD3557AF762DB3353322 Karl-Johan Alm (kallewoof)
+70A1D47DD44F59DF8B22244333E472FE870C7E5D Kristaps Kaupe (kristapsk)
30DE693AE0DE9E37B3E7EB6BBFF0F67810C1EED1 Lisa Neigut (niftynei)
E463A93F5F3117EEDE6C7316BD02942421F4889F Luke Dashjr (luke-jr)
B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B Marco Falke (marco)
diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py
index 7d83c5f751..2da7ae793d 100755
--- a/contrib/devtools/test-symbol-check.py
+++ b/contrib/devtools/test-symbol-check.py
@@ -73,20 +73,21 @@ class TestSymbolChecks(unittest.TestCase):
(1, executable + ': NEEDED library libutil.so.1 is not allowed\n' +
executable + ': failed LIBRARY_DEPENDENCIES'))
- # finally, check a conforming file that simply uses a math function
+ # finally, check a simple conforming binary
source = 'test3.c'
executable = 'test3'
with open(source, 'w', encoding="utf8") as f:
f.write('''
- #include <math.h>
+ #include <stdio.h>
int main()
{
- return (int)pow(2.0, 4.0);
+ printf("42");
+ return 0;
}
''')
- self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']),
+ self.assertEqual(call_symbol_check(cc, source, executable, []),
(0, ''))
def test_MACHO(self):
diff --git a/contrib/guix/guix-attest b/contrib/guix/guix-attest
index dcf709b542..6e12cbead7 100755
--- a/contrib/guix/guix-attest
+++ b/contrib/guix/guix-attest
@@ -159,23 +159,21 @@ Hint: You may wish to remove the existing attestations and their signatures by
EOF
}
-# Given a document with unix line endings (just <LF>) in stdin, make all lines
-# end in <CR><LF> and make sure there's no trailing <LF> at the end of the file.
-#
-# This is necessary as cleartext signatures are calculated on text after their
-# line endings are canonicalized.
+echo "Attesting to build outputs for version: '${VERSION}'"
+echo ""
+
+# Given a SHA256SUMS file as stdin that has lines like:
+# 0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6 a/b/d/c/d/s/bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
+# ...
#
-# For more information:
-# 1. https://security.stackexchange.com/a/104261
-# 2. https://datatracker.ietf.org/doc/html/rfc4880#section-7.1
+# Replace each line's file name with its basename:
+# 0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6 bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
+# ...
#
-rfc4880_normalize_document() {
- sed 's/$/\r/' | head -c -2
+basenameify_SHA256SUMS() {
+ sed -E 's@(^[[:xdigit:]]{64}[[:space:]]+).+/([^/]+$)@\1\2@'
}
-echo "Attesting to build outputs for version: '${VERSION}'"
-echo ""
-
outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name"
mkdir -p "$outsigdir"
(
@@ -188,7 +186,7 @@ mkdir -p "$outsigdir"
cat "${noncodesigned_fragments[@]}" \
| sort -u \
| sort -k2 \
- | rfc4880_normalize_document \
+ | basenameify_SHA256SUMS \
> "$temp_noncodesigned"
if [ -e noncodesigned.SHA256SUMS ]; then
# The SHA256SUMS already exists, make sure it's exactly what we
@@ -216,7 +214,7 @@ mkdir -p "$outsigdir"
cat "${sha256sum_fragments[@]}" \
| sort -u \
| sort -k2 \
- | rfc4880_normalize_document \
+ | basenameify_SHA256SUMS \
> "$temp_all"
if [ -e all.SHA256SUMS ]; then
# The SHA256SUMS already exists, make sure it's exactly what we
diff --git a/contrib/guix/guix-verify b/contrib/guix/guix-verify
index e4863f115b..02ae022741 100755
--- a/contrib/guix/guix-verify
+++ b/contrib/guix/guix-verify
@@ -77,11 +77,13 @@ verify() {
echo ""
echo "Hint: Either the signature is invalid or the public key is missing"
echo ""
+ failure=1
elif ! diff --report-identical "$compare_manifest" "$current_manifest" 1>&2; then
echo "ERR: The SHA256SUMS attestation in these two directories differ:"
echo " '${compare_manifest}'"
echo " '${current_manifest}'"
echo ""
+ failure=1
else
echo "Verified: '${current_manifest}'"
echo ""
@@ -166,3 +168,7 @@ if (( ${#all_noncodesigned[@]} + ${#all_all[@]} == 0 )); then
echo ""
exit 1
fi
+
+if [ -n "$failure" ]; then
+ exit 1
+fi
diff --git a/contrib/guix/libexec/prelude.bash b/contrib/guix/libexec/prelude.bash
index 9705607119..40ae4b5208 100644
--- a/contrib/guix/libexec/prelude.bash
+++ b/contrib/guix/libexec/prelude.bash
@@ -49,7 +49,7 @@ fi
# Set common variables
################
-VERSION="${VERSION:-$(git_head_version)}"
+VERSION="${FORCE_VERSION:-$(git_head_version)}"
DISTNAME="${DISTNAME:-bitcoin-${VERSION}}"
version_base_prefix="${PWD}/guix-build-"
diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py
index 73f54cd488..9a8bcc57a5 100755
--- a/contrib/linearize/linearize-data.py
+++ b/contrib/linearize/linearize-data.py
@@ -17,7 +17,6 @@ import datetime
import time
import glob
from collections import namedtuple
-from binascii import unhexlify
settings = {}
@@ -332,7 +331,7 @@ if __name__ == '__main__':
settings['max_out_sz'] = int(settings['max_out_sz'])
settings['split_timestamp'] = int(settings['split_timestamp'])
settings['file_timestamp'] = int(settings['file_timestamp'])
- settings['netmagic'] = unhexlify(settings['netmagic'].encode('utf-8'))
+ settings['netmagic'] = bytes.fromhex(settings['netmagic'])
settings['out_of_order_cache_sz'] = int(settings['out_of_order_cache_sz'])
settings['debug_output'] = settings['debug_output'].lower()
diff --git a/contrib/signet/getcoins.py b/contrib/signet/getcoins.py
index 691f0bb1b6..dc203f1254 100755
--- a/contrib/signet/getcoins.py
+++ b/contrib/signet/getcoins.py
@@ -5,32 +5,64 @@
import argparse
import subprocess
-import requests
import sys
+import requests
+
+DEFAULT_GLOBAL_FAUCET = 'https://signetfaucet.com/claim'
+GLOBAL_FIRST_BLOCK_HASH = '00000086d6b2636cb2a392d45edc4ec544a10024d30141c9adf4bfd9de533b53'
parser = argparse.ArgumentParser(description='Script to get coins from a faucet.', epilog='You may need to start with double-dash (--) when providing bitcoin-cli arguments.')
parser.add_argument('-c', '--cmd', dest='cmd', default='bitcoin-cli', help='bitcoin-cli command to use')
-parser.add_argument('-f', '--faucet', dest='faucet', default='https://signetfaucet.com/claim', help='URL of the faucet')
+parser.add_argument('-f', '--faucet', dest='faucet', default=DEFAULT_GLOBAL_FAUCET, help='URL of the faucet')
parser.add_argument('-a', '--addr', dest='addr', default='', help='Bitcoin address to which the faucet should send')
parser.add_argument('-p', '--password', dest='password', default='', help='Faucet password, if any')
parser.add_argument('bitcoin_cli_args', nargs='*', help='Arguments to pass on to bitcoin-cli (default: -signet)')
args = parser.parse_args()
-if args.addr == '':
- if args.bitcoin_cli_args == []:
- args.bitcoin_cli_args = ['-signet']
- # get address for receiving coins
+if args.bitcoin_cli_args == []:
+ args.bitcoin_cli_args = ['-signet']
+
+
+def bitcoin_cli(rpc_command_and_params):
+ argv = [args.cmd] + args.bitcoin_cli_args + rpc_command_and_params
try:
- args.addr = subprocess.check_output([args.cmd] + args.bitcoin_cli_args + ['getnewaddress', 'faucet', 'bech32']).strip()
+ return subprocess.check_output(argv).strip().decode()
except FileNotFoundError:
print('The binary', args.cmd, 'could not be found.')
- exit()
+ exit(1)
+ except subprocess.CalledProcessError:
+ cmdline = ' '.join(argv)
+ print(f'-----\nError while calling "{cmdline}" (see output above).')
+ exit(1)
+
+
+if args.faucet.lower() == DEFAULT_GLOBAL_FAUCET:
+ # Get the hash of the block at height 1 of the currently active signet chain
+ curr_signet_hash = bitcoin_cli(['getblockhash', '1'])
+ if curr_signet_hash != GLOBAL_FIRST_BLOCK_HASH:
+ print('The global faucet cannot be used with a custom Signet network. Please use the global signet or setup your custom faucet to use this functionality.\n')
+ exit(1)
+
+if args.addr == '':
+ # get address for receiving coins
+ args.addr = bitcoin_cli(['getnewaddress', 'faucet', 'bech32'])
data = {'address': args.addr, 'password': args.password}
try:
res = requests.post(args.faucet, data=data)
except:
print('Unexpected error when contacting faucet:', sys.exc_info()[0])
- exit()
-print(res.text)
+ exit(1)
+
+# Display the output as per the returned status code
+if res:
+ # When the return code is in between 200 and 400 i.e. successful
+ print(res.text)
+elif res.status_code == 404:
+ print('The specified faucet URL does not exist. Please check for any server issues/typo.')
+elif res.status_code == 429:
+ print('The script does not allow for repeated transactions as the global faucet is rate-limitied to 1 request/IP/day. You can access the faucet website to get more coins manually')
+else:
+ print(f'Returned Error Code {res.status_code}\n{res.text}\n')
+ print('Please check the provided arguments for their validity and/or any possible typo.')
diff --git a/contrib/signet/miner b/contrib/signet/miner
index 78e1fa5ecd..012bd6cc31 100755
--- a/contrib/signet/miner
+++ b/contrib/signet/miner
@@ -15,7 +15,6 @@ import sys
import time
import subprocess
-from binascii import unhexlify
from io import BytesIO
PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
@@ -202,7 +201,7 @@ def finish_block(block, signet_solution, grind_cmd):
def generate_psbt(tmpl, reward_spk, *, blocktime=None):
signet_spk = tmpl["signet_challenge"]
- signet_spk_bin = unhexlify(signet_spk)
+ signet_spk_bin = bytes.fromhex(signet_spk)
cbtx = create_coinbase(height=tmpl["height"], value=tmpl["coinbasevalue"], spk=reward_spk)
cbtx.vin[0].nSequence = 2**32-2
@@ -258,7 +257,7 @@ def get_reward_addr_spk(args, height):
return args.address, args.reward_spk
reward_addr = get_reward_address(args, height)
- reward_spk = unhexlify(json.loads(args.bcli("getaddressinfo", reward_addr))["scriptPubKey"])
+ reward_spk = bytes.fromhex(json.loads(args.bcli("getaddressinfo", reward_addr))["scriptPubKey"])
if args.address is not None:
# will always be the same, so cache
args.reward_spk = reward_spk
diff --git a/contrib/verify-commits/README.md b/contrib/verify-commits/README.md
index e95a57586f..b8b15280ba 100644
--- a/contrib/verify-commits/README.md
+++ b/contrib/verify-commits/README.md
@@ -40,7 +40,7 @@ Import trusted keys
In order to check the commit signatures, you must add the trusted PGP keys to your machine. [GnuPG](https://gnupg.org/) may be used to import the trusted keys by running the following command:
```sh
-gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys $(<contrib/verify-commits/trusted-keys)
+gpg --keyserver hkps://keys.openpgp.org --recv-keys $(<contrib/verify-commits/trusted-keys)
```
Key expiry/revocation
diff --git a/contrib/zmq/zmq_sub.py b/contrib/zmq/zmq_sub.py
index 9cb887e2dc..6269269d37 100755
--- a/contrib/zmq/zmq_sub.py
+++ b/contrib/zmq/zmq_sub.py
@@ -23,7 +23,6 @@
https://github.com/bitcoin/bitcoin/blob/37a7fe9e440b83e2364d5498931253937abe9294/contrib/zmq/zmq_sub.py
"""
-import binascii
import asyncio
import zmq
import zmq.asyncio
@@ -58,18 +57,18 @@ class ZMQHandler():
sequence = str(struct.unpack('<I', seq)[-1])
if topic == b"hashblock":
print('- HASH BLOCK ('+sequence+') -')
- print(binascii.hexlify(body))
+ print(body.hex())
elif topic == b"hashtx":
print('- HASH TX ('+sequence+') -')
- print(binascii.hexlify(body))
+ print(body.hex())
elif topic == b"rawblock":
print('- RAW BLOCK HEADER ('+sequence+') -')
- print(binascii.hexlify(body[:80]))
+ print(body[:80].hex())
elif topic == b"rawtx":
print('- RAW TX ('+sequence+') -')
- print(binascii.hexlify(body))
+ print(body.hex())
elif topic == b"sequence":
- hash = binascii.hexlify(body[:32])
+ hash = body[:32].hex()
label = chr(body[32])
mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]
print('- SEQUENCE ('+sequence+') -')
diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk
index d45ac3d03f..8a3116bb3b 100644
--- a/depends/packages/bdb.mk
+++ b/depends/packages/bdb.mk
@@ -12,7 +12,7 @@ $(package)_config_opts_mingw32=--enable-mingw
$(package)_config_opts_linux=--with-pic
$(package)_config_opts_android=--with-pic
$(package)_cflags+=-Wno-error=implicit-function-declaration
-$(package)_cxxflags=-std=c++17
+$(package)_cxxflags+=-std=c++17
$(package)_cppflags_mingw32=-DUNICODE -D_UNICODE
endef
diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk
index f879d176f5..21df50b040 100644
--- a/depends/packages/boost.mk
+++ b/depends/packages/boost.mk
@@ -23,7 +23,7 @@ else
$(package)_toolset_$(host_os)=gcc
endif
$(package)_config_libraries=filesystem,system,test
-$(package)_cxxflags=-std=c++17 -fvisibility=hidden
+$(package)_cxxflags+=-std=c++17 -fvisibility=hidden
$(package)_cxxflags_linux=-fPIC
$(package)_cxxflags_android=-fPIC
$(package)_cxxflags_x86_64_darwin=-fcf-protection=full
diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk
index 3b7f3690a4..9798248c61 100644
--- a/depends/packages/zeromq.mk
+++ b/depends/packages/zeromq.mk
@@ -12,7 +12,7 @@ define $(package)_set_vars
$(package)_config_opts += --disable-Werror --disable-drafts --enable-option-checking
$(package)_config_opts_linux=--with-pic
$(package)_config_opts_android=--with-pic
- $(package)_cxxflags=-std=c++17
+ $(package)_cxxflags+=-std=c++17
endef
define $(package)_preprocess_cmds
diff --git a/doc/README.md b/doc/README.md
index 38f6b1d327..4c79ecf42f 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -77,6 +77,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th
- [Fuzz-testing](fuzzing.md)
- [I2P Support](i2p.md)
- [Init Scripts (systemd/upstart/openrc)](init.md)
+- [Managing Wallets](managing-wallets.md)
- [PSBT support](psbt.md)
- [Reduce Memory](reduce-memory.md)
- [Reduce Traffic](reduce-traffic.md)
diff --git a/doc/descriptors.md b/doc/descriptors.md
index e27ff87546..3bbb626a42 100644
--- a/doc/descriptors.md
+++ b/doc/descriptors.md
@@ -99,7 +99,7 @@ Descriptors consist of several types of expressions. The top level expression is
`ADDR` expressions are any type of supported address:
- P2PKH addresses (base58, of the form `1...` for mainnet or `[nm]...` for testnet). Note that P2PKH addresses in descriptors cannot be used for P2PK outputs (use the `pk` function instead).
- P2SH addresses (base58, of the form `3...` for mainnet or `2...` for testnet, defined in [BIP 13](https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki)).
-- Segwit addresses (bech32, of the form `bc1...` for mainnet or `tb1...` for testnet, defined in [BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)).
+- Segwit addresses (bech32 and bech32m, of the form `bc1...` for mainnet or `tb1...` for testnet, defined in [BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) and [BIP 350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)).
## Explanation
diff --git a/doc/i2p.md b/doc/i2p.md
index 3a507a25ab..5f631c11ca 100644
--- a/doc/i2p.md
+++ b/doc/i2p.md
@@ -10,11 +10,22 @@ started with I2P terminology.
## Run Bitcoin Core with an I2P router (proxy)
A running I2P router (proxy) with [SAM](https://geti2p.net/en/docs/api/samv3)
-enabled is required (there is an [official one](https://geti2p.net) and
-[a few alternatives](https://en.wikipedia.org/wiki/I2P#Routers)). Notice the IP
-address and port the SAM proxy is listening to; usually, it is
-`127.0.0.1:7656`. Once it is up and running with SAM enabled, use the following
-Bitcoin Core options:
+enabled is required. Options include:
+
+- [i2prouter (I2P Router)](https://geti2p.net), the official implementation in
+ Java
+- [i2pd (I2P Daemon)](https://github.com/PurpleI2P/i2pd)
+ ([documentation](https://i2pd.readthedocs.io/en/latest)), a lighter
+ alternative in C++ (successfully tested with version 2.23 and up; version 2.36
+ or later recommended)
+- [i2p-zero](https://github.com/i2p-zero/i2p-zero)
+- [other alternatives](https://en.wikipedia.org/wiki/I2P#Routers)
+
+Note the IP address and port the SAM proxy is listening to; usually, it is
+`127.0.0.1:7656`.
+
+Once an I2P router with SAM enabled is up and running, use the following Bitcoin
+Core configuration options:
```
-i2psam=<ip:port>
@@ -42,15 +53,30 @@ named `i2p_private_key` in the Bitcoin Core data directory.
## Additional configuration options related to I2P
-You may set the `debug=i2p` config logging option to have additional
-information in the debug log about your I2P configuration and connections. Run
-`bitcoin-cli help logging` for more information.
+```
+-debug=i2p
+```
+
+Set the `debug=i2p` config logging option to see additional information in the
+debug log about your I2P configuration and connections. Run `bitcoin-cli help
+logging` for more information.
+
+```
+-onlynet=i2p
+```
+
+Make outgoing connections only to I2P addresses. Incoming connections are not
+affected by this option. It can be specified multiple times to allow multiple
+network types, e.g. onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p.
+
+Warning: if you use -onlynet with values other than onion, and the -onion or
+-proxy option is set, then outgoing onion connections will still be made; use
+-noonion or -onion=0 to disable outbound onion connections in this case.
-It is possible to restrict outgoing connections in the usual way with
-`onlynet=i2p`. I2P support was added to Bitcoin Core in version 22.0 (mid-2021)
-and there may be fewer I2P peers than Tor or IP ones. Therefore, using
-`onlynet=i2p` alone (without other `onlynet=`) may make a node more susceptible
-to [Sybil attacks](https://en.bitcoin.it/wiki/Weaknesses#Sybil_attack). Use
+I2P support was added to Bitcoin Core in version 22.0 and there may be fewer I2P
+peers than Tor or IP ones. Therefore, using I2P alone without other networks may
+make a node more susceptible to [Sybil
+attacks](https://en.bitcoin.it/wiki/Weaknesses#Sybil_attack). You can use
`bitcoin-cli -addrinfo` to see the number of I2P addresses known to your node.
Another consideration with `onlynet=i2p` is that the initial blocks download
diff --git a/doc/managing-wallets.md b/doc/managing-wallets.md
new file mode 100644
index 0000000000..aab6d131bd
--- /dev/null
+++ b/doc/managing-wallets.md
@@ -0,0 +1,125 @@
+# Managing the Wallet
+
+## 1. Backing Up and Restoring The Wallet
+
+### 1.1 Creating the Wallet
+
+Since version 0.21, Bitcoin Core no longer has a default wallet.
+Wallets can be created with the `createwallet` RPC or with the `Create wallet` GUI menu item.
+
+In the GUI, the `Create a new wallet` button is displayed on the main screen when there is no wallet loaded. Alternatively, there is the option `File` ->`Create wallet`.
+
+The following command, for example, creates a descriptor wallet. More information about this command may be found by running `bitcoin-cli help createwallet`.
+
+```
+$ bitcoin-cli -named createwallet wallet_name="wallet-01" descriptors=true
+```
+
+The `descriptors` parameter can be omitted if the intention is to create a legacy wallet. For now, the default type is the legacy wallet, but that is expected to change in a future release.
+
+By default, wallets are created in the `wallets` folder of the data directory, which varies by operating system, as shown below. The user can change the default by using the `-datadir` or `-walletdir` initialization parameters.
+
+| Operating System | Default wallet directory |
+| -----------------|:------------------------------------------------------------|
+| Linux | `/home/<user>/.bitcoin/wallets` |
+| Windows | `C:\Users\<user>\AppData\Roaming\Bitcoin\wallets` |
+| macOS | `/Users/<user>/Library/Application Support/Bitcoin/wallets` |
+
+### 1.2 Encrypting the Wallet
+
+The `wallet.dat` file is not encrypted by default and is, therefore, vulnerable if an attacker gains access to the device where the wallet or the backups are stored.
+
+Wallet encryption may prevent unauthorized access. However, this significantly increases the risk of losing coins due to forgotten passphrases. There is no way to recover a passphrase. This tradeoff should be well thought out by the user.
+
+Wallet encryption may also not protect against more sophisticated attacks. An attacker can, for example, obtain the password by installing a keylogger on the user's machine.
+
+After encrypting the wallet or changing the passphrase, a new backup needs to be created immediately. The reason is that the keypool is flushed and a new HD seed is generated after encryption. Any bitcoins received by the new seed cannot be recovered from the previous backups.
+
+The wallet's private key may be encrypted with the following command:
+
+```
+$ bitcoin-cli -rpcwallet="wallet-01" encryptwallet "passphrase"
+```
+
+Once encrypted, the passphrase can be changed with the `walletpassphrasechange` command.
+
+```
+$ bitcoin-cli -rpcwallet="wallet-01" walletpassphrasechange "oldpassphrase" "newpassphrase"
+```
+
+The argument passed to `-rpcwallet` is the name of the wallet to be encrypted.
+
+Only the wallet's private key is encrypted. All other wallet information, such as transactions, is still visible.
+
+The wallet's private key can also be encrypted in the `createwallet` command via the `passphrase` argument:
+
+```
+$ bitcoin-cli -named createwallet wallet_name="wallet-01" descriptors=true passphrase="passphrase"
+```
+
+Note that if the passphrase is lost, all the coins in the wallet will also be lost forever.
+
+### 1.3 Unlocking the Wallet
+
+If the wallet is encrypted and the user tries any operation related to private keys, such as sending bitcoins, an error message will be displayed.
+
+```
+$ bitcoin-cli -rpcwallet="wallet-01" sendtoaddress "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" 0.01
+error code: -13
+error message:
+Error: Please enter the wallet passphrase with walletpassphrase first.
+```
+
+To unlock the wallet and allow it to run these operations, the `walletpassphrase` RPC is required.
+
+This command takes the passphrase and an argument called `timeout`, which specifies the time in seconds that the wallet decryption key is stored in memory. After this period expires, the user needs to execute this RPC again.
+
+```
+$ bitcoin-cli -rpcwallet="wallet-01" walletpassphrase "passphrase" 120
+```
+
+In the GUI, there is no specific menu item to unlock the wallet. When the user sends bitcoins, the passphrase will be prompted automatically.
+
+### 1.4 Backing Up the Wallet
+
+To backup the wallet, the `backupwallet` RPC or the `Backup Wallet` GUI menu item must be used to ensure the file is in a safe state when the copy is made.
+
+In the RPC, the destination parameter must include the name of the file. Otherwise, the command will return an error message like "Error: Wallet backup failed!" for descriptor wallets. If it is a legacy wallet, it will be copied and a file will be created with the default file name `wallet.dat`.
+
+```
+$ bitcoin-cli -rpcwallet="wallet-01" backupwallet /home/node01/Backups/backup-01.dat
+```
+
+In the GUI, the wallet is selected in the `Wallet` drop-down list in the upper right corner. If this list is not present, the wallet can be loaded in `File` ->`Open wallet` if necessary. Then, the backup can be done in `File` -> `Backup Wallet...`.
+
+This backup file can be stored on one or multiple offline devices, which must be reliable enough to work in an emergency and be malware free. Backup files can be regularly tested to avoid problems in the future.
+
+If the computer has malware, it can compromise the wallet when recovering the backup file. One way to minimize this is to not connect the backup to an online device.
+
+If both the wallet and all backups are lost for any reason, the bitcoins related to this wallet will become permanently inaccessible.
+
+### 1.5 Backup Frequency
+
+The original Bitcoin Core wallet was a collection of unrelated private keys. If a non-HD wallet had received funds to an address and then was restored from a backup made before the address was generated, then any funds sent to that address would have been lost because there was no deterministic mechanism to derive the address again.
+
+Bitcoin Core [version 0.13](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.13.0.md) introduced HD wallets with deterministic key derivation. With HD wallets, users no longer lose funds when restoring old backups because all addresses are derived from the HD wallet seed.
+
+This means that a single backup is enough to recover the coins at any time. It is still recommended to make regular backups (once a week) or after a significant number of new transactions to maintain the metadata, such as labels. Metadata cannot be retrieved from a blockchain rescan, so if the backup is too old, the metadata will be lost forever.
+
+Wallets created before version 0.13 are not HD and must be backed up every 100 keys used since the previous backup, or even more often to maintain the metadata.
+
+### 1.6 Restoring the Wallet From a Backup
+
+To restore a wallet, the `restorewallet` RPC must be used.
+
+```
+$ bitcoin-cli restorewallet "restored-wallet" /home/node01/Backups/backup-01.dat
+```
+
+After that, `getwalletinfo` can be used to check if the wallet has been fully restored.
+
+```
+$ bitcoin-cli -rpcwallet="restored-wallet" getwalletinfo
+```
+
+The restored wallet can also be loaded in the GUI via `File` ->`Open wallet`. \ No newline at end of file
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 61c65d5a3e..6815408ca7 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -108,6 +108,12 @@ RPC
Tests
-----
+- For the `regtest` network the activation heights of several softforks were
+ changed.
+ * BIP 34 (blockheight in coinbase) from 500 to 2 (#16333)
+ * BIP 66 (DERSIG) from 1251 to 102 (#22632)
+ * BIP 65 (CLTV) from 1351 to 111 (#21862)
+
Credits
=======
diff --git a/doc/release-process.md b/doc/release-process.md
index c57fa5b23a..3e4748b742 100644
--- a/doc/release-process.md
+++ b/doc/release-process.md
@@ -199,30 +199,22 @@ popd
### After 3 or more people have guix-built and their results match:
-Combine `all.SHA256SUMS` and `all.SHA256SUMS.asc` into a clear-signed
-`SHA256SUMS.asc` message:
-
-```sh
-echo -e "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n$(cat all.SHA256SUMS)\n$(cat filename.txt.asc)" > SHA256SUMS.asc
-```
-
-Here's an equivalent, more readable command if you're confident that you won't
-mess up whitespaces when copy-pasting:
+Combine the `all.SHA256SUMS.asc` file from all signers into `SHA256SUMS.asc`:
```bash
-cat << EOF > SHA256SUMS.asc
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA256
-
-$(cat all.SHA256SUMS)
-$(cat all.SHA256SUMS.asc)
-EOF
+cat "$VERSION"/*/all.SHA256SUMS.asc > SHA256SUMS.asc
```
-- Upload to the bitcoincore.org server (`/var/www/bin/bitcoin-core-${VERSION}`):
- 1. The contents of `./bitcoin/guix-build-${VERSION}/output`, except for
+
+- Upload to the bitcoincore.org server (`/var/www/bin/bitcoin-core-${VERSION}/`):
+ 1. The contents of each `./bitcoin/guix-build-${VERSION}/output/${HOST}/` directory, except for
`*-debug*` files.
+ Guix will output all of the results into host subdirectories, but the SHA256SUMS
+ file does not include these subdirectories. In order for downloads via torrent
+ to verify without directory structure modification, all of the uploaded files
+ need to be in the same directory as the SHA256SUMS file.
+
The `*-debug*` files generated by the guix build contain debug symbols
for troubleshooting by developers. It is assumed that anyone that is
interested in debugging can run guix to generate the files for
@@ -230,7 +222,13 @@ EOF
as save storage space *do not upload these to the bitcoincore.org server,
nor put them in the torrent*.
- 2. The combined clear-signed message you just created `SHA256SUMS.asc`
+ ```sh
+ find guix-build-${VERSION}/output/ -maxdepth 2 -type f -not -name "SHA256SUMS.part" -and -not -name "*debug*" -exec scp {} user@bitcoincore.org:/var/www/bin/bitcoin-core-${VERSION} \;
+ ```
+
+ 2. The `SHA256SUMS` file
+
+ 3. The `SHA256SUMS.asc` combined signature file you just created
- Create a torrent of the `/var/www/bin/bitcoin-core-${VERSION}` directory such
that at the top level there is only one file: the `bitcoin-core-${VERSION}`
diff --git a/doc/tor.md b/doc/tor.md
index 7d134b64e0..a3ec1987aa 100644
--- a/doc/tor.md
+++ b/doc/tor.md
@@ -57,11 +57,11 @@ outgoing connections, but more is possible.
-onlynet=onion Make outgoing connections only to .onion addresses. Incoming
connections are not affected by this option. This option can be
specified multiple times to allow multiple network types, e.g.
- ipv4, ipv6 or onion. If you use this option with values other
- than onion you *cannot* disable onion connections; outgoing onion
- connections will be enabled when you use -proxy or -onion. Use
- -noonion or -onion=0 if you want to be sure there are no outbound
- onion connections over the default proxy or your defined -proxy.
+ onlynet=ipv4, onlynet=ipv6, onlynet=onion, onlynet=i2p.
+ Warning: if you use -onlynet with values other than onion, and
+ the -onion or -proxy option is set, then outgoing onion
+ connections will still be made; use -noonion or -onion=0 to
+ disable outbound onion connections in this case.
In a typical situation, this suffices to run behind a Tor proxy:
diff --git a/doc/tracing.md b/doc/tracing.md
index 1242a0d250..87fc9603fe 100644
--- a/doc/tracing.md
+++ b/doc/tracing.md
@@ -147,7 +147,7 @@ For example:
```C++
TRACE6(net, inbound_message,
pnode->GetId(),
- pnode->GetAddrName().c_str(),
+ pnode->m_addr_name.c_str(),
pnode->ConnectionTypeAsString().c_str(),
sanitizedType.c_str(),
msg.data.size(),
diff --git a/share/rpcauth/rpcauth.py b/share/rpcauth/rpcauth.py
index b14c80171e..c6d9b652b8 100755
--- a/share/rpcauth/rpcauth.py
+++ b/share/rpcauth/rpcauth.py
@@ -5,7 +5,6 @@
from argparse import ArgumentParser
from base64 import urlsafe_b64encode
-from binascii import hexlify
from getpass import getpass
from os import urandom
@@ -13,7 +12,7 @@ import hmac
def generate_salt(size):
"""Create size byte hex salt"""
- return hexlify(urandom(size)).decode()
+ return urandom(size).hex()
def generate_password():
"""Create 32 byte b64 password"""
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 40d44aaa2e..6af5ead443 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -207,7 +207,6 @@ test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_fuzz_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON) $(RUNTIME_LDFLAGS)
test_fuzz_fuzz_SOURCES = \
test/fuzz/addition_overflow.cpp \
- test/fuzz/addrdb.cpp \
test/fuzz/addrman.cpp \
test/fuzz/asmap.cpp \
test/fuzz/asmap_direct.cpp \
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index c3e224ee83..a5383be7cf 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -244,12 +244,7 @@ bool CAddrDB::Read(CAddrMan& addr)
bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
{
- bool ret = DeserializeDB(ssPeers, addr, false);
- if (!ret) {
- // Ensure addrman is left in a clean state
- addr.Clear();
- }
- return ret;
+ return DeserializeDB(ssPeers, addr, false);
}
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
diff --git a/src/addrdb.h b/src/addrdb.h
index 1e0ccb1f60..26400ee0b6 100644
--- a/src/addrdb.h
+++ b/src/addrdb.h
@@ -8,10 +8,8 @@
#include <fs.h>
#include <net_types.h> // For banmap_t
-#include <serialize.h>
#include <univalue.h>
-#include <string>
#include <vector>
class CAddress;
@@ -21,21 +19,15 @@ class CDataStream;
class CBanEntry
{
public:
- static const int CURRENT_VERSION=1;
- int nVersion;
- int64_t nCreateTime;
- int64_t nBanUntil;
+ static constexpr int CURRENT_VERSION{1};
+ int nVersion{CBanEntry::CURRENT_VERSION};
+ int64_t nCreateTime{0};
+ int64_t nBanUntil{0};
- CBanEntry()
- {
- SetNull();
- }
+ CBanEntry() {}
explicit CBanEntry(int64_t nCreateTimeIn)
- {
- SetNull();
- nCreateTime = nCreateTimeIn;
- }
+ : nCreateTime{nCreateTimeIn} {}
/**
* Create a ban entry from JSON.
@@ -44,19 +36,6 @@ public:
*/
explicit CBanEntry(const UniValue& json);
- SERIALIZE_METHODS(CBanEntry, obj)
- {
- uint8_t ban_reason = 2; //! For backward compatibility
- READWRITE(obj.nVersion, obj.nCreateTime, obj.nBanUntil, ban_reason);
- }
-
- void SetNull()
- {
- nVersion = CBanEntry::CURRENT_VERSION;
- nCreateTime = 0;
- nBanUntil = 0;
- }
-
/**
* Generate a JSON representation of this ban entry.
* @return JSON suitable for passing to the `CBanEntry(const UniValue&)` constructor.
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 96139182d3..48e79c64ed 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -15,6 +15,27 @@
#include <unordered_map>
#include <unordered_set>
+/** Over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread */
+static constexpr uint32_t ADDRMAN_TRIED_BUCKETS_PER_GROUP{8};
+/** Over how many buckets entries with new addresses originating from a single group are spread */
+static constexpr uint32_t ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP{64};
+/** Maximum number of times an address can be added to the new table */
+static constexpr int32_t ADDRMAN_NEW_BUCKETS_PER_ADDRESS{8};
+/** How old addresses can maximally be */
+static constexpr int64_t ADDRMAN_HORIZON_DAYS{30};
+/** After how many failed attempts we give up on a new node */
+static constexpr int32_t ADDRMAN_RETRIES{3};
+/** How many successive failures are allowed ... */
+static constexpr int32_t ADDRMAN_MAX_FAILURES{10};
+/** ... in at least this many days */
+static constexpr int64_t ADDRMAN_MIN_FAIL_DAYS{7};
+/** How recent a successful connection should be before we allow an address to be evicted from tried */
+static constexpr int64_t ADDRMAN_REPLACEMENT_HOURS{4};
+/** The maximum number of tried addr collisions to store */
+static constexpr size_t ADDRMAN_SET_TRIED_COLLISION_SIZE{10};
+/** The maximum time we'll spend trying to resolve a tried table collision, in seconds */
+static constexpr int64_t ADDRMAN_TEST_WINDOW{40*60}; // 40 minutes
+
int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const
{
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash();
@@ -77,6 +98,302 @@ double CAddrInfo::GetChance(int64_t nNow) const
return fChance;
}
+CAddrMan::CAddrMan(bool deterministic, int32_t consistency_check_ratio)
+ : insecure_rand{deterministic}
+ , nKey{deterministic ? uint256{1} : insecure_rand.rand256()}
+ , m_consistency_check_ratio{consistency_check_ratio}
+{
+ for (auto& bucket : vvNew) {
+ for (auto& entry : bucket) {
+ entry = -1;
+ }
+ }
+ for (auto& bucket : vvTried) {
+ for (auto& entry : bucket) {
+ entry = -1;
+ }
+ }
+}
+
+template <typename Stream>
+void CAddrMan::Serialize(Stream& s_) const
+{
+ LOCK(cs);
+
+ /**
+ * Serialized format.
+ * * format version byte (@see `Format`)
+ * * lowest compatible format version byte. This is used to help old software decide
+ * whether to parse the file. For example:
+ * * Bitcoin Core version N knows how to parse up to format=3. If a new format=4 is
+ * introduced in version N+1 that is compatible with format=3 and it is known that
+ * version N will be able to parse it, then version N+1 will write
+ * (format=4, lowest_compatible=3) in the first two bytes of the file, and so
+ * version N will still try to parse it.
+ * * Bitcoin Core version N+2 introduces a new incompatible format=5. It will write
+ * (format=5, lowest_compatible=5) and so any versions that do not know how to parse
+ * format=5 will not try to read the file.
+ * * nKey
+ * * nNew
+ * * nTried
+ * * number of "new" buckets XOR 2**30
+ * * all new addresses (total count: nNew)
+ * * all tried addresses (total count: nTried)
+ * * for each new bucket:
+ * * number of elements
+ * * for each element: index in the serialized "all new addresses"
+ * * asmap checksum
+ *
+ * 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it
+ * as incompatible. This is necessary because it did not check the version number on
+ * deserialization.
+ *
+ * vvNew, vvTried, mapInfo, mapAddr and vRandom are never encoded explicitly;
+ * they are instead reconstructed from the other information.
+ *
+ * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
+ * changes to the ADDRMAN_ parameters without breaking the on-disk structure.
+ *
+ * We don't use SERIALIZE_METHODS since the serialization and deserialization code has
+ * very little in common.
+ */
+
+ // Always serialize in the latest version (FILE_FORMAT).
+
+ OverrideStream<Stream> s(&s_, s_.GetType(), s_.GetVersion() | ADDRV2_FORMAT);
+
+ s << static_cast<uint8_t>(FILE_FORMAT);
+
+ // Increment `lowest_compatible` iff a newly introduced format is incompatible with
+ // the previous one.
+ static constexpr uint8_t lowest_compatible = Format::V3_BIP155;
+ s << static_cast<uint8_t>(INCOMPATIBILITY_BASE + lowest_compatible);
+
+ s << nKey;
+ s << nNew;
+ s << nTried;
+
+ int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
+ s << nUBuckets;
+ std::unordered_map<int, int> mapUnkIds;
+ int nIds = 0;
+ for (const auto& entry : mapInfo) {
+ mapUnkIds[entry.first] = nIds;
+ const CAddrInfo &info = entry.second;
+ if (info.nRefCount) {
+ assert(nIds != nNew); // this means nNew was wrong, oh ow
+ s << info;
+ nIds++;
+ }
+ }
+ nIds = 0;
+ for (const auto& entry : mapInfo) {
+ const CAddrInfo &info = entry.second;
+ if (info.fInTried) {
+ assert(nIds != nTried); // this means nTried was wrong, oh ow
+ s << info;
+ nIds++;
+ }
+ }
+ for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
+ int nSize = 0;
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvNew[bucket][i] != -1)
+ nSize++;
+ }
+ s << nSize;
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvNew[bucket][i] != -1) {
+ int nIndex = mapUnkIds[vvNew[bucket][i]];
+ s << nIndex;
+ }
+ }
+ }
+ // Store asmap checksum after bucket entries so that it
+ // can be ignored by older clients for backward compatibility.
+ uint256 asmap_checksum;
+ if (m_asmap.size() != 0) {
+ asmap_checksum = SerializeHash(m_asmap);
+ }
+ s << asmap_checksum;
+}
+
+template <typename Stream>
+void CAddrMan::Unserialize(Stream& s_)
+{
+ LOCK(cs);
+
+ assert(vRandom.empty());
+
+ Format format;
+ s_ >> Using<CustomUintFormatter<1>>(format);
+
+ int stream_version = s_.GetVersion();
+ if (format >= Format::V3_BIP155) {
+ // Add ADDRV2_FORMAT to the version so that the CNetAddr and CAddress
+ // unserialize methods know that an address in addrv2 format is coming.
+ stream_version |= ADDRV2_FORMAT;
+ }
+
+ OverrideStream<Stream> s(&s_, s_.GetType(), stream_version);
+
+ uint8_t compat;
+ s >> compat;
+ const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE;
+ if (lowest_compatible > FILE_FORMAT) {
+ throw std::ios_base::failure(strprintf(
+ "Unsupported format of addrman database: %u. It is compatible with formats >=%u, "
+ "but the maximum supported by this version of %s is %u.",
+ format, lowest_compatible, PACKAGE_NAME, static_cast<uint8_t>(FILE_FORMAT)));
+ }
+
+ s >> nKey;
+ s >> nNew;
+ s >> nTried;
+ int nUBuckets = 0;
+ s >> nUBuckets;
+ if (format >= Format::V1_DETERMINISTIC) {
+ nUBuckets ^= (1 << 30);
+ }
+
+ if (nNew > ADDRMAN_NEW_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE || nNew < 0) {
+ throw std::ios_base::failure(
+ strprintf("Corrupt CAddrMan serialization: nNew=%d, should be in [0, %d]",
+ nNew,
+ ADDRMAN_NEW_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE));
+ }
+
+ if (nTried > ADDRMAN_TRIED_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE || nTried < 0) {
+ throw std::ios_base::failure(
+ strprintf("Corrupt CAddrMan serialization: nTried=%d, should be in [0, %d]",
+ nTried,
+ ADDRMAN_TRIED_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE));
+ }
+
+ // Deserialize entries from the new table.
+ for (int n = 0; n < nNew; n++) {
+ CAddrInfo &info = mapInfo[n];
+ s >> info;
+ mapAddr[info] = n;
+ info.nRandomPos = vRandom.size();
+ vRandom.push_back(n);
+ }
+ nIdCount = nNew;
+
+ // Deserialize entries from the tried table.
+ int nLost = 0;
+ for (int n = 0; n < nTried; n++) {
+ CAddrInfo info;
+ s >> info;
+ int nKBucket = info.GetTriedBucket(nKey, m_asmap);
+ int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
+ if (info.IsValid()
+ && vvTried[nKBucket][nKBucketPos] == -1) {
+ info.nRandomPos = vRandom.size();
+ info.fInTried = true;
+ vRandom.push_back(nIdCount);
+ mapInfo[nIdCount] = info;
+ mapAddr[info] = nIdCount;
+ vvTried[nKBucket][nKBucketPos] = nIdCount;
+ nIdCount++;
+ } else {
+ nLost++;
+ }
+ }
+ nTried -= nLost;
+
+ // Store positions in the new table buckets to apply later (if possible).
+ // An entry may appear in up to ADDRMAN_NEW_BUCKETS_PER_ADDRESS buckets,
+ // so we store all bucket-entry_index pairs to iterate through later.
+ std::vector<std::pair<int, int>> bucket_entries;
+
+ for (int bucket = 0; bucket < nUBuckets; ++bucket) {
+ int num_entries{0};
+ s >> num_entries;
+ for (int n = 0; n < num_entries; ++n) {
+ int entry_index{0};
+ s >> entry_index;
+ if (entry_index >= 0 && entry_index < nNew) {
+ bucket_entries.emplace_back(bucket, entry_index);
+ }
+ }
+ }
+
+ // If the bucket count and asmap checksum haven't changed, then attempt
+ // to restore the entries to the buckets/positions they were in before
+ // serialization.
+ uint256 supplied_asmap_checksum;
+ if (m_asmap.size() != 0) {
+ supplied_asmap_checksum = SerializeHash(m_asmap);
+ }
+ uint256 serialized_asmap_checksum;
+ if (format >= Format::V2_ASMAP) {
+ s >> serialized_asmap_checksum;
+ }
+ const bool restore_bucketing{nUBuckets == ADDRMAN_NEW_BUCKET_COUNT &&
+ serialized_asmap_checksum == supplied_asmap_checksum};
+
+ if (!restore_bucketing) {
+ LogPrint(BCLog::ADDRMAN, "Bucketing method was updated, re-bucketing addrman entries from disk\n");
+ }
+
+ for (auto bucket_entry : bucket_entries) {
+ int bucket{bucket_entry.first};
+ const int entry_index{bucket_entry.second};
+ CAddrInfo& info = mapInfo[entry_index];
+
+ // Don't store the entry in the new bucket if it's not a valid address for our addrman
+ if (!info.IsValid()) continue;
+
+ // The entry shouldn't appear in more than
+ // ADDRMAN_NEW_BUCKETS_PER_ADDRESS. If it has already, just skip
+ // this bucket_entry.
+ if (info.nRefCount >= ADDRMAN_NEW_BUCKETS_PER_ADDRESS) continue;
+
+ int bucket_position = info.GetBucketPosition(nKey, true, bucket);
+ if (restore_bucketing && vvNew[bucket][bucket_position] == -1) {
+ // Bucketing has not changed, using existing bucket positions for the new table
+ vvNew[bucket][bucket_position] = entry_index;
+ ++info.nRefCount;
+ } else {
+ // In case the new table data cannot be used (bucket count wrong or new asmap),
+ // try to give them a reference based on their primary source address.
+ bucket = info.GetNewBucket(nKey, m_asmap);
+ bucket_position = info.GetBucketPosition(nKey, true, bucket);
+ if (vvNew[bucket][bucket_position] == -1) {
+ vvNew[bucket][bucket_position] = entry_index;
+ ++info.nRefCount;
+ }
+ }
+ }
+
+ // Prune new entries with refcount 0 (as a result of collisions or invalid address).
+ int nLostUnk = 0;
+ for (auto it = mapInfo.cbegin(); it != mapInfo.cend(); ) {
+ if (it->second.fInTried == false && it->second.nRefCount == 0) {
+ const auto itCopy = it++;
+ Delete(itCopy->first);
+ ++nLostUnk;
+ } else {
+ ++it;
+ }
+ }
+ if (nLost + nLostUnk > 0) {
+ LogPrint(BCLog::ADDRMAN, "addrman lost %i new and %i tried addresses due to collisions or invalid addresses\n", nLostUnk, nLost);
+ }
+
+ Check();
+}
+
+// explicit instantiation
+template void CAddrMan::Serialize(CHashWriter& s) const;
+template void CAddrMan::Serialize(CAutoFile& s) const;
+template void CAddrMan::Serialize(CDataStream& s) const;
+template void CAddrMan::Unserialize(CAutoFile& s);
+template void CAddrMan::Unserialize(CHashVerifier<CAutoFile>& s);
+template void CAddrMan::Unserialize(CDataStream& s);
+template void CAddrMan::Unserialize(CHashVerifier<CDataStream>& s);
+
CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId)
{
AssertLockHeld(cs);
@@ -431,11 +748,16 @@ CAddrInfo CAddrMan::Select_(bool newOnly) const
}
}
-#ifdef DEBUG_ADDRMAN
-int CAddrMan::Check_()
+int CAddrMan::Check_() const
{
AssertLockHeld(cs);
+ // Run consistency checks 1 in m_consistency_check_ratio times if enabled
+ if (m_consistency_check_ratio == 0) return 0;
+ if (insecure_rand.randrange(m_consistency_check_ratio) >= 1) return 0;
+
+ LogPrint(BCLog::ADDRMAN, "Addrman checks started: new %i, tried %i, total %u\n", nNew, nTried, vRandom.size());
+
std::unordered_set<int> setTried;
std::unordered_map<int, int> mapNew;
@@ -458,8 +780,10 @@ int CAddrMan::Check_()
return -4;
mapNew[n] = info.nRefCount;
}
- if (mapAddr[info] != n)
+ const auto it{mapAddr.find(info)};
+ if (it == mapAddr.end() || it->second != n) {
return -5;
+ }
if (info.nRandomPos < 0 || (size_t)info.nRandomPos >= vRandom.size() || vRandom[info.nRandomPos] != n)
return -14;
if (info.nLastTry < 0)
@@ -478,10 +802,13 @@ int CAddrMan::Check_()
if (vvTried[n][i] != -1) {
if (!setTried.count(vvTried[n][i]))
return -11;
- if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n)
+ const auto it{mapInfo.find(vvTried[n][i])};
+ if (it == mapInfo.end() || it->second.GetTriedBucket(nKey, m_asmap) != n) {
return -17;
- if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i)
+ }
+ if (it->second.GetBucketPosition(nKey, false, n) != i) {
return -18;
+ }
setTried.erase(vvTried[n][i]);
}
}
@@ -492,8 +819,10 @@ int CAddrMan::Check_()
if (vvNew[n][i] != -1) {
if (!mapNew.count(vvNew[n][i]))
return -12;
- if (mapInfo[vvNew[n][i]].GetBucketPosition(nKey, true, n) != i)
+ const auto it{mapInfo.find(vvNew[n][i])};
+ if (it == mapInfo.end() || it->second.GetBucketPosition(nKey, true, n) != i) {
return -19;
+ }
if (--mapNew[vvNew[n][i]] == 0)
mapNew.erase(vvNew[n][i]);
}
@@ -507,9 +836,9 @@ int CAddrMan::Check_()
if (nKey.IsNull())
return -16;
+ LogPrint(BCLog::ADDRMAN, "Addrman checks completed successfully\n");
return 0;
}
-#endif
void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size_t max_pct, std::optional<Network> network) const
{
diff --git a/src/addrman.h b/src/addrman.h
index 736d9783e6..2548b891ba 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -26,6 +26,9 @@
#include <unordered_map>
#include <vector>
+/** Default for -checkaddrman */
+static constexpr int32_t DEFAULT_ADDRMAN_CONSISTENCY_CHECKS{0};
+
/**
* Extended statistics about a CAddress
*/
@@ -58,6 +61,7 @@ private:
mutable int nRandomPos{-1};
friend class CAddrMan;
+ friend class CAddrManDeterministic;
public:
@@ -123,53 +127,21 @@ public:
* attempt was unsuccessful.
* * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not
* be observable by adversaries.
- * * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive)
- * consistency checks for the entire data structure.
+ * * Several indexes are kept for high performance. Setting m_consistency_check_ratio with the -checkaddrman
+ * configuration option will introduce (expensive) consistency checks for the entire data structure.
*/
-//! total number of buckets for tried addresses
-#define ADDRMAN_TRIED_BUCKET_COUNT_LOG2 8
-
-//! total number of buckets for new addresses
-#define ADDRMAN_NEW_BUCKET_COUNT_LOG2 10
-
-//! maximum allowed number of entries in buckets for new and tried addresses
-#define ADDRMAN_BUCKET_SIZE_LOG2 6
-
-//! over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread
-#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8
-
-//! over how many buckets entries with new addresses originating from a single group are spread
-#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64
-
-//! in how many buckets for entries with new addresses a single address may occur
-#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8
-
-//! how old addresses can maximally be
-#define ADDRMAN_HORIZON_DAYS 30
-
-//! after how many failed attempts we give up on a new node
-#define ADDRMAN_RETRIES 3
-
-//! how many successive failures are allowed ...
-#define ADDRMAN_MAX_FAILURES 10
-
-//! ... in at least this many days
-#define ADDRMAN_MIN_FAIL_DAYS 7
-
-//! how recent a successful connection should be before we allow an address to be evicted from tried
-#define ADDRMAN_REPLACEMENT_HOURS 4
-
-//! Convenience
-#define ADDRMAN_TRIED_BUCKET_COUNT (1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2)
-#define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2)
-#define ADDRMAN_BUCKET_SIZE (1 << ADDRMAN_BUCKET_SIZE_LOG2)
+/** Total number of buckets for tried addresses */
+static constexpr int32_t ADDRMAN_TRIED_BUCKET_COUNT_LOG2{8};
+static constexpr int ADDRMAN_TRIED_BUCKET_COUNT{1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2};
-//! the maximum number of tried addr collisions to store
-#define ADDRMAN_SET_TRIED_COLLISION_SIZE 10
+/** Total number of buckets for new addresses */
+static constexpr int32_t ADDRMAN_NEW_BUCKET_COUNT_LOG2{10};
+static constexpr int ADDRMAN_NEW_BUCKET_COUNT{1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2};
-//! the maximum time we'll spend trying to resolve a tried table collision, in seconds
-static const int64_t ADDRMAN_TEST_WINDOW = 40*60; // 40 minutes
+/** Maximum allowed number of entries in buckets for new and tried addresses */
+static constexpr int32_t ADDRMAN_BUCKET_SIZE_LOG2{6};
+static constexpr int ADDRMAN_BUCKET_SIZE{1 << ADDRMAN_BUCKET_SIZE_LOG2};
/**
* Stochastical (IP) address manager
@@ -196,306 +168,13 @@ public:
// Read asmap from provided binary file
static std::vector<bool> DecodeAsmap(fs::path path);
- /**
- * Serialized format.
- * * format version byte (@see `Format`)
- * * lowest compatible format version byte. This is used to help old software decide
- * whether to parse the file. For example:
- * * Bitcoin Core version N knows how to parse up to format=3. If a new format=4 is
- * introduced in version N+1 that is compatible with format=3 and it is known that
- * version N will be able to parse it, then version N+1 will write
- * (format=4, lowest_compatible=3) in the first two bytes of the file, and so
- * version N will still try to parse it.
- * * Bitcoin Core version N+2 introduces a new incompatible format=5. It will write
- * (format=5, lowest_compatible=5) and so any versions that do not know how to parse
- * format=5 will not try to read the file.
- * * nKey
- * * nNew
- * * nTried
- * * number of "new" buckets XOR 2**30
- * * all new addresses (total count: nNew)
- * * all tried addresses (total count: nTried)
- * * for each new bucket:
- * * number of elements
- * * for each element: index in the serialized "all new addresses"
- * * asmap checksum
- *
- * 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it
- * as incompatible. This is necessary because it did not check the version number on
- * deserialization.
- *
- * vvNew, vvTried, mapInfo, mapAddr and vRandom are never encoded explicitly;
- * they are instead reconstructed from the other information.
- *
- * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
- * changes to the ADDRMAN_ parameters without breaking the on-disk structure.
- *
- * We don't use SERIALIZE_METHODS since the serialization and deserialization code has
- * very little in common.
- */
template <typename Stream>
- void Serialize(Stream& s_) const
- EXCLUSIVE_LOCKS_REQUIRED(!cs)
- {
- LOCK(cs);
-
- // Always serialize in the latest version (FILE_FORMAT).
-
- OverrideStream<Stream> s(&s_, s_.GetType(), s_.GetVersion() | ADDRV2_FORMAT);
-
- s << static_cast<uint8_t>(FILE_FORMAT);
-
- // Increment `lowest_compatible` iff a newly introduced format is incompatible with
- // the previous one.
- static constexpr uint8_t lowest_compatible = Format::V3_BIP155;
- s << static_cast<uint8_t>(INCOMPATIBILITY_BASE + lowest_compatible);
-
- s << nKey;
- s << nNew;
- s << nTried;
-
- int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
- s << nUBuckets;
- std::unordered_map<int, int> mapUnkIds;
- int nIds = 0;
- for (const auto& entry : mapInfo) {
- mapUnkIds[entry.first] = nIds;
- const CAddrInfo &info = entry.second;
- if (info.nRefCount) {
- assert(nIds != nNew); // this means nNew was wrong, oh ow
- s << info;
- nIds++;
- }
- }
- nIds = 0;
- for (const auto& entry : mapInfo) {
- const CAddrInfo &info = entry.second;
- if (info.fInTried) {
- assert(nIds != nTried); // this means nTried was wrong, oh ow
- s << info;
- nIds++;
- }
- }
- for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
- int nSize = 0;
- for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
- if (vvNew[bucket][i] != -1)
- nSize++;
- }
- s << nSize;
- for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
- if (vvNew[bucket][i] != -1) {
- int nIndex = mapUnkIds[vvNew[bucket][i]];
- s << nIndex;
- }
- }
- }
- // Store asmap checksum after bucket entries so that it
- // can be ignored by older clients for backward compatibility.
- uint256 asmap_checksum;
- if (m_asmap.size() != 0) {
- asmap_checksum = SerializeHash(m_asmap);
- }
- s << asmap_checksum;
- }
+ void Serialize(Stream& s_) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
template <typename Stream>
- void Unserialize(Stream& s_)
- EXCLUSIVE_LOCKS_REQUIRED(!cs)
- {
- LOCK(cs);
-
- assert(vRandom.empty());
-
- Format format;
- s_ >> Using<CustomUintFormatter<1>>(format);
-
- int stream_version = s_.GetVersion();
- if (format >= Format::V3_BIP155) {
- // Add ADDRV2_FORMAT to the version so that the CNetAddr and CAddress
- // unserialize methods know that an address in addrv2 format is coming.
- stream_version |= ADDRV2_FORMAT;
- }
-
- OverrideStream<Stream> s(&s_, s_.GetType(), stream_version);
-
- uint8_t compat;
- s >> compat;
- const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE;
- if (lowest_compatible > FILE_FORMAT) {
- throw std::ios_base::failure(strprintf(
- "Unsupported format of addrman database: %u. It is compatible with formats >=%u, "
- "but the maximum supported by this version of %s is %u.",
- format, lowest_compatible, PACKAGE_NAME, static_cast<uint8_t>(FILE_FORMAT)));
- }
-
- s >> nKey;
- s >> nNew;
- s >> nTried;
- int nUBuckets = 0;
- s >> nUBuckets;
- if (format >= Format::V1_DETERMINISTIC) {
- nUBuckets ^= (1 << 30);
- }
-
- if (nNew > ADDRMAN_NEW_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE || nNew < 0) {
- throw std::ios_base::failure(
- strprintf("Corrupt CAddrMan serialization: nNew=%d, should be in [0, %u]",
- nNew,
- ADDRMAN_NEW_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE));
- }
-
- if (nTried > ADDRMAN_TRIED_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE || nTried < 0) {
- throw std::ios_base::failure(
- strprintf("Corrupt CAddrMan serialization: nTried=%d, should be in [0, %u]",
- nTried,
- ADDRMAN_TRIED_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE));
- }
-
- // Deserialize entries from the new table.
- for (int n = 0; n < nNew; n++) {
- CAddrInfo &info = mapInfo[n];
- s >> info;
- mapAddr[info] = n;
- info.nRandomPos = vRandom.size();
- vRandom.push_back(n);
- }
- nIdCount = nNew;
-
- // Deserialize entries from the tried table.
- int nLost = 0;
- for (int n = 0; n < nTried; n++) {
- CAddrInfo info;
- s >> info;
- int nKBucket = info.GetTriedBucket(nKey, m_asmap);
- int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
- if (info.IsValid()
- && vvTried[nKBucket][nKBucketPos] == -1) {
- info.nRandomPos = vRandom.size();
- info.fInTried = true;
- vRandom.push_back(nIdCount);
- mapInfo[nIdCount] = info;
- mapAddr[info] = nIdCount;
- vvTried[nKBucket][nKBucketPos] = nIdCount;
- nIdCount++;
- } else {
- nLost++;
- }
- }
- nTried -= nLost;
-
- // Store positions in the new table buckets to apply later (if possible).
- // An entry may appear in up to ADDRMAN_NEW_BUCKETS_PER_ADDRESS buckets,
- // so we store all bucket-entry_index pairs to iterate through later.
- std::vector<std::pair<int, int>> bucket_entries;
-
- for (int bucket = 0; bucket < nUBuckets; ++bucket) {
- int num_entries{0};
- s >> num_entries;
- for (int n = 0; n < num_entries; ++n) {
- int entry_index{0};
- s >> entry_index;
- if (entry_index >= 0 && entry_index < nNew) {
- bucket_entries.emplace_back(bucket, entry_index);
- }
- }
- }
-
- // If the bucket count and asmap checksum haven't changed, then attempt
- // to restore the entries to the buckets/positions they were in before
- // serialization.
- uint256 supplied_asmap_checksum;
- if (m_asmap.size() != 0) {
- supplied_asmap_checksum = SerializeHash(m_asmap);
- }
- uint256 serialized_asmap_checksum;
- if (format >= Format::V2_ASMAP) {
- s >> serialized_asmap_checksum;
- }
- const bool restore_bucketing{nUBuckets == ADDRMAN_NEW_BUCKET_COUNT &&
- serialized_asmap_checksum == supplied_asmap_checksum};
-
- if (!restore_bucketing) {
- LogPrint(BCLog::ADDRMAN, "Bucketing method was updated, re-bucketing addrman entries from disk\n");
- }
-
- for (auto bucket_entry : bucket_entries) {
- int bucket{bucket_entry.first};
- const int entry_index{bucket_entry.second};
- CAddrInfo& info = mapInfo[entry_index];
-
- // Don't store the entry in the new bucket if it's not a valid address for our addrman
- if (!info.IsValid()) continue;
-
- // The entry shouldn't appear in more than
- // ADDRMAN_NEW_BUCKETS_PER_ADDRESS. If it has already, just skip
- // this bucket_entry.
- if (info.nRefCount >= ADDRMAN_NEW_BUCKETS_PER_ADDRESS) continue;
-
- int bucket_position = info.GetBucketPosition(nKey, true, bucket);
- if (restore_bucketing && vvNew[bucket][bucket_position] == -1) {
- // Bucketing has not changed, using existing bucket positions for the new table
- vvNew[bucket][bucket_position] = entry_index;
- ++info.nRefCount;
- } else {
- // In case the new table data cannot be used (bucket count wrong or new asmap),
- // try to give them a reference based on their primary source address.
- bucket = info.GetNewBucket(nKey, m_asmap);
- bucket_position = info.GetBucketPosition(nKey, true, bucket);
- if (vvNew[bucket][bucket_position] == -1) {
- vvNew[bucket][bucket_position] = entry_index;
- ++info.nRefCount;
- }
- }
- }
+ void Unserialize(Stream& s_) EXCLUSIVE_LOCKS_REQUIRED(!cs);
- // Prune new entries with refcount 0 (as a result of collisions or invalid address).
- int nLostUnk = 0;
- for (auto it = mapInfo.cbegin(); it != mapInfo.cend(); ) {
- if (it->second.fInTried == false && it->second.nRefCount == 0) {
- const auto itCopy = it++;
- Delete(itCopy->first);
- ++nLostUnk;
- } else {
- ++it;
- }
- }
- if (nLost + nLostUnk > 0) {
- LogPrint(BCLog::ADDRMAN, "addrman lost %i new and %i tried addresses due to collisions or invalid addresses\n", nLostUnk, nLost);
- }
-
- Check();
- }
-
- void Clear()
- EXCLUSIVE_LOCKS_REQUIRED(!cs)
- {
- LOCK(cs);
- std::vector<int>().swap(vRandom);
- nKey = insecure_rand.rand256();
- for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
- for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
- vvNew[bucket][entry] = -1;
- }
- }
- for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; bucket++) {
- for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
- vvTried[bucket][entry] = -1;
- }
- }
-
- nIdCount = 0;
- nTried = 0;
- nNew = 0;
- nLastGood = 1; //Initially at 1 so that "never" is strictly worse.
- mapInfo.clear();
- mapAddr.clear();
- }
-
- CAddrMan()
- {
- Clear();
- }
+ explicit CAddrMan(bool deterministic, int32_t consistency_check_ratio);
~CAddrMan()
{
@@ -510,22 +189,7 @@ public:
return vRandom.size();
}
- //! Add a single address.
- bool Add(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty = 0)
- EXCLUSIVE_LOCKS_REQUIRED(!cs)
- {
- LOCK(cs);
- bool fRet = false;
- Check();
- fRet |= Add_(addr, source, nTimePenalty);
- Check();
- if (fRet) {
- LogPrint(BCLog::ADDRMAN, "Added %s from %s: %i tried, %i new\n", addr.ToStringIPPort(), source.ToString(), nTried, nNew);
- }
- return fRet;
- }
-
- //! Add multiple addresses.
+ //! Add addresses to addrman's new table.
bool Add(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0)
EXCLUSIVE_LOCKS_REQUIRED(!cs)
{
@@ -632,17 +296,16 @@ public:
Check();
}
-protected:
- //! secret key to randomize bucket select with
- uint256 nKey;
+private:
+ //! A mutex to protect the inner data structures.
+ mutable Mutex cs;
//! Source of random numbers for randomization in inner loops
mutable FastRandomContext insecure_rand GUARDED_BY(cs);
- //! A mutex to protect the inner data structures.
- mutable Mutex cs;
+ //! secret key to randomize bucket select with
+ uint256 nKey;
-private:
//! Serialization versions.
enum Format : uint8_t {
V0_HISTORICAL = 0, //!< historic format, before commit e6b343d88
@@ -666,7 +329,7 @@ private:
static constexpr uint8_t INCOMPATIBILITY_BASE = 32;
//! last used nId
- int nIdCount GUARDED_BY(cs);
+ int nIdCount GUARDED_BY(cs){0};
//! table with information about all nIds
std::unordered_map<int, CAddrInfo> mapInfo GUARDED_BY(cs);
@@ -680,23 +343,26 @@ private:
mutable std::vector<int> vRandom GUARDED_BY(cs);
// number of "tried" entries
- int nTried GUARDED_BY(cs);
+ int nTried GUARDED_BY(cs){0};
//! list of "tried" buckets
int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs);
//! number of (unique) "new" entries
- int nNew GUARDED_BY(cs);
+ int nNew GUARDED_BY(cs){0};
//! list of "new" buckets
int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs);
- //! last time Good was called (memory only)
- int64_t nLastGood GUARDED_BY(cs);
+ //! last time Good was called (memory only). Initially set to 1 so that "never" is strictly worse.
+ int64_t nLastGood GUARDED_BY(cs){1};
//! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discipline used to resolve these collisions.
std::set<int> m_tried_collisions;
+ /** Perform consistency checks every m_consistency_check_ratio operations (if non-zero). */
+ const int32_t m_consistency_check_ratio;
+
//! Find an entry.
CAddrInfo* Find(const CNetAddr& addr, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
@@ -734,22 +400,19 @@ private:
CAddrInfo SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs);
//! Consistency check
- void Check() const
- EXCLUSIVE_LOCKS_REQUIRED(cs)
+ void Check() const EXCLUSIVE_LOCKS_REQUIRED(cs)
{
-#ifdef DEBUG_ADDRMAN
AssertLockHeld(cs);
+
const int err = Check_();
if (err) {
LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err);
+ assert(false);
}
-#endif
}
-#ifdef DEBUG_ADDRMAN
//! Perform consistency check. Returns an error code or zero.
int Check_() const EXCLUSIVE_LOCKS_REQUIRED(cs);
-#endif
/**
* Return all or many randomly selected addresses, optionally by network.
@@ -778,6 +441,7 @@ private:
void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
friend class CAddrManTest;
+ friend class CAddrManDeterministic;
};
#endif // BITCOIN_ADDRMAN_H
diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp
index b7bd8a3261..d69a651811 100644
--- a/src/bench/addrman.cpp
+++ b/src/bench/addrman.cpp
@@ -72,17 +72,15 @@ static void AddrManAdd(benchmark::Bench& bench)
{
CreateAddresses();
- CAddrMan addrman;
-
bench.run([&] {
+ CAddrMan addrman{/* deterministic */ false, /* consistency_check_ratio */ 0};
AddAddressesToAddrMan(addrman);
- addrman.Clear();
});
}
static void AddrManSelect(benchmark::Bench& bench)
{
- CAddrMan addrman;
+ CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0);
FillAddrMan(addrman);
@@ -94,7 +92,7 @@ static void AddrManSelect(benchmark::Bench& bench)
static void AddrManGetAddr(benchmark::Bench& bench)
{
- CAddrMan addrman;
+ CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0);
FillAddrMan(addrman);
@@ -112,10 +110,12 @@ static void AddrManGood(benchmark::Bench& bench)
* we want to do the same amount of work in every loop iteration. */
bench.epochs(5).epochIterations(1);
+ const size_t addrman_count{bench.epochs() * bench.epochIterations()};
- std::vector<CAddrMan> addrmans(bench.epochs() * bench.epochIterations());
- for (auto& addrman : addrmans) {
- FillAddrMan(addrman);
+ std::vector<std::unique_ptr<CAddrMan>> addrmans(addrman_count);
+ for (size_t i{0}; i < addrman_count; ++i) {
+ addrmans[i] = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ 0);
+ FillAddrMan(*addrmans[i]);
}
auto markSomeAsGood = [](CAddrMan& addrman) {
@@ -130,7 +130,7 @@ static void AddrManGood(benchmark::Bench& bench)
uint64_t i = 0;
bench.run([&] {
- markSomeAsGood(addrmans.at(i));
+ markSomeAsGood(*addrmans.at(i));
++i;
});
}
diff --git a/src/bitcoin-cli-res.rc b/src/bitcoin-cli-res.rc
index 405a302261..d9e5dcf7fd 100644
--- a/src/bitcoin-cli-res.rc
+++ b/src/bitcoin-cli-res.rc
@@ -2,9 +2,7 @@
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
-#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
-#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -18,13 +16,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoin-cli (JSON-RPC client for " PACKAGE_NAME ")"
- VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "FileVersion", PACKAGE_VERSION
VALUE "InternalName", "bitcoin-cli"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoin-cli.exe"
VALUE "ProductName", "bitcoin-cli"
- VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ VALUE "ProductVersion", PACKAGE_VERSION
END
END
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 1ec6411e32..34785bf6a4 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -885,6 +885,29 @@ static void GetWalletBalances(UniValue& result)
}
/**
+ * GetProgressBar constructs a progress bar with 5% intervals.
+ *
+ * @param[in] progress The proportion of the progress bar to be filled between 0 and 1.
+ * @param[out] progress_bar String representation of the progress bar.
+ */
+static void GetProgressBar(double progress, std::string& progress_bar)
+{
+ if (progress < 0 || progress > 1) return;
+
+ static constexpr double INCREMENT{0.05};
+ static const std::string COMPLETE_BAR{"\u2592"};
+ static const std::string INCOMPLETE_BAR{"\u2591"};
+
+ for (int i = 0; i < progress / INCREMENT; ++i) {
+ progress_bar += COMPLETE_BAR;
+ }
+
+ for (int i = 0; i < (1 - progress) / INCREMENT; ++i) {
+ progress_bar += INCOMPLETE_BAR;
+ }
+}
+
+/**
* ParseGetInfoResult takes in -getinfo result in UniValue object and parses it
* into a user friendly UniValue string to be printed on the console.
* @param[out] result Reference to UniValue result containing the -getinfo output.
@@ -926,7 +949,17 @@ static void ParseGetInfoResult(UniValue& result)
std::string result_string = strprintf("%sChain: %s%s\n", BLUE, result["chain"].getValStr(), RESET);
result_string += strprintf("Blocks: %s\n", result["blocks"].getValStr());
result_string += strprintf("Headers: %s\n", result["headers"].getValStr());
- result_string += strprintf("Verification progress: %.4f%%\n", result["verificationprogress"].get_real() * 100);
+
+ const double ibd_progress{result["verificationprogress"].get_real()};
+ std::string ibd_progress_bar;
+ // Display the progress bar only if IBD progress is less than 99%
+ if (ibd_progress < 0.99) {
+ GetProgressBar(ibd_progress, ibd_progress_bar);
+ // Add padding between progress bar and IBD progress
+ ibd_progress_bar += " ";
+ }
+
+ result_string += strprintf("Verification progress: %s%.4f%%\n", ibd_progress_bar, ibd_progress * 100);
result_string += strprintf("Difficulty: %s\n\n", result["difficulty"].getValStr());
result_string += strprintf(
diff --git a/src/bitcoin-tx-res.rc b/src/bitcoin-tx-res.rc
index b545ce9dbe..46e4fc9274 100644
--- a/src/bitcoin-tx-res.rc
+++ b/src/bitcoin-tx-res.rc
@@ -2,9 +2,7 @@
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
-#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
-#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -18,13 +16,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoin-tx (CLI Bitcoin transaction editor utility)"
- VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "FileVersion", PACKAGE_VERSION
VALUE "InternalName", "bitcoin-tx"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoin-tx.exe"
VALUE "ProductName", "bitcoin-tx"
- VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ VALUE "ProductVersion", PACKAGE_VERSION
END
END
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index 3fc87ae1ff..cd2cdf01f2 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -188,10 +188,11 @@ static void RegisterLoad(const std::string& strInput)
static CAmount ExtractAndValidateValue(const std::string& strValue)
{
- CAmount value;
- if (!ParseMoney(strValue, value))
+ if (std::optional<CAmount> parsed = ParseMoney(strValue)) {
+ return parsed.value();
+ } else {
throw std::runtime_error("invalid TX output value");
- return value;
+ }
}
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)
diff --git a/src/bitcoin-util-res.rc b/src/bitcoin-util-res.rc
index 3f0fa8ab6d..0de8c5befa 100644
--- a/src/bitcoin-util-res.rc
+++ b/src/bitcoin-util-res.rc
@@ -2,9 +2,7 @@
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
-#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
-#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -18,13 +16,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoin-util (CLI Bitcoin utility)"
- VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "FileVersion", PACKAGE_VERSION
VALUE "InternalName", "bitcoin-util"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoin-util.exe"
VALUE "ProductName", "bitcoin-util"
- VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ VALUE "ProductVersion", PACKAGE_VERSION
END
END
diff --git a/src/bitcoin-wallet-res.rc b/src/bitcoin-wallet-res.rc
index 59346ab8f6..d86ffbd9f1 100644
--- a/src/bitcoin-wallet-res.rc
+++ b/src/bitcoin-wallet-res.rc
@@ -2,9 +2,7 @@
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
-#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
-#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -18,13 +16,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoin-wallet (CLI tool for " PACKAGE_NAME " wallets)"
- VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "FileVersion", PACKAGE_VERSION
VALUE "InternalName", "bitcoin-wallet"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoin-wallet.exe"
VALUE "ProductName", "bitcoin-wallet"
- VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ VALUE "ProductVersion", PACKAGE_VERSION
END
END
diff --git a/src/bitcoind-res.rc b/src/bitcoind-res.rc
index a98b50c899..353761dfa7 100644
--- a/src/bitcoind-res.rc
+++ b/src/bitcoind-res.rc
@@ -2,9 +2,7 @@
#include "clientversion.h" // holds the needed client version information
#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD
-#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
#define VER_FILEVERSION VER_PRODUCTVERSION
-#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -18,13 +16,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Bitcoin"
VALUE "FileDescription", "bitcoind (Bitcoin node with a JSON-RPC server)"
- VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "FileVersion", PACKAGE_VERSION
VALUE "InternalName", "bitcoind"
VALUE "LegalCopyright", COPYRIGHT_STR
VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php."
VALUE "OriginalFilename", "bitcoind.exe"
VALUE "ProductName", "bitcoind"
- VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ VALUE "ProductVersion", PACKAGE_VERSION
END
END
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 0b3242b1aa..4cc37560a3 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -392,8 +392,8 @@ public:
consensus.BIP16Exception = uint256();
consensus.BIP34Height = 2; // BIP34 activated on regtest (Block at height 1 not enforced for testing purposes)
consensus.BIP34Hash = uint256();
- consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in functional tests)
- consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in functional tests)
+ consensus.BIP65Height = 111; // BIP65 activated on regtest (Block at height 110 and earlier not enforced for testing purposes)
+ consensus.BIP66Height = 102; // BIP66 activated on regtest (Block at height 101 and earlier not enforced for testing purposes)
consensus.CSVHeight = 432; // CSV activated on regtest (Used in rpc activation tests)
consensus.SegwitHeight = 0; // SEGWIT is always activated on regtest unless overridden
consensus.MinBIP9WarningHeight = 0;
diff --git a/src/clientversion.cpp b/src/clientversion.cpp
index 29c38e2d3b..f97e4097e8 100644
--- a/src/clientversion.cpp
+++ b/src/clientversion.cpp
@@ -30,8 +30,10 @@ const std::string CLIENT_NAME("Satoshi");
#define BUILD_DESC BUILD_GIT_TAG
#define BUILD_SUFFIX ""
#else
- #define BUILD_DESC "v" STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD)
- #ifdef BUILD_GIT_COMMIT
+ #define BUILD_DESC "v" PACKAGE_VERSION
+ #if CLIENT_VERSION_IS_RELEASE
+ #define BUILD_SUFFIX ""
+ #elif defined(BUILD_GIT_COMMIT)
#define BUILD_SUFFIX "-" BUILD_GIT_COMMIT
#elif defined(GIT_COMMIT_ID)
#define BUILD_SUFFIX "-g" GIT_COMMIT_ID
@@ -40,8 +42,6 @@ const std::string CLIENT_NAME("Satoshi");
#endif
#endif
-const std::string CLIENT_BUILD(BUILD_DESC BUILD_SUFFIX);
-
static std::string FormatVersion(int nVersion)
{
return strprintf("%d.%d.%d", nVersion / 10000, (nVersion / 100) % 100, nVersion % 100);
@@ -49,6 +49,7 @@ static std::string FormatVersion(int nVersion)
std::string FormatFullVersion()
{
+ static const std::string CLIENT_BUILD(BUILD_DESC BUILD_SUFFIX);
return CLIENT_BUILD;
}
diff --git a/src/clientversion.h b/src/clientversion.h
index 0ed3f68094..a3e6233437 100644
--- a/src/clientversion.h
+++ b/src/clientversion.h
@@ -36,7 +36,6 @@ static const int CLIENT_VERSION =
+ 1 * CLIENT_VERSION_BUILD;
extern const std::string CLIENT_NAME;
-extern const std::string CLIENT_BUILD;
std::string FormatFullVersion();
diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp
index 0582a60c4f..b73b22a2b8 100644
--- a/src/crypto/chacha_poly_aead.cpp
+++ b/src/crypto/chacha_poly_aead.cpp
@@ -31,8 +31,9 @@ ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_
{
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
- m_chacha_main.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
- m_chacha_header.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
+
+ m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
+ m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
// set the cached sequence number to uint64 max which hints for an unset cache.
// we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index 95886d3138..2d897f4c40 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -28,6 +28,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
"-addresstype",
"-avoidpartialspends",
"-changetype",
+ "-consolidatefeerate=<amt>",
"-disablewallet",
"-discardfee=<amt>",
"-fallbackfee=<amt>",
diff --git a/src/fs.cpp b/src/fs.cpp
index 4f20ca4d28..b9b3c46d8d 100644
--- a/src/fs.cpp
+++ b/src/fs.cpp
@@ -154,7 +154,10 @@ std::string get_filesystem_error_message(const fs::filesystem_error& e)
#ifdef __GLIBCXX__
// reference: https://github.com/gcc-mirror/gcc/blob/gcc-7_3_0-release/libstdc%2B%2B-v3/include/std/fstream#L270
-
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
static std::string openmodeToStr(std::ios_base::openmode mode)
{
switch (mode & ~std::ios_base::ate) {
@@ -192,6 +195,9 @@ static std::string openmodeToStr(std::ios_base::openmode mode)
return std::string();
}
}
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
void ifstream::open(const fs::path& p, std::ios_base::openmode mode)
{
@@ -242,7 +248,11 @@ void ofstream::close()
}
#else // __GLIBCXX__
+#if BOOST_VERSION >= 107700
+static_assert(sizeof(*BOOST_FILESYSTEM_C_STR(fs::path())) == sizeof(wchar_t),
+#else
static_assert(sizeof(*fs::path().BOOST_FILESYSTEM_C_STR) == sizeof(wchar_t),
+#endif // BOOST_VERSION >= 107700
"Warning: This build is using boost::filesystem ofstream and ifstream "
"implementations which will fail to open paths containing multibyte "
"characters. You should delete this static_assert to ignore this warning, "
diff --git a/src/init.cpp b/src/init.cpp
index 1b406bed28..d4a3c891b1 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -501,7 +501,8 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.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);
argsman.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of -checkblocks is: %s (0-4, default: %u)", Join(CHECKLEVEL_DOC, ", "), DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
- argsman.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);
+ argsman.AddArg("-checkaddrman=<n>", strprintf("Run addrman consistency checks every <n> operations. Use 0 to disable. (default: %u)", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-checkmempool=<n>", strprintf("Run mempool consistency checks every <n> transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
@@ -859,6 +860,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
return InitError(Untranslated("Cannot set -bind or -whitebind together with -listen=0"));
}
+ // if listen=0, then disallow listenonion=1
+ if (!args.GetBoolArg("-listen", DEFAULT_LISTEN) && args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
+ return InitError(Untranslated("Cannot set -listen=0 together with -listenonion=1"));
+ }
+
// Make sure enough file descriptors are available
int nBind = std::max(nUserBind, size_t(1));
nUserMaxConnections = args.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
@@ -915,10 +921,11 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// incremental relay fee sets the minimum feerate increase necessary for BIP 125 replacement in the mempool
// and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
if (args.IsArgSet("-incrementalrelayfee")) {
- CAmount n = 0;
- if (!ParseMoney(args.GetArg("-incrementalrelayfee", ""), n))
+ if (std::optional<CAmount> inc_relay_fee = ParseMoney(args.GetArg("-incrementalrelayfee", ""))) {
+ ::incrementalRelayFee = CFeeRate{inc_relay_fee.value()};
+ } else {
return InitError(AmountErrMsg("incrementalrelayfee", args.GetArg("-incrementalrelayfee", "")));
- incrementalRelayFee = CFeeRate(n);
+ }
}
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
@@ -950,12 +957,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
if (args.IsArgSet("-minrelaytxfee")) {
- CAmount n = 0;
- if (!ParseMoney(args.GetArg("-minrelaytxfee", ""), n)) {
+ if (std::optional<CAmount> min_relay_fee = ParseMoney(args.GetArg("-minrelaytxfee", ""))) {
+ // High fee check is done afterward in CWallet::Create()
+ ::minRelayTxFee = CFeeRate{min_relay_fee.value()};
+ } else {
return InitError(AmountErrMsg("minrelaytxfee", args.GetArg("-minrelaytxfee", "")));
}
- // High fee check is done afterward in CWallet::Create()
- ::minRelayTxFee = CFeeRate(n);
} else if (incrementalRelayFee > ::minRelayTxFee) {
// Allow only setting incrementalRelayFee to control both
::minRelayTxFee = incrementalRelayFee;
@@ -965,18 +972,19 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that happens
if (args.IsArgSet("-blockmintxfee")) {
- CAmount n = 0;
- if (!ParseMoney(args.GetArg("-blockmintxfee", ""), n))
+ if (!ParseMoney(args.GetArg("-blockmintxfee", ""))) {
return InitError(AmountErrMsg("blockmintxfee", args.GetArg("-blockmintxfee", "")));
+ }
}
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions
if (args.IsArgSet("-dustrelayfee")) {
- CAmount n = 0;
- if (!ParseMoney(args.GetArg("-dustrelayfee", ""), n))
+ if (std::optional<CAmount> parsed = ParseMoney(args.GetArg("-dustrelayfee", ""))) {
+ dustRelayFee = CFeeRate{parsed.value()};
+ } else {
return InitError(AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", "")));
- dustRelayFee = CFeeRate(n);
+ }
}
fRequireStandard = !args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
@@ -1164,7 +1172,22 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
const bool ignores_incoming_txs{args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)};
assert(!node.addrman);
- node.addrman = std::make_unique<CAddrMan>();
+ auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
+ node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+ {
+ // Load addresses from peers.dat
+ uiInterface.InitMessage(_("Loading P2P addresses…").translated);
+ int64_t nStart = GetTimeMillis();
+ CAddrDB adb;
+ if (adb.Read(*node.addrman)) {
+ LogPrintf("Loaded %i addresses from peers.dat %dms\n", node.addrman->size(), GetTimeMillis() - nStart);
+ } else {
+ // Addrman can be in an inconsistent state after failure, reset it
+ node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+ LogPrintf("Recreating peers.dat\n");
+ adb.Write(*node.addrman);
+ }
+ }
assert(!node.banman);
node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index 7cac435e96..eceede3c8f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -35,7 +35,9 @@ namespace interfaces {
class Handler;
class Wallet;
-//! Helper for findBlock to selectively return pieces of block data.
+//! Helper for findBlock to selectively return pieces of block data. If block is
+//! found, data will be returned by setting specified output variables. If block
+//! is not found, output variables will keep their previous values.
class FoundBlock
{
public:
@@ -60,6 +62,7 @@ public:
bool* m_in_active_chain = nullptr;
const FoundBlock* m_next_block = nullptr;
CBlock* m_data = nullptr;
+ mutable bool found = false;
};
//! Interface giving clients (wallet processes, maybe other analysis tools in
@@ -262,11 +265,18 @@ public:
//! Current RPC serialization flags.
virtual int rpcSerializationFlags() = 0;
+ //! Get settings value.
+ virtual util::SettingsValue getSetting(const std::string& arg) = 0;
+
+ //! Get list of settings values.
+ virtual std::vector<util::SettingsValue> getSettingsList(const std::string& arg) = 0;
+
//! Return <datadir>/settings.json setting value.
virtual util::SettingsValue getRwSetting(const std::string& name) = 0;
- //! Write a setting to <datadir>/settings.json.
- virtual bool updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0;
+ //! Write a setting to <datadir>/settings.json. Optionally just update the
+ //! setting in memory and do not write the file.
+ virtual bool updateRwSetting(const std::string& name, const util::SettingsValue& value, bool write=true) = 0;
//! Synchronously send transactionAddedToMempool notifications about all
//! current mempool transactions to the specified handler and return after
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index fb1febc11b..a85db04b8b 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -332,6 +332,9 @@ public:
//! loaded at startup or by RPC.
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
+
+ //! Return pointer to internal context, useful for testing.
+ virtual WalletContext* context() { return nullptr; }
};
//! Information about one wallet address.
@@ -410,7 +413,7 @@ struct WalletTxOut
//! Return implementation of Wallet interface. This function is defined in
//! dummywallet.cpp and throws if the wallet component is not compiled.
-std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet);
+std::unique_ptr<Wallet> MakeWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
//! Return implementation of ChainClient interface for a wallet client. This
//! function will be undefined in builds where ENABLE_WALLET is false.
diff --git a/src/key.cpp b/src/key.cpp
index 7bef3d529b..40df248e02 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -357,6 +357,7 @@ void CExtKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8];
memcpy(chaincode.begin(), code+9, 32);
key.Set(code+42, code+BIP32_EXTKEY_SIZE, true);
+ if ((nDepth == 0 && (nChild != 0 || ReadLE32(vchFingerprint) != 0)) || code[41] != 0) key = CKey();
}
bool ECC_InitSanityCheck() {
diff --git a/src/key.h b/src/key.h
index d47e54800c..92cbc1e899 100644
--- a/src/key.h
+++ b/src/key.h
@@ -133,10 +133,15 @@ public:
* optionally tweaked by *merkle_root. Additional nonce entropy can be provided through
* aux.
*
- * When merkle_root is not nullptr, this results in a signature with a modified key as
- * specified in BIP341:
- * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G
- * - Otherwise: key + H_TapTweak(pubkey || *merkle_root)
+ * merkle_root is used to optionally perform tweaking of the private key, as specified
+ * in BIP341:
+ * - If merkle_root == nullptr: no tweaking is done, sign with key directly (this is
+ * used for signatures in BIP342 script).
+ * - If merkle_root->IsNull(): sign with key + H_TapTweak(pubkey) (this is used for
+ * key path spending when no scripts are present).
+ * - Otherwise: sign with key + H_TapTweak(pubkey || *merkle_root)
+ * (this is used for key path spending, with specific
+ * Merkle root of the script tree).
*/
bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const;
diff --git a/src/miner.cpp b/src/miner.cpp
index d9186a5d6d..168ade5507 100644
--- a/src/miner.cpp
+++ b/src/miner.cpp
@@ -73,11 +73,11 @@ static BlockAssembler::Options DefaultOptions()
// If -blockmaxweight is not given, limit to DEFAULT_BLOCK_MAX_WEIGHT
BlockAssembler::Options options;
options.nBlockMaxWeight = gArgs.GetArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
- CAmount n = 0;
- if (gArgs.IsArgSet("-blockmintxfee") && ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) {
- options.blockMinFeeRate = CFeeRate(n);
+ if (gArgs.IsArgSet("-blockmintxfee")) {
+ std::optional<CAmount> parsed = ParseMoney(gArgs.GetArg("-blockmintxfee", ""));
+ options.blockMinFeeRate = CFeeRate{parsed.value_or(DEFAULT_BLOCK_MIN_TX_FEE)};
} else {
- options.blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
+ options.blockMinFeeRate = CFeeRate{DEFAULT_BLOCK_MIN_TX_FEE};
}
return options;
}
diff --git a/src/net.cpp b/src/net.cpp
index 8ef770ede2..9b1e17c587 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -331,7 +331,7 @@ CNode* CConnman::FindNode(const std::string& addrName)
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
- if (pnode->GetAddrName() == addrName) {
+ if (pnode->m_addr_name == addrName) {
return pnode;
}
}
@@ -414,14 +414,10 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
return nullptr;
}
// It is possible that we already have a connection to the IP/port pszDest resolved to.
- // In that case, drop the connection that was just created, and return the existing CNode instead.
- // Also store the name we used to connect in that CNode, so that future FindNode() calls to that
- // name catch this early.
+ // In that case, drop the connection that was just created.
LOCK(cs_vNodes);
CNode* pnode = FindNode(static_cast<CService>(addrConnect));
- if (pnode)
- {
- pnode->MaybeSetAddrName(std::string(pszDest));
+ if (pnode) {
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
@@ -534,19 +530,8 @@ std::string ConnectionTypeAsString(ConnectionType conn_type)
assert(false);
}
-std::string CNode::GetAddrName() const {
- LOCK(cs_addrName);
- return addrName;
-}
-
-void CNode::MaybeSetAddrName(const std::string& addrNameIn) {
- LOCK(cs_addrName);
- if (addrName.empty()) {
- addrName = addrNameIn;
- }
-}
-
-CService CNode::GetAddrLocal() const {
+CService CNode::GetAddrLocal() const
+{
LOCK(cs_addrLocal);
return addrLocal;
}
@@ -587,7 +572,7 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
X(nLastBlockTime);
X(nTimeConnected);
X(nTimeOffset);
- stats.addrName = GetAddrName();
+ X(m_addr_name);
X(nVersion);
{
LOCK(cs_SubVer);
@@ -1304,8 +1289,9 @@ void CConnman::NotifyNumConnectionsChanged()
}
if(vNodesSize != nPrevNodeCount) {
nPrevNodeCount = vNodesSize;
- if(clientInterface)
- clientInterface->NotifyNumConnectionsChanged(vNodesSize);
+ if (m_client_interface) {
+ m_client_interface->NotifyNumConnectionsChanged(vNodesSize);
+ }
}
}
@@ -2136,7 +2122,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
if (pnode->addr.IsValid()) {
mapConnected[pnode->addr] = pnode->IsInboundConn();
}
- std::string addrName = pnode->GetAddrName();
+ std::string addrName{pnode->m_addr_name};
if (!addrName.empty()) {
mapConnectedByName[std::move(addrName)] = std::make_pair(pnode->IsInboundConn(), static_cast<const CService&>(pnode->addr));
}
@@ -2448,7 +2434,9 @@ void CConnman::SetNetworkActive(bool active)
fNetworkActive = active;
- uiInterface.NotifyNetworkActiveChanged(fNetworkActive);
+ if (m_client_interface) {
+ m_client_interface->NotifyNetworkActiveChanged(fNetworkActive);
+ }
}
CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, CAddrMan& addrman_in, bool network_active)
@@ -2473,8 +2461,8 @@ bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags
}
bilingual_str strError;
if (!BindListenPort(addr, strError, permissions)) {
- if ((flags & BF_REPORT_ERROR) && clientInterface) {
- clientInterface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR);
+ if ((flags & BF_REPORT_ERROR) && m_client_interface) {
+ m_client_interface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR);
}
return false;
}
@@ -2513,8 +2501,8 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
Init(connOptions);
if (fListen && !InitBinds(connOptions)) {
- if (clientInterface) {
- clientInterface->ThreadSafeMessageBox(
+ if (m_client_interface) {
+ m_client_interface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want this."),
"", CClientUIInterface::MSG_ERROR);
}
@@ -2531,22 +2519,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
AddAddrFetch(strDest);
}
- if (clientInterface) {
- clientInterface->InitMessage(_("Loading P2P addresses…").translated);
- }
- // Load addresses from peers.dat
- int64_t nStart = GetTimeMillis();
- {
- CAddrDB adb;
- if (adb.Read(addrman))
- LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart);
- else {
- addrman.Clear(); // Addrman can be in an inconsistent state after failure, reset it
- LogPrintf("Recreating peers.dat\n");
- DumpAddresses();
- }
- }
-
if (m_use_addrman_outgoing) {
// Load addresses from anchors.dat
m_anchors = ReadAnchors(gArgs.GetDataDirNet() / ANCHORS_DATABASE_FILENAME);
@@ -2556,7 +2528,9 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
LogPrintf("%i block-relay-only anchors will be tried for connections.\n", m_anchors.size());
}
- uiInterface.InitMessage(_("Starting network threads…").translated);
+ if (m_client_interface) {
+ m_client_interface->InitMessage(_("Starting network threads…").translated);
+ }
fAddressesInitialized = true;
@@ -2594,8 +2568,8 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
threadOpenAddedConnections = std::thread(&util::TraceThread, "addcon", [this] { ThreadOpenAddedConnections(); });
if (connOptions.m_use_addrman_outgoing && !connOptions.m_specified_outgoing.empty()) {
- if (clientInterface) {
- clientInterface->ThreadSafeMessageBox(
+ if (m_client_interface) {
+ m_client_interface->ThreadSafeMessageBox(
_("Cannot provide specific connections and have addrman find outgoing connections at the same."),
"", CClientUIInterface::MSG_ERROR);
}
@@ -2977,6 +2951,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const
: nTimeConnected(GetTimeSeconds()),
addr(addrIn),
addrBind(addrBindIn),
+ m_addr_name{addrNameIn.empty() ? addr.ToStringIPPort() : addrNameIn},
m_inbound_onion(inbound_onion),
nKeyedNetGroup(nKeyedNetGroupIn),
id(idIn),
@@ -2986,7 +2961,6 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const
{
if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND);
hSocket = hSocketIn;
- addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
if (conn_type_in != ConnectionType::BLOCK_RELAY) {
m_tx_relay = std::make_unique<TxRelay>();
}
@@ -2996,7 +2970,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const
mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0;
if (fLogIPs) {
- LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", addrName, id);
+ LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", m_addr_name, id);
} else {
LogPrint(BCLog::NET, "Added connection peer=%d\n", id);
}
@@ -3025,7 +2999,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
TRACE6(net, outbound_message,
pnode->GetId(),
- pnode->GetAddrName().c_str(),
+ pnode->m_addr_name.c_str(),
pnode->ConnectionTypeAsString().c_str(),
msg.m_type.c_str(),
msg.data.size(),
diff --git a/src/net.h b/src/net.h
index 889d57b74c..d568c56205 100644
--- a/src/net.h
+++ b/src/net.h
@@ -248,7 +248,7 @@ public:
int64_t nLastBlockTime;
int64_t nTimeConnected;
int64_t nTimeOffset;
- std::string addrName;
+ std::string m_addr_name;
int nVersion;
std::string cleanSubVer;
bool fInbound;
@@ -430,6 +430,7 @@ public:
const CAddress addr;
// Bind address of our side of the connection
const CAddress addrBind;
+ const std::string m_addr_name;
//! Whether this peer is an inbound onion, i.e. connected via our Tor onion service.
const bool m_inbound_onion;
std::atomic<int> nVersion{0};
@@ -658,10 +659,6 @@ public:
return nLocalServices;
}
- std::string GetAddrName() const;
- //! Sets the addrName only if it was not previously set
- void MaybeSetAddrName(const std::string& addrNameIn);
-
std::string ConnectionTypeAsString() const { return ::ConnectionTypeAsString(m_conn_type); }
/** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */
@@ -693,10 +690,7 @@ private:
//! service advertisements.
const ServiceFlags nLocalServices;
- std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread
-
- mutable RecursiveMutex cs_addrName;
- std::string addrName GUARDED_BY(cs_addrName);
+ std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread
// Our address, as reported by the peer
CService addrLocal GUARDED_BY(cs_addrLocal);
@@ -787,7 +781,7 @@ public:
nMaxAddnode = connOptions.nMaxAddnode;
nMaxFeeler = connOptions.nMaxFeeler;
m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler;
- clientInterface = connOptions.uiInterface;
+ m_client_interface = connOptions.uiInterface;
m_banman = connOptions.m_banman;
m_msgproc = connOptions.m_msgproc;
nSendBufferMaxSize = connOptions.nSendBufferMaxSize;
@@ -1126,7 +1120,7 @@ private:
int nMaxFeeler;
int m_max_outbound;
bool m_use_addrman_outgoing;
- CClientUIInterface* clientInterface;
+ CClientUIInterface* m_client_interface;
NetEventsInterface* m_msgproc;
/** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
BanMan* m_banman;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 8243ef0f55..3ad34e83ba 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -161,7 +161,7 @@ static constexpr size_t MAX_ADDR_TO_SEND{1000};
static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1};
/** The soft limit of the address processing token bucket (the regular MAX_ADDR_RATE_PER_SECOND
* based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR
- * is exempt from this limit. */
+ * is exempt from this limit). */
static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND};
// Internal stuff
@@ -263,14 +263,14 @@ struct Peer {
std::atomic_bool m_wants_addrv2{false};
/** Whether this peer has already sent us a getaddr message. */
bool m_getaddr_recvd{false};
- /** Number of addr messages that can be processed from this peer. Start at 1 to
+ /** Number of addresses that can be processed from this peer. Start at 1 to
* permit self-announcement. */
double m_addr_token_bucket{1.0};
/** When m_addr_token_bucket was last updated */
std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()};
/** Total number of addresses that were dropped due to rate limiting. */
std::atomic<uint64_t> m_addr_rate_limited{0};
- /** Total number of addresses that were processed (excludes rate limited ones). */
+ /** Total number of addresses that were processed (excludes rate-limited ones). */
std::atomic<uint64_t> m_addr_processed{0};
/** Set of txids to reconsider once their parent transactions have been accepted **/
@@ -1087,25 +1087,25 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, int64_t nTime)
// Note that pnode->GetLocalServices() is a reflection of the local
// services we were offering when the CNode object was created for this
// peer.
- ServiceFlags nLocalNodeServices = pnode.GetLocalServices();
+ uint64_t my_services{pnode.GetLocalServices()};
uint64_t nonce = pnode.GetLocalNonce();
const int nNodeStartingHeight{m_best_height};
NodeId nodeid = pnode.GetId();
CAddress addr = pnode.addr;
- CAddress addrYou = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ?
- addr :
- CAddress(CService(), addr.nServices);
- CAddress addrMe = CAddress(CService(), nLocalNodeServices);
+ CService addr_you = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? addr : CService();
+ uint64_t your_services{addr.nServices};
const bool tx_relay = !m_ignore_incoming_txs && pnode.m_tx_relay != nullptr;
- m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
+ m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, my_services, nTime,
+ your_services, addr_you, // Together the pre-version-31402 serialization of CAddress "addrYou" (without nTime)
+ my_services, CService(), // Together the pre-version-31402 serialization of CAddress "addrMe" (without nTime)
nonce, strSubVersion, nNodeStartingHeight, tx_relay));
if (fLogIPs) {
- LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), tx_relay, nodeid);
+ LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, them=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addr_you.ToString(), tx_relay, nodeid);
} else {
- LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), tx_relay, nodeid);
+ LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, tx_relay, nodeid);
}
}
@@ -2487,21 +2487,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
int64_t nTime;
- CAddress addrMe;
- CAddress addrFrom;
+ CService addrMe;
uint64_t nNonce = 1;
- uint64_t nServiceInt;
ServiceFlags nServices;
int nVersion;
std::string cleanSubVer;
int starting_height = -1;
bool fRelay = true;
- vRecv >> nVersion >> nServiceInt >> nTime >> addrMe;
+ vRecv >> nVersion >> Using<CustomUintFormatter<8>>(nServices) >> nTime;
if (nTime < 0) {
nTime = 0;
}
- nServices = ServiceFlags(nServiceInt);
+ vRecv.ignore(8); // Ignore the addrMe service bits sent by the peer
+ vRecv >> addrMe;
if (!pfrom.IsInboundConn())
{
m_addrman.SetServices(pfrom.addr, nServices);
@@ -2520,8 +2519,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
- if (!vRecv.empty())
- vRecv >> addrFrom >> nNonce;
+ if (!vRecv.empty()) {
+ // The version message includes information about the sending node which we don't use:
+ // - 8 bytes (service bits)
+ // - 16 bytes (ipv6 address)
+ // - 2 bytes (port)
+ vRecv.ignore(26);
+ vRecv >> nNonce;
+ }
if (!vRecv.empty()) {
std::string strSubVer;
vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH);
@@ -2848,11 +2853,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
// Apply rate limiting.
- if (rate_limited) {
- if (peer->m_addr_token_bucket < 1.0) {
+ if (peer->m_addr_token_bucket < 1.0) {
+ if (rate_limited) {
++num_rate_limit;
continue;
}
+ } else {
peer->m_addr_token_bucket -= 1.0;
}
// We only bother storing full nodes, though this may include
@@ -2880,12 +2886,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
peer->m_addr_processed += num_proc;
peer->m_addr_rate_limited += num_rate_limit;
- LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d%s\n",
- vAddr.size(),
- num_proc,
- num_rate_limit,
- pfrom.GetId(),
- fLogIPs ? ", peeraddr=" + pfrom.addr.ToString() : "");
+ LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d\n",
+ vAddr.size(), num_proc, num_rate_limit, pfrom.GetId());
m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60);
if (vAddr.size() < 1000) peer->m_getaddr_sent = false;
@@ -4084,7 +4086,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
TRACE6(net, inbound_message,
pfrom->GetId(),
- pfrom->GetAddrName().c_str(),
+ pfrom->m_addr_name.c_str(),
pfrom->ConnectionTypeAsString().c_str(),
msg.m_command.c_str(),
msg.m_recv.size(),
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 183b5a5d91..b46ad0333e 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -334,6 +334,7 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec
REVERSE_LOCK(lock);
if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) block.m_data->SetNull();
}
+ block.found = true;
return true;
}
@@ -660,6 +661,14 @@ public:
RPCRunLater(name, std::move(fn), seconds);
}
int rpcSerializationFlags() override { return RPCSerializationFlags(); }
+ util::SettingsValue getSetting(const std::string& name) override
+ {
+ return gArgs.GetSetting(name);
+ }
+ std::vector<util::SettingsValue> getSettingsList(const std::string& name) override
+ {
+ return gArgs.GetSettingsList(name);
+ }
util::SettingsValue getRwSetting(const std::string& name) override
{
util::SettingsValue result;
@@ -670,7 +679,7 @@ public:
});
return result;
}
- bool updateRwSetting(const std::string& name, const util::SettingsValue& value) override
+ bool updateRwSetting(const std::string& name, const util::SettingsValue& value, bool write) override
{
gArgs.LockSettings([&](util::Settings& settings) {
if (value.isNull()) {
@@ -679,7 +688,7 @@ public:
settings.rw_settings[name] = value;
}
});
- return gArgs.WriteSettingsFile();
+ return !write || gArgs.WriteSettingsFile();
}
void requestMempoolTransactions(Notifications& notifications) override
{
diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp
index 8125b41c41..43624c7993 100644
--- a/src/policy/rbf.cpp
+++ b/src/policy/rbf.cpp
@@ -3,6 +3,10 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <policy/rbf.h>
+
+#include <policy/settings.h>
+#include <tinyformat.h>
+#include <util/moneystr.h>
#include <util/rbf.h>
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
@@ -42,3 +46,34 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
// If we don't have a local mempool we can only check the transaction itself.
return SignalsOptInRBF(tx) ? RBFTransactionState::REPLACEABLE_BIP125 : RBFTransactionState::UNKNOWN;
}
+
+bool GetEntriesForConflicts(const CTransaction& tx,
+ CTxMemPool& m_pool,
+ const CTxMemPool::setEntries& setIterConflicting,
+ CTxMemPool::setEntries& allConflicting,
+ std::string& err_string)
+{
+ AssertLockHeld(m_pool.cs);
+ const uint256 hash = tx.GetHash();
+ uint64_t nConflictingCount = 0;
+ for (const auto& mi : setIterConflicting) {
+ nConflictingCount += mi->GetCountWithDescendants();
+ // This potentially overestimates the number of actual descendants
+ // but we just want to be conservative to avoid doing too much
+ // work.
+ if (nConflictingCount > MAX_BIP125_REPLACEMENT_CANDIDATES) {
+ err_string = strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
+ hash.ToString(),
+ nConflictingCount,
+ MAX_BIP125_REPLACEMENT_CANDIDATES);
+ return false;
+ }
+ }
+ // If not too many to replace, then calculate the set of
+ // transactions that would have to be evicted
+ for (CTxMemPool::txiter it : setIterConflicting) {
+ m_pool.CalculateDescendants(it, allConflicting);
+ }
+ return true;
+}
+
diff --git a/src/policy/rbf.h b/src/policy/rbf.h
index e078070c1c..a67e9058df 100644
--- a/src/policy/rbf.h
+++ b/src/policy/rbf.h
@@ -7,6 +7,10 @@
#include <txmempool.h>
+/** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all
+ * mempool conflicts and their descendants. */
+static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100};
+
/** The rbf state of unconfirmed transactions */
enum class RBFTransactionState {
/** Unconfirmed tx that does not signal rbf and is not in the mempool */
@@ -31,4 +35,19 @@ enum class RBFTransactionState {
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
+/** Get all descendants of setIterConflicting. Also enforce BIP125 Rule #5, "The number of original
+ * transactions to be replaced and their descendant transactions which will be evicted from the
+ * mempool must not exceed a total of 100 transactions." Quit as early as possible. There cannot be
+ * more than MAX_BIP125_REPLACEMENT_CANDIDATES potential entries.
+ * @param[in] setIterConflicting The set of iterators to mempool entries.
+ * @param[out] err_string Used to return errors, if any.
+ * @param[out] allConflicting Populated with all the mempool entries that would be replaced,
+ * which includes descendants of setIterConflicting. Not cleared at
+ * the start; any existing mempool entries will remain in the set.
+ * @returns false if Rule 5 is broken.
+ */
+bool GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& m_pool,
+ const CTxMemPool::setEntries& setIterConflicting,
+ CTxMemPool::setEntries& allConflicting,
+ std::string& err_string) EXCLUSIVE_LOCKS_REQUIRED(m_pool.cs);
#endif // BITCOIN_POLICY_RBF_H
diff --git a/src/protocol.h b/src/protocol.h
index f9248899dc..2149e45993 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -396,7 +396,6 @@ public:
// ambiguous what that would mean. Make sure no code relying on that is introduced:
assert(!(s.GetType() & SER_GETHASH));
bool use_v2;
- bool store_time;
if (s.GetType() & SER_DISK) {
// In the disk serialization format, the encoding (v1 or v2) is determined by a flag version
// that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines
@@ -413,24 +412,16 @@ public:
} else {
throw std::ios_base::failure("Unsupported CAddress disk format version");
}
- store_time = true;
} else {
// In the network serialization format, the encoding (v1 or v2) is determined directly by
// the value of ADDRV2_FORMAT in the stream version, as no explicitly encoded version
// exists in the stream.
assert(s.GetType() & SER_NETWORK);
use_v2 = s.GetVersion() & ADDRV2_FORMAT;
- // The only time we serialize a CAddress object without nTime is in
- // the initial VERSION messages which contain two CAddress records.
- // At that point, the serialization version is INIT_PROTO_VERSION.
- // After the version handshake, serialization version is >=
- // MIN_PEER_PROTO_VERSION and all ADDR messages are serialized with
- // nTime.
- store_time = s.GetVersion() != INIT_PROTO_VERSION;
}
SER_READ(obj, obj.nTime = TIME_INIT);
- if (store_time) READWRITE(obj.nTime);
+ READWRITE(obj.nTime);
// nServices is serialized as CompactSize in V2; as uint64_t in V1.
if (use_v2) {
uint64_t services_tmp;
@@ -445,7 +436,7 @@ public:
SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj));
}
- //! Always included in serialization, except in the network format on INIT_PROTO_VERSION.
+ //! Always included in serialization.
uint32_t nTime{TIME_INIT};
//! Serialized as uint64_t in V1, and as CompactSize in V2.
ServiceFlags nServices{NODE_NONE};
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index 75202e7cf4..d14a20b870 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -180,6 +180,23 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
}
+std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
+{
+ std::vector<CKeyID> out;
+ // For now, use the old full pubkey-based key derivation logic. As it is indexed by
+ // Hash160(full pubkey), we need to return both a version prefixed with 0x02, and one
+ // with 0x03.
+ unsigned char b[33] = {0x02};
+ std::copy(m_keydata.begin(), m_keydata.end(), b + 1);
+ CPubKey fullpubkey;
+ fullpubkey.Set(b, b + 33);
+ out.push_back(fullpubkey.GetID());
+ b[0] = 0x03;
+ fullpubkey.Set(b, b + 33);
+ out.push_back(fullpubkey.GetID());
+ return out;
+}
+
bool XOnlyPubKey::IsFullyValid() const
{
secp256k1_xonly_pubkey pubkey;
@@ -333,6 +350,7 @@ void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) {
nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8];
memcpy(chaincode.begin(), code+9, 32);
pubkey.Set(code+41, code+BIP32_EXTKEY_SIZE);
+ if ((nDepth == 0 && (nChild != 0 || ReadLE32(vchFingerprint) != 0)) || !pubkey.IsFullyValid()) pubkey = CPubKey();
}
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
diff --git a/src/pubkey.h b/src/pubkey.h
index eec34a89c2..861a2cf500 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -267,6 +267,11 @@ public:
/** Construct a Taproot tweaked output point with this point as internal key. */
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;
+ /** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey.
+ * This is needed for key lookups since keys are indexed by CKeyID.
+ */
+ std::vector<CKeyID> GetKeyIDs() const;
+
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
const unsigned char* data() const { return m_keydata.begin(); }
static constexpr size_t size() { return decltype(m_keydata)::size(); }
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 4ab4e388d9..f6ea147ddb 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -28,6 +28,7 @@
#include <qt/utilitydialog.h>
#include <qt/winshutdownmonitor.h>
#include <uint256.h>
+#include <util/string.h>
#include <util/system.h>
#include <util/threadnames.h>
#include <util/translation.h>
@@ -144,6 +145,53 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
QApplication::installTranslator(&translator);
}
+static bool InitSettings()
+{
+ if (!gArgs.GetSettingsPath()) {
+ return true; // Do nothing if settings file disabled.
+ }
+
+ std::vector<std::string> errors;
+ if (!gArgs.ReadSettingsFile(&errors)) {
+ bilingual_str error = _("Settings file could not be read");
+ InitError(Untranslated(strprintf("%s:\n%s\n", error.original, MakeUnorderedList(errors))));
+
+ QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Reset | QMessageBox::Abort);
+ /*: Explanatory text shown on startup when the settings file cannot be read.
+ Prompts user to make a choice between resetting or aborting. */
+ messagebox.setInformativeText(QObject::tr("Do you want to reset settings to default values, or to abort without making changes?"));
+ messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(errors)));
+ messagebox.setTextFormat(Qt::PlainText);
+ messagebox.setDefaultButton(QMessageBox::Reset);
+ switch (messagebox.exec()) {
+ case QMessageBox::Reset:
+ break;
+ case QMessageBox::Abort:
+ return false;
+ default:
+ assert(false);
+ }
+ }
+
+ errors.clear();
+ if (!gArgs.WriteSettingsFile(&errors)) {
+ bilingual_str error = _("Settings file could not be written");
+ InitError(Untranslated(strprintf("%s:\n%s\n", error.original, MakeUnorderedList(errors))));
+
+ QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Ok);
+ /*: Explanatory text shown on startup when the settings file could not be written.
+ Prompts user to check that we have the ability to write to the file.
+ Explains that the user has the option of running without a settings file.*/
+ messagebox.setInformativeText(QObject::tr("A fatal error occurred. Check that settings file is writable, or try running with -nosettings."));
+ messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(errors)));
+ messagebox.setTextFormat(Qt::PlainText);
+ messagebox.setDefaultButton(QMessageBox::Ok);
+ messagebox.exec();
+ return false;
+ }
+ return true;
+}
+
/* qDebug() message handler --> debug.log */
void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
{
@@ -295,6 +343,17 @@ void BitcoinApplication::requestShutdown()
window->setClientModel(nullptr);
pollShutdownTimer->stop();
+#ifdef ENABLE_WALLET
+ // Delete wallet controller here manually, instead of relying on Qt object
+ // tracking (https://doc.qt.io/qt-5/objecttrees.html). This makes sure
+ // walletmodel m_handle_* notification handlers are deleted before wallets
+ // are unloaded, which can simplify wallet implementations. It also avoids
+ // these notifications having to be handled while GUI objects are being
+ // destroyed, making GUI code less fragile as well.
+ delete m_wallet_controller;
+ m_wallet_controller = nullptr;
+#endif // ENABLE_WALLET
+
delete clientModel;
clientModel = nullptr;
@@ -512,9 +571,8 @@ int GuiMain(int argc, char* argv[])
// Parse URIs on command line -- this can affect Params()
PaymentServer::ipcParseCommandLine(argc, argv);
#endif
- if (!gArgs.InitSettings(error)) {
- InitError(Untranslated(error));
- QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error initializing settings: %1").arg(QString::fromStdString(error)));
+
+ if (!InitSettings()) {
return EXIT_FAILURE;
}
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 863225099a..55eba60bcd 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -110,6 +110,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
activity->create();
});
+ connect(walletFrame, &WalletFrame::message, [this](const QString& title, const QString& message, unsigned int style) {
+ this->message(title, message, style);
+ });
+ connect(walletFrame, &WalletFrame::currentWalletSet, [this] { updateWalletStatus(); });
setCentralWidget(walletFrame);
} else
#endif // ENABLE_WALLET
@@ -326,7 +330,7 @@ void BitcoinGUI::createActions()
verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
m_load_psbt_action = new QAction(tr("&Load PSBT from file…"), this);
m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction"));
- m_load_psbt_clipboard_action = new QAction(tr("Load PSBT from clipboard…"), this);
+ m_load_psbt_clipboard_action = new QAction(tr("Load PSBT from &clipboard…"), this);
m_load_psbt_clipboard_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction from clipboard"));
openRPCConsoleAction = new QAction(tr("Node window"), this);
@@ -483,7 +487,7 @@ void BitcoinGUI::createMenuBar()
QMenu* window_menu = appMenuBar->addMenu(tr("&Window"));
- QAction* minimize_action = window_menu->addAction(tr("Minimize"));
+ QAction* minimize_action = window_menu->addAction(tr("&Minimize"));
minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
connect(minimize_action, &QAction::triggered, [] {
QApplication::activeWindow()->showMinimized();
@@ -591,8 +595,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH
connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections);
connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive);
- modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromTime_t(tip_info->header_time));
- setNumBlocks(tip_info->block_height, QDateTime::fromTime_t(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD);
+ modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time));
+ setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD);
connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks);
// Receive and report messages from client model
@@ -691,7 +695,6 @@ void BitcoinGUI::addWallet(WalletModel* walletModel)
});
connect(wallet_view, &WalletView::encryptionStatusChanged, this, &BitcoinGUI::updateWalletStatus);
connect(wallet_view, &WalletView::incomingTransaction, this, &BitcoinGUI::incomingTransaction);
- connect(wallet_view, &WalletView::hdEnabledStatusChanged, this, &BitcoinGUI::updateWalletStatus);
connect(this, &BitcoinGUI::setPrivacy, wallet_view, &WalletView::setPrivacy);
wallet_view->setPrivacy(isPrivacyModeActivated());
const QString display_name = walletModel->getDisplayName();
@@ -1337,9 +1340,8 @@ void BitcoinGUI::setEncryptionStatus(int status)
void BitcoinGUI::updateWalletStatus()
{
- if (!walletFrame) {
- return;
- }
+ assert(walletFrame);
+
WalletView * const walletView = walletFrame->currentWalletView();
if (!walletView) {
return;
diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp
index bb2073b9fe..c86cb16af6 100644
--- a/src/qt/clientmodel.cpp
+++ b/src/qt/clientmodel.cpp
@@ -216,7 +216,7 @@ bool ClientModel::isReleaseVersion() const
QString ClientModel::formatClientStartupTime() const
{
- return QDateTime::fromTime_t(GetStartupTime()).toString();
+ return QDateTime::fromSecsSinceEpoch(GetStartupTime()).toString();
}
QString ClientModel::dataDir() const
@@ -294,7 +294,7 @@ static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_
bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection,
Q_ARG(int, tip.block_height),
- Q_ARG(QDateTime, QDateTime::fromTime_t(tip.block_time)),
+ Q_ARG(QDateTime, QDateTime::fromSecsSinceEpoch(tip.block_time)),
Q_ARG(double, verificationProgress),
Q_ARG(bool, fHeader),
Q_ARG(SynchronizationState, sync_state));
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
index dc24bbc6a6..f9a61c3e60 100644
--- a/src/qt/createwalletdialog.cpp
+++ b/src/qt/createwalletdialog.cpp
@@ -32,7 +32,7 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
// set to true, enable it when isEncryptWalletChecked is false.
ui->disable_privkeys_checkbox->setEnabled(!checked);
#ifdef ENABLE_EXTERNAL_SIGNER
- ui->external_signer_checkbox->setEnabled(!checked);
+ ui->external_signer_checkbox->setEnabled(m_has_signers && !checked);
#endif
// When the disable_privkeys_checkbox is disabled, uncheck it.
if (!ui->disable_privkeys_checkbox->isEnabled()) {
@@ -115,7 +115,8 @@ CreateWalletDialog::~CreateWalletDialog()
void CreateWalletDialog::setSigners(const std::vector<ExternalSigner>& signers)
{
- if (!signers.empty()) {
+ m_has_signers = !signers.empty();
+ if (m_has_signers) {
ui->external_signer_checkbox->setEnabled(true);
ui->external_signer_checkbox->setChecked(true);
ui->encrypt_wallet_checkbox->setEnabled(false);
diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h
index 25ddf97585..fc13cc44eb 100644
--- a/src/qt/createwalletdialog.h
+++ b/src/qt/createwalletdialog.h
@@ -35,6 +35,7 @@ public:
private:
Ui::CreateWalletDialog *ui;
+ bool m_has_signers = false;
};
#endif // BITCOIN_QT_CREATEWALLETDIALOG_H
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index bd72328c02..2ff1445709 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -51,20 +51,20 @@
</spacer>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_Main_Prune">
- <item>
- <widget class="QCheckBox" name="prune">
- <property name="toolTip">
- <string>Enabling pruning significantly reduces the disk space required to store transactions. All blocks are still fully validated. Reverting this setting requires re-downloading the entire blockchain.</string>
- </property>
- <property name="text">
- <string>Prune &amp;block storage to</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="pruneSize"/>
- </item>
+ <layout class="QHBoxLayout" name="horizontalLayout_Main_Prune">
+ <item>
+ <widget class="QCheckBox" name="prune">
+ <property name="toolTip">
+ <string>Enabling pruning significantly reduces the disk space required to store transactions. All blocks are still fully validated. Reverting this setting requires re-downloading the entire blockchain.</string>
+ </property>
+ <property name="text">
+ <string>Prune &amp;block storage to</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="pruneSize"/>
+ </item>
<item>
<widget class="QLabel" name="pruneSizeUnitLabel">
<property name="text">
@@ -201,6 +201,16 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_Wallet">
<item>
+ <widget class="QCheckBox" name="subFeeFromAmount">
+ <property name="toolTip">
+ <string extracomment="Tooltip text for Options window setting that sets subtracting the fee from a sending amount as default.">Whether to set subtract fee from amount as default or not.</string>
+ </property>
+ <property name="text">
+ <string extracomment="An Options window setting to set subtracting the fee from a sending amount as default.">Subtract &amp;fee from amount by default</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Expert</string>
@@ -235,27 +245,27 @@
<string>External Signer (e.g. hardware wallet)</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutHww">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayoutHww">
- <item>
- <widget class="QLabel" name="externalSignerPathLabel">
- <property name="text">
- <string>&amp;External signer script path</string>
- </property>
- <property name="buddy">
- <cstring>externalSignerPath</cstring>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="externalSignerPath">
- <property name="toolTip">
- <string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayoutHww">
+ <item>
+ <widget class="QLabel" name="externalSignerPathLabel">
+ <property name="text">
+ <string>&amp;External signer script path</string>
+ </property>
+ <property name="buddy">
+ <cstring>externalSignerPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="externalSignerPath">
+ <property name="toolTip">
+ <string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index ecdfce2f5a..e98e50ba14 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -81,7 +81,7 @@ QString dateTimeStr(const QDateTime &date)
QString dateTimeStr(qint64 nTime)
{
- return dateTimeStr(QDateTime::fromTime_t((qint32)nTime));
+ return dateTimeStr(QDateTime::fromSecsSinceEpoch(nTime));
}
QFont fixedPitchFont(bool use_embedded_font)
diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts
index 7026f49c01..47c002498a 100644
--- a/src/qt/locale/bitcoin_en.ts
+++ b/src/qt/locale/bitcoin_en.ts
@@ -749,8 +749,8 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<source>%n active connection(s) to Bitcoin network.</source>
<extracomment>A substring of the tooltip.</extracomment>
<translation type="unfinished">
- <numerusform></numerusform>
- <numerusform></numerusform>
+ <numerusform>%n active connection to Bitcoin network.</numerusform>
+ <numerusform>%n active connections to Bitcoin network.</numerusform>
</translation>
</message>
<message>
@@ -1376,8 +1376,8 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<source>(sufficient to restore backups %n day(s) old)</source>
<extracomment>Explanatory text on the capability of the current prune target.</extracomment>
<translation type="unfinished">
- <numerusform></numerusform>
- <numerusform></numerusform>
+ <numerusform>(sufficient to restore backups %n day old)</numerusform>
+ <numerusform>(sufficient to restore backups %n days old)</numerusform>
</translation>
</message>
<message>
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index b12fe96567..5ad4fc9b33 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -239,6 +239,7 @@ void OptionsDialog::setMapper()
/* Wallet */
mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
+ mapper->addMapping(ui->subFeeFromAmount, OptionsModel::SubFeeFromAmount);
mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
/* Network */
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index 24a4e9ee96..d87fc1f84a 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -124,6 +124,11 @@ void OptionsModel::Init(bool resetSettings)
if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) {
addOverriddenOption("-signer");
}
+
+ if (!settings.contains("SubFeeFromAmount")) {
+ settings.setValue("SubFeeFromAmount", false);
+ }
+ m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool();
#endif
// Network
@@ -335,6 +340,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
return settings.value("bSpendZeroConfChange");
case ExternalSignerPath:
return settings.value("external_signer_path");
+ case SubFeeFromAmount:
+ return m_sub_fee_from_amount;
#endif
case DisplayUnit:
return nDisplayUnit;
@@ -460,6 +467,10 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
setRestartRequired(true);
}
break;
+ case SubFeeFromAmount:
+ m_sub_fee_from_amount = value.toBool();
+ settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount);
+ break;
#endif
case DisplayUnit:
setDisplayUnit(value);
diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h
index 535843e8ba..203ee27ad8 100644
--- a/src/qt/optionsmodel.h
+++ b/src/qt/optionsmodel.h
@@ -61,6 +61,7 @@ public:
Language, // QString
UseEmbeddedMonospacedFont, // bool
CoinControlFeatures, // bool
+ SubFeeFromAmount, // bool
ThreadsScriptVerif, // int
Prune, // bool
PruneSize, // int
@@ -88,6 +89,7 @@ public:
QString getThirdPartyTxUrls() const { return strThirdPartyTxUrls; }
bool getUseEmbeddedMonospacedFont() const { return m_use_embedded_monospaced_font; }
bool getCoinControlFeatures() const { return fCoinControlFeatures; }
+ bool getSubFeeFromAmount() const { return m_sub_fee_from_amount; }
const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; }
/* Explicit setters */
@@ -112,6 +114,7 @@ private:
QString strThirdPartyTxUrls;
bool m_use_embedded_monospaced_font;
bool fCoinControlFeatures;
+ bool m_sub_fee_from_amount;
/* settings that were overridden by command-line */
QString strOverriddenByCommandLine;
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index 1b7fda6e77..433a1ea934 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -72,8 +72,13 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
case NetNodeId:
return (qint64)rec->nodeStats.nodeid;
case Address:
- // prepend to peer address down-arrow symbol for inbound connection and up-arrow for outbound connection
- return QString::fromStdString((rec->nodeStats.fInbound ? "↓ " : "↑ ") + rec->nodeStats.addrName);
+ return QString::fromStdString(rec->nodeStats.m_addr_name);
+ case Direction:
+ return QString(rec->nodeStats.fInbound ?
+ //: An Inbound Connection from a Peer.
+ tr("Inbound") :
+ //: An Outbound Connection to a Peer.
+ tr("Outbound"));
case ConnectionType:
return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false);
case Network:
@@ -94,6 +99,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
case Address:
return {};
+ case Direction:
case ConnectionType:
case Network:
return QVariant(Qt::AlignCenter);
diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h
index 0d841ebf28..40265ee266 100644
--- a/src/qt/peertablemodel.h
+++ b/src/qt/peertablemodel.h
@@ -48,6 +48,7 @@ public:
enum ColumnIndex {
NetNodeId = 0,
Address,
+ Direction,
ConnectionType,
Network,
Ping,
@@ -84,6 +85,9 @@ private:
/*: Title of Peers Table column which contains the
IP/Onion/I2P address of the connected peer. */
tr("Address"),
+ /*: Title of Peers Table column which indicates the direction
+ the peer connection was initiated from. */
+ tr("Direction"),
/*: Title of Peers Table column which describes the type of
peer connection. The "type" describes why the connection exists. */
tr("Type"),
diff --git a/src/qt/peertablesortproxy.cpp b/src/qt/peertablesortproxy.cpp
index 78932da8d4..419133bc32 100644
--- a/src/qt/peertablesortproxy.cpp
+++ b/src/qt/peertablesortproxy.cpp
@@ -25,7 +25,9 @@ bool PeerTableSortProxy::lessThan(const QModelIndex& left_index, const QModelInd
case PeerTableModel::NetNodeId:
return left_stats.nodeid < right_stats.nodeid;
case PeerTableModel::Address:
- return left_stats.addrName.compare(right_stats.addrName) < 0;
+ return left_stats.m_addr_name.compare(right_stats.m_addr_name) < 0;
+ case PeerTableModel::Direction:
+ return left_stats.fInbound > right_stats.fInbound; // default sort Inbound, then Outbound
case PeerTableModel::ConnectionType:
return left_stats.m_conn_type < right_stats.m_conn_type;
case PeerTableModel::Network:
diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp
index 2adfeeaaf0..289fb9f7c8 100644
--- a/src/qt/psbtoperationsdialog.cpp
+++ b/src/qt/psbtoperationsdialog.cpp
@@ -47,18 +47,22 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
{
m_transaction_data = psbtx;
- bool complete;
- size_t n_could_sign;
- FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
- TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, &n_could_sign, m_transaction_data, complete);
- if (err != TransactionError::OK) {
- showStatus(tr("Failed to load transaction: %1")
- .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
- return;
+ bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
+ if (m_wallet_model) {
+ size_t n_could_sign;
+ TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, &n_could_sign, m_transaction_data, complete);
+ if (err != TransactionError::OK) {
+ showStatus(tr("Failed to load transaction: %1")
+ .arg(QString::fromStdString(TransactionErrorString(err).translated)),
+ StatusLevel::ERR);
+ return;
+ }
+ m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
+ } else {
+ m_ui->signTransactionButton->setEnabled(false);
}
m_ui->broadcastTransactionButton->setEnabled(complete);
- m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
updateTransactionDisplay();
}
@@ -133,7 +137,7 @@ void PSBTOperationsDialog::saveTransaction() {
}
CTxDestination address;
ExtractDestination(out.scriptPubKey, address);
- QString amount = BitcoinUnits::format(m_wallet_model->getOptionsModel()->getDisplayUnit(), out.nValue);
+ QString amount = BitcoinUnits::format(m_client_model->getOptionsModel()->getDisplayUnit(), out.nValue);
QString address_str = QString::fromStdString(EncodeDestination(address));
filename_suggestion.append(address_str + "-" + amount);
first = false;
@@ -224,6 +228,10 @@ void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {
}
size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) {
+ if (!m_wallet_model) {
+ return 0;
+ }
+
size_t n_signed;
bool complete;
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, &n_signed, m_transaction_data, complete);
@@ -246,7 +254,10 @@ void PSBTOperationsDialog::showTransactionStatus(const PartiallySignedTransactio
case PSBTRole::SIGNER: {
QString need_sig_text = tr("Transaction still needs signature(s).");
StatusLevel level = StatusLevel::INFO;
- if (m_wallet_model->wallet().privateKeysDisabled()) {
+ if (!m_wallet_model) {
+ need_sig_text += " " + tr("(But no wallet is loaded.)");
+ level = StatusLevel::WARN;
+ } else if (m_wallet_model->wallet().privateKeysDisabled()) {
need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
level = StatusLevel::WARN;
} else if (n_could_sign < 1) {
diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp
index ec3d970a7f..ab8225e19f 100644
--- a/src/qt/recentrequeststablemodel.cpp
+++ b/src/qt/recentrequeststablemodel.cpp
@@ -234,7 +234,7 @@ bool RecentRequestEntryLessThan::operator()(const RecentRequestEntry& left, cons
switch(column)
{
case RecentRequestsTableModel::Date:
- return pLeft->date.toTime_t() < pRight->date.toTime_t();
+ return pLeft->date.toSecsSinceEpoch() < pRight->date.toSecsSinceEpoch();
case RecentRequestsTableModel::Label:
return pLeft->recipient.label < pRight->recipient.label;
case RecentRequestsTableModel::Message:
diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h
index b817b64e77..c489c0eaf4 100644
--- a/src/qt/recentrequeststablemodel.h
+++ b/src/qt/recentrequeststablemodel.h
@@ -7,6 +7,8 @@
#include <qt/sendcoinsrecipient.h>
+#include <string>
+
#include <QAbstractTableModel>
#include <QStringList>
#include <QDateTime>
@@ -26,9 +28,9 @@ public:
SERIALIZE_METHODS(RecentRequestEntry, obj) {
unsigned int date_timet;
- SER_WRITE(obj, date_timet = obj.date.toTime_t());
+ SER_WRITE(obj, date_timet = obj.date.toSecsSinceEpoch());
READWRITE(obj.nVersion, obj.id, date_timet, obj.recipient);
- SER_READ(obj, obj.date = QDateTime::fromTime_t(date_timet));
+ SER_READ(obj, obj.date = QDateTime::fromSecsSinceEpoch(date_timet));
}
};
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 56f55363b2..829f7add80 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -651,7 +651,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
setNumConnections(model->getNumConnections());
connect(model, &ClientModel::numConnectionsChanged, this, &RPCConsole::setNumConnections);
- setNumBlocks(bestblock_height, QDateTime::fromTime_t(bestblock_date), verification_progress, false);
+ setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, false);
connect(model, &ClientModel::numBlocksChanged, this, &RPCConsole::setNumBlocks);
updateNetworkState();
@@ -706,6 +706,13 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
// create ban table context menu
banTableContextMenu = new QMenu(this);
+ /*: Context menu action to copy the IP/Netmask of a banned peer.
+ IP/Netmask is the combination of a peer's IP address and its Netmask.
+ For IP address see: https://en.wikipedia.org/wiki/IP_address */
+ banTableContextMenu->addAction(tr("&Copy IP/Netmask"), [this] {
+ GUIUtil::copyEntryData(ui->banlistWidget, BanTableModel::Address, Qt::DisplayRole);
+ });
+ banTableContextMenu->addSeparator();
banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode);
connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu);
@@ -1129,7 +1136,7 @@ void RPCConsole::updateDetailWidget()
}
const auto stats = selected_peers.first().data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>();
// update the detail ui with latest node information
- QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " ");
+ QString peerAddrDetails(QString::fromStdString(stats->nodeStats.m_addr_name) + " ");
peerAddrDetails += tr("(peer: %1)").arg(QString::number(stats->nodeStats.nodeid));
if (!stats->nodeStats.addrLocal.empty())
peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp
index 683c0441fa..5fa5165615 100644
--- a/src/qt/sendcoinsentry.cpp
+++ b/src/qt/sendcoinsentry.cpp
@@ -97,7 +97,9 @@ void SendCoinsEntry::clear()
ui->payTo->clear();
ui->addAsLabel->clear();
ui->payAmount->clear();
- ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked);
+ if (model && model->getOptionsModel()) {
+ ui->checkboxSubtractFeeFromAmount->setChecked(model->getOptionsModel()->getSubFeeFromAmount());
+ }
ui->messageTextLabel->clear();
ui->messageTextLabel->hide();
ui->messageLabel->hide();
diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp
index 39c69fe184..022f367422 100644
--- a/src/qt/test/addressbooktests.cpp
+++ b/src/qt/test/addressbooktests.cpp
@@ -109,9 +109,10 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
- AddWallet(wallet);
- WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get());
- RemoveWallet(wallet, std::nullopt);
+ WalletContext& context = *node.walletClient().context();
+ AddWallet(context, wallet);
+ WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt);
EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress);
editAddressDialog.setModel(walletModel.getAddressTableModel());
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index e883337fb5..1976bee74b 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -164,9 +164,10 @@ void TestGUI(interfaces::Node& node)
TransactionView transactionView(platformStyle.get());
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
- AddWallet(wallet);
- WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get());
- RemoveWallet(wallet, std::nullopt);
+ WalletContext& context = *node.walletClient().context();
+ AddWallet(context, wallet);
+ WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt);
sendCoinsDialog.setModel(&walletModel);
transactionView.setModel(&walletModel);
diff --git a/src/qt/transactionfilterproxy.cpp b/src/qt/transactionfilterproxy.cpp
index a631f497af..57c05a647e 100644
--- a/src/qt/transactionfilterproxy.cpp
+++ b/src/qt/transactionfilterproxy.cpp
@@ -7,17 +7,12 @@
#include <qt/transactiontablemodel.h>
#include <qt/transactionrecord.h>
+#include <algorithm>
#include <cstdlib>
-
-// Earliest date that can be represented (far in the past)
-const QDateTime TransactionFilterProxy::MIN_DATE = QDateTime::fromTime_t(0);
-// Last date that can be represented (far in the future)
-const QDateTime TransactionFilterProxy::MAX_DATE = QDateTime::fromTime_t(0xFFFFFFFF);
+#include <optional>
TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
QSortFilterProxyModel(parent),
- dateFrom(MIN_DATE),
- dateTo(MAX_DATE),
m_search_string(),
typeFilter(ALL_TYPES),
watchOnlyFilter(WatchOnlyFilter_All),
@@ -46,8 +41,8 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
return false;
QDateTime datetime = index.data(TransactionTableModel::DateRole).toDateTime();
- if (datetime < dateFrom || datetime > dateTo)
- return false;
+ if (dateFrom && datetime < *dateFrom) return false;
+ if (dateTo && datetime > *dateTo) return false;
QString address = index.data(TransactionTableModel::AddressRole).toString();
QString label = index.data(TransactionTableModel::LabelRole).toString();
@@ -65,10 +60,10 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
return true;
}
-void TransactionFilterProxy::setDateRange(const QDateTime &from, const QDateTime &to)
+void TransactionFilterProxy::setDateRange(const std::optional<QDateTime>& from, const std::optional<QDateTime>& to)
{
- this->dateFrom = from;
- this->dateTo = to;
+ dateFrom = from;
+ dateTo = to;
invalidateFilter();
}
diff --git a/src/qt/transactionfilterproxy.h b/src/qt/transactionfilterproxy.h
index 693b363692..09bc9e75db 100644
--- a/src/qt/transactionfilterproxy.h
+++ b/src/qt/transactionfilterproxy.h
@@ -10,6 +10,8 @@
#include <QDateTime>
#include <QSortFilterProxyModel>
+#include <optional>
+
/** Filter the transaction list according to pre-specified rules. */
class TransactionFilterProxy : public QSortFilterProxyModel
{
@@ -18,10 +20,6 @@ class TransactionFilterProxy : public QSortFilterProxyModel
public:
explicit TransactionFilterProxy(QObject *parent = nullptr);
- /** Earliest date that can be represented (far in the past) */
- static const QDateTime MIN_DATE;
- /** Last date that can be represented (far in the future) */
- static const QDateTime MAX_DATE;
/** Type filter bit field (all types) */
static const quint32 ALL_TYPES = 0xFFFFFFFF;
@@ -34,7 +32,8 @@ public:
WatchOnlyFilter_No
};
- void setDateRange(const QDateTime &from, const QDateTime &to);
+ /** Filter transactions between date range. Use std::nullopt for open range. */
+ void setDateRange(const std::optional<QDateTime>& from, const std::optional<QDateTime>& to);
void setSearchString(const QString &);
/**
@note Type filter takes a bit field created with TYPE() or ALL_TYPES
@@ -55,8 +54,8 @@ protected:
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override;
private:
- QDateTime dateFrom;
- QDateTime dateTo;
+ std::optional<QDateTime> dateFrom;
+ std::optional<QDateTime> dateTo;
QString m_search_string;
quint32 typeFilter;
WatchOnlyFilter watchOnlyFilter;
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index b68ceaedbb..23590ea4d2 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -610,7 +610,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case TypeRole:
return rec->type;
case DateRole:
- return QDateTime::fromTime_t(static_cast<uint>(rec->time));
+ return QDateTime::fromSecsSinceEpoch(rec->time);
case WatchonlyRole:
return rec->involvesWatchAddress;
case WatchonlyDecorationRole:
@@ -630,7 +630,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case TxPlainTextRole:
{
QString details;
- QDateTime date = QDateTime::fromTime_t(static_cast<uint>(rec->time));
+ QDateTime date = QDateTime::fromSecsSinceEpoch(rec->time);
QString txLabel = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
details.append(date.toString("M/d/yy HH:mm"));
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 83d17a32c0..908cb917f1 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -19,6 +19,8 @@
#include <node/ui_interface.h>
+#include <optional>
+
#include <QApplication>
#include <QComboBox>
#include <QDateTimeEdit>
@@ -266,26 +268,26 @@ void TransactionView::chooseDate(int idx)
{
case All:
transactionProxyModel->setDateRange(
- TransactionFilterProxy::MIN_DATE,
- TransactionFilterProxy::MAX_DATE);
+ std::nullopt,
+ std::nullopt);
break;
case Today:
transactionProxyModel->setDateRange(
GUIUtil::StartOfDay(current),
- TransactionFilterProxy::MAX_DATE);
+ std::nullopt);
break;
case ThisWeek: {
// Find last Monday
QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
transactionProxyModel->setDateRange(
GUIUtil::StartOfDay(startOfWeek),
- TransactionFilterProxy::MAX_DATE);
+ std::nullopt);
} break;
case ThisMonth:
transactionProxyModel->setDateRange(
GUIUtil::StartOfDay(QDate(current.year(), current.month(), 1)),
- TransactionFilterProxy::MAX_DATE);
+ std::nullopt);
break;
case LastMonth:
transactionProxyModel->setDateRange(
@@ -295,7 +297,7 @@ void TransactionView::chooseDate(int idx)
case ThisYear:
transactionProxyModel->setDateRange(
GUIUtil::StartOfDay(QDate(current.year(), 1, 1)),
- TransactionFilterProxy::MAX_DATE);
+ std::nullopt);
break;
case Range:
dateRangeWidget->setVisible(true);
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp
index a1f357e0db..30c29eb356 100644
--- a/src/qt/walletframe.cpp
+++ b/src/qt/walletframe.cpp
@@ -4,12 +4,18 @@
#include <qt/walletframe.h>
+#include <node/ui_interface.h>
+#include <psbt.h>
+#include <qt/guiutil.h>
#include <qt/overviewpage.h>
+#include <qt/psbtoperationsdialog.h>
#include <qt/walletmodel.h>
#include <qt/walletview.h>
#include <cassert>
+#include <QApplication>
+#include <QClipboard>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
@@ -103,7 +109,8 @@ void WalletFrame::setCurrentWallet(WalletModel* wallet_model)
walletView->updateGeometry();
walletStack->setCurrentWidget(walletView);
- walletView->updateEncryptionStatus();
+
+ Q_EMIT currentWalletSet();
}
void WalletFrame::removeWallet(WalletModel* wallet_model)
@@ -184,10 +191,40 @@ void WalletFrame::gotoVerifyMessageTab(QString addr)
void WalletFrame::gotoLoadPSBT(bool from_clipboard)
{
- WalletView *walletView = currentWalletView();
- if (walletView) {
- walletView->gotoLoadPSBT(from_clipboard);
+ std::string data;
+
+ if (from_clipboard) {
+ std::string raw = QApplication::clipboard()->text().toStdString();
+ bool invalid;
+ data = DecodeBase64(raw, &invalid);
+ if (invalid) {
+ Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR);
+ return;
+ }
+ } else {
+ QString filename = GUIUtil::getOpenFileName(this,
+ tr("Load Transaction Data"), QString(),
+ tr("Partially Signed Transaction (*.psbt)"), nullptr);
+ if (filename.isEmpty()) return;
+ if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
+ Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
+ return;
+ }
+ std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
+ data = std::string(std::istreambuf_iterator<char>{in}, {});
}
+
+ std::string error;
+ PartiallySignedTransaction psbtx;
+ if (!DecodeRawPSBT(psbtx, data, error)) {
+ Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
+ return;
+ }
+
+ PSBTOperationsDialog* dlg = new PSBTOperationsDialog(this, currentWalletModel(), clientModel);
+ dlg->openWithPSBT(psbtx);
+ dlg->setAttribute(Qt::WA_DeleteOnClose);
+ dlg->exec();
}
void WalletFrame::encryptWallet()
diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h
index 4f77bd716f..cbf6af95ec 100644
--- a/src/qt/walletframe.h
+++ b/src/qt/walletframe.h
@@ -48,6 +48,8 @@ public:
Q_SIGNALS:
void createWalletButtonClicked();
+ void message(const QString& title, const QString& message, unsigned int style);
+ void currentWalletSet();
private:
QStackedWidget *walletStack;
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index 3b8cf4c7ed..7e96e85c0c 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -8,7 +8,6 @@
#include <qt/askpassphrasedialog.h>
#include <qt/clientmodel.h>
#include <qt/guiutil.h>
-#include <qt/psbtoperationsdialog.h>
#include <qt/optionsmodel.h>
#include <qt/overviewpage.h>
#include <qt/platformstyle.h>
@@ -21,13 +20,10 @@
#include <interfaces/node.h>
#include <node/ui_interface.h>
-#include <psbt.h>
#include <util/strencodings.h>
#include <QAction>
#include <QActionGroup>
-#include <QApplication>
-#include <QClipboard>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QProgressDialog>
@@ -122,10 +118,6 @@ void WalletView::setWalletModel(WalletModel *_walletModel)
// Handle changes in encryption status
connect(_walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged);
- updateEncryptionStatus();
-
- // update HD status
- Q_EMIT hdEnabledStatusChanged();
// Balloon pop-up for new transaction
connect(_walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction);
@@ -205,44 +197,6 @@ void WalletView::gotoVerifyMessageTab(QString addr)
signVerifyMessageDialog->setAddress_VM(addr);
}
-void WalletView::gotoLoadPSBT(bool from_clipboard)
-{
- std::string data;
-
- if (from_clipboard) {
- std::string raw = QApplication::clipboard()->text().toStdString();
- bool invalid;
- data = DecodeBase64(raw, &invalid);
- if (invalid) {
- Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR);
- return;
- }
- } else {
- QString filename = GUIUtil::getOpenFileName(this,
- tr("Load Transaction Data"), QString(),
- tr("Partially Signed Transaction (*.psbt)"), nullptr);
- if (filename.isEmpty()) return;
- if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
- Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
- return;
- }
- std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
- data = std::string(std::istreambuf_iterator<char>{in}, {});
- }
-
- std::string error;
- PartiallySignedTransaction psbtx;
- if (!DecodeRawPSBT(psbtx, data, error)) {
- Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
- return;
- }
-
- PSBTOperationsDialog* dlg = new PSBTOperationsDialog(this, walletModel, clientModel);
- dlg->openWithPSBT(psbtx);
- dlg->setAttribute(Qt::WA_DeleteOnClose);
- dlg->exec();
-}
-
bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
{
return sendCoinsPage->handlePaymentRequest(recipient);
@@ -253,11 +207,6 @@ void WalletView::showOutOfSyncWarning(bool fShow)
overviewPage->showOutOfSyncWarning(fShow);
}
-void WalletView::updateEncryptionStatus()
-{
- Q_EMIT encryptionStatusChanged();
-}
-
void WalletView::encryptWallet()
{
if(!walletModel)
@@ -266,7 +215,7 @@ void WalletView::encryptWallet()
dlg.setModel(walletModel);
dlg.exec();
- updateEncryptionStatus();
+ Q_EMIT encryptionStatusChanged();
}
void WalletView::backupWallet()
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
index fedf06b710..bb6ad0f69e 100644
--- a/src/qt/walletview.h
+++ b/src/qt/walletview.h
@@ -83,8 +83,6 @@ public Q_SLOTS:
void gotoSignMessageTab(QString addr = "");
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
- /** Load Partially Signed Bitcoin Transaction */
- void gotoLoadPSBT(bool from_clipboard = false);
/** Show incoming transaction notification for new transactions.
@@ -105,9 +103,6 @@ public Q_SLOTS:
/** Show used receiving addresses */
void usedReceivingAddresses();
- /** Re-emit encryption status signal */
- void updateEncryptionStatus();
-
/** Show progress dialog e.g. for rescan */
void showProgress(const QString &title, int nProgress);
@@ -119,8 +114,6 @@ Q_SIGNALS:
void message(const QString &title, const QString &message, unsigned int style);
/** Encryption status of wallet changed */
void encryptionStatusChanged();
- /** HD-Enabled status of wallet changed (only possible during startup) */
- void hdEnabledStatusChanged();
/** Notify that a new transaction appeared */
void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName);
/** Notify that the out of sync warning icon has been pressed */
diff --git a/src/qt/winshutdownmonitor.h b/src/qt/winshutdownmonitor.h
index 8edb98c744..bf399edcf3 100644
--- a/src/qt/winshutdownmonitor.h
+++ b/src/qt/winshutdownmonitor.h
@@ -17,7 +17,7 @@ class WinShutdownMonitor : public QAbstractNativeEventFilter
{
public:
/** Implements QAbstractNativeEventFilter interface for processing Windows messages */
- bool nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult);
+ bool nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult) override;
/** Register the reason for blocking shutdown on Windows to allow clean client exit */
static void registerShutdownBlockReason(const QString& strReason, const HWND& mainWinId);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 4956ee39e9..909019d796 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -36,6 +36,7 @@
#include <txmempool.h>
#include <undo.h>
#include <util/strencodings.h>
+#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
@@ -1328,7 +1329,7 @@ static RPCHelpMan verifychain()
"\nVerifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
- strprintf("How thorough the block verification is:\n - %s", Join(CHECKLEVEL_DOC, "\n- "))},
+ strprintf("How thorough the block verification is:\n%s", MakeUnorderedList(CHECKLEVEL_DOC))},
{"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
},
RPCResult{
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 9c8582c7a3..4357ab2bb3 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -142,6 +142,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "importmulti", 0, "requests" },
{ "importmulti", 1, "options" },
{ "importdescriptors", 0, "requests" },
+ { "listdescriptors", 0, "private" },
{ "verifychain", 0, "checklevel" },
{ "verifychain", 1, "nblocks" },
{ "getblockstats", 0, "hash_or_height" },
@@ -186,6 +187,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 5, "descriptors"},
{ "createwallet", 6, "load_on_startup"},
{ "createwallet", 7, "external_signer"},
+ { "restorewallet", 2, "load_on_startup"},
{ "loadwallet", 1, "load_on_startup"},
{ "unloadwallet", 1, "load_on_startup"},
{ "getnodeaddresses", 0, "count"},
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index abc9ec3ce3..0f554ec5e7 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -151,6 +151,8 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"},
}},
{RPCResult::Type::BOOL, "addr_relay_enabled", "Whether we participate in address relay with this peer"},
+ {RPCResult::Type::NUM, "addr_processed", "The total number of addresses processed, excluding those dropped due to rate limiting"},
+ {RPCResult::Type::NUM, "addr_rate_limited", "The total number of addresses dropped due to rate limiting"},
{RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer",
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
@@ -195,7 +197,7 @@ static RPCHelpMan getpeerinfo()
CNodeStateStats statestats;
bool fStateStats = peerman.GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
- obj.pushKV("addr", stats.addrName);
+ obj.pushKV("addr", stats.m_addr_name);
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
@@ -949,7 +951,7 @@ static RPCHelpMan addpeeraddress()
address.nTime = GetAdjustedTime();
// The source address is set equal to the address. This is equivalent to the peer
// announcing itself.
- if (node.addrman->Add(address, address)) success = true;
+ if (node.addrman->Add({address}, address)) success = true;
}
obj.pushKV("success", success);
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index c617b0389c..00e77d89e5 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -903,7 +903,7 @@ static RPCHelpMan testmempoolaccept()
RPCResult{
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
"Returns results for each transaction in the same order they were passed in.\n"
- "It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\n",
+ "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
{
{RPCResult::Type::OBJ, "", "",
{
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index 122a92f084..f21eddf56c 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -18,6 +18,7 @@
#include <univalue.h>
#include <util/rbf.h>
#include <util/strencodings.h>
+#include <util/translation.h>
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf)
{
@@ -280,22 +281,22 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
int nHashType = ParseSighashString(hashType);
// Script verification errors
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
}
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result)
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result)
{
// Make errors UniValue
UniValue vErrors(UniValue::VARR);
for (const auto& err_pair : input_errors) {
- if (err_pair.second == "Missing amount") {
+ if (err_pair.second.original == "Missing amount") {
// This particular error needs to be an exception for some reason
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
}
- TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
+ TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second.original);
}
result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index ce7d5834fa..d2e116f7ee 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -8,6 +8,7 @@
#include <map>
#include <string>
+struct bilingual_str;
class FillableSigningProvider;
class UniValue;
struct CMutableTransaction;
@@ -25,7 +26,7 @@ class SigningProvider;
* @param result JSON object where signed transaction results accumulate
*/
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
-void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result);
+void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result);
/**
* Parse a prevtxs UniValue array and get the map of coins from it
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 682b55742a..621a1b9fd6 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -1242,14 +1242,8 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
CPubKey pubkey(full_key);
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
KeyOriginInfo info;
- if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
+ if (provider.GetKeyOriginByXOnly(xkey, info)) {
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
- } else {
- full_key[0] = 0x03;
- pubkey = CPubKey(full_key);
- if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
- return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
- }
}
return key_provider;
}
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index dd7c0a4a05..eafa9840d7 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1874,9 +1874,9 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE);
assert(program.size() >= uint256::size());
//! The internal pubkey (x-only, so no Y coordinate parity).
- const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
+ const XOnlyPubKey p{Span<const unsigned char>{control.data() + 1, control.data() + TAPROOT_CONTROL_BASE_SIZE}};
//! The output pubkey (taken from the scriptPubKey).
- const XOnlyPubKey q{uint256(program)};
+ const XOnlyPubKey q{program};
// Compute the Merkle root from the leaf and the provided path.
const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index 93136a0b79..ab49e84577 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -170,6 +170,13 @@ struct PrecomputedTransactionData
PrecomputedTransactionData() = default;
+ /** Initialize this PrecomputedTransactionData with transaction data.
+ *
+ * @param[in] tx The transaction for which data is being precomputed.
+ * @param[in] spent_outputs The CTxOuts being spent, one for each tx.vin, in order.
+ * @param[in] force Whether to precompute data for all optional features,
+ * regardless of what is in the inputs (used at signing
+ * time, when the inputs aren't filled in yet). */
template <class T>
void Init(const T& tx, std::vector<CTxOut>&& spent_outputs, bool force = false);
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 7864e690d8..b912b00365 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -11,6 +11,7 @@
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
+#include <util/translation.h>
#include <util/vector.h>
typedef std::vector<unsigned char> valtype;
@@ -59,22 +60,7 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
CKey key;
- {
- // For now, use the old full pubkey-based key derivation logic. As it indexed by
- // Hash160(full pubkey), we need to try both a version prefixed with 0x02, and one
- // with 0x03.
- unsigned char b[33] = {0x02};
- std::copy(pubkey.begin(), pubkey.end(), b + 1);
- CPubKey fullpubkey;
- fullpubkey.Set(b, b + 33);
- CKeyID keyid = fullpubkey.GetID();
- if (!provider.GetKey(keyid, key)) {
- b[0] = 0x03;
- fullpubkey.Set(b, b + 33);
- CKeyID keyid = fullpubkey.GetID();
- if (!provider.GetKey(keyid, key)) return false;
- }
- }
+ if (!provider.GetKeyByXOnly(pubkey, key)) return false;
// BIP341/BIP342 signing needs lots of precomputed transaction data. While some
// (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset
@@ -629,7 +615,7 @@ bool IsSegWitOutput(const SigningProvider& provider, const CScript& script)
return false;
}
-bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, int nHashType, std::map<int, std::string>& input_errors)
+bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, int nHashType, std::map<int, bilingual_str>& input_errors)
{
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
@@ -639,29 +625,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
PrecomputedTransactionData txdata;
std::vector<CTxOut> spent_outputs;
- spent_outputs.resize(mtx.vin.size());
- bool have_all_spent_outputs = true;
- for (unsigned int i = 0; i < mtx.vin.size(); i++) {
+ for (unsigned int i = 0; i < mtx.vin.size(); ++i) {
CTxIn& txin = mtx.vin[i];
auto coin = coins.find(txin.prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
- have_all_spent_outputs = false;
+ txdata.Init(txConst, /* spent_outputs */ {}, /* force */ true);
+ break;
} else {
- spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey);
+ spent_outputs.emplace_back(coin->second.out.nValue, coin->second.out.scriptPubKey);
}
}
- if (have_all_spent_outputs) {
+ if (spent_outputs.size() == mtx.vin.size()) {
txdata.Init(txConst, std::move(spent_outputs), true);
- } else {
- txdata.Init(txConst, {}, true);
}
// Sign what we can:
- for (unsigned int i = 0; i < mtx.vin.size(); i++) {
+ for (unsigned int i = 0; i < mtx.vin.size(); ++i) {
CTxIn& txin = mtx.vin[i];
auto coin = coins.find(txin.prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
- input_errors[i] = "Input not found or already spent";
+ input_errors[i] = _("Input not found or already spent");
continue;
}
const CScript& prevPubKey = coin->second.out.scriptPubKey;
@@ -677,7 +660,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
// amount must be specified for valid segwit signature
if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) {
- input_errors[i] = "Missing amount";
+ input_errors[i] = _("Missing amount");
continue;
}
@@ -685,12 +668,12 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) {
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
- input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";
+ input_errors[i] = Untranslated("Unable to sign input, invalid stack size (possibly missing key)");
} else if (serror == SCRIPT_ERR_SIG_NULLFAIL) {
// Verification failed (possibly due to insufficient signatures).
- input_errors[i] = "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)";
+ input_errors[i] = Untranslated("CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)");
} else {
- input_errors[i] = ScriptErrorString(serror);
+ input_errors[i] = Untranslated(ScriptErrorString(serror));
}
} else {
// If this input succeeds, make sure there is no error set for it
diff --git a/src/script/sign.h b/src/script/sign.h
index b4e7318892..6d3479c143 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -21,6 +21,7 @@ class CScript;
class CTransaction;
class SigningProvider;
+struct bilingual_str;
struct CMutableTransaction;
/** Interface for signature creators. */
@@ -44,8 +45,8 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator {
const PrecomputedTransactionData* m_txdata;
public:
- MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL);
- MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL);
+ MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn);
+ MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn);
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
@@ -178,6 +179,6 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script);
bool IsSegWitOutput(const SigningProvider& provider, const CScript& script);
/** Sign the CMutableTransaction */
-bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* provider, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors);
+bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* provider, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors);
#endif // BITCOIN_SCRIPT_SIGN_H
diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h
index 939ae10622..fbce61c6a9 100644
--- a/src/script/signingprovider.h
+++ b/src/script/signingprovider.h
@@ -26,6 +26,30 @@ public:
virtual bool HaveKey(const CKeyID &address) const { return false; }
virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; }
virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; }
+
+ bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
+ {
+ for (const auto& id : pubkey.GetKeyIDs()) {
+ if (GetKey(id, key)) return true;
+ }
+ return false;
+ }
+
+ bool GetPubKeyByXOnly(const XOnlyPubKey& pubkey, CPubKey& out) const
+ {
+ for (const auto& id : pubkey.GetKeyIDs()) {
+ if (GetPubKey(id, out)) return true;
+ }
+ return false;
+ }
+
+ bool GetKeyOriginByXOnly(const XOnlyPubKey& pubkey, KeyOriginInfo& info) const
+ {
+ for (const auto& id : pubkey.GetKeyIDs()) {
+ if (GetKeyOrigin(id, info)) return true;
+ }
+ return false;
+ }
};
extern const SigningProvider& DUMMY_SIGNING_PROVIDER;
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index b8349bb9ab..67a79a157c 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -504,6 +504,7 @@ WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_
TaprootSpendData TaprootBuilder::GetSpendData() const
{
+ assert(IsComplete());
TaprootSpendData spd;
spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash;
spd.internal_key = m_internal_key;
diff --git a/src/script/standard.h b/src/script/standard.h
index ac4e2f3276..78492733db 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -227,8 +227,11 @@ struct TaprootSpendData
/** The Merkle root of the script tree (0 if no scripts). */
uint256 merkle_root;
/** Map from (script, leaf_version) to (sets of) control blocks.
- * The control blocks are sorted by size, so that the signing logic can
- * easily prefer the cheapest one. */
+ * More than one control block for a given script is only possible if it
+ * appears in multiple branches of the tree. We keep them all so that
+ * inference can reconstruct the full tree. Within each set, the control
+ * blocks are sorted by size, so that the signing logic can easily
+ * prefer the cheapest one. */
std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts;
/** Merge other TaprootSpendData (for the same scriptPubKey) into this. */
void Merge(TaprootSpendData other);
@@ -252,7 +255,7 @@ private:
/** Merkle hash of this node. */
uint256 hash;
/** Tracked leaves underneath this node (either from the node itself, or its children).
- * The merkle_branch field for each is the partners to get to *this* node. */
+ * The merkle_branch field of each is the partners to get to *this* node. */
std::vector<LeafInfo> leaves;
};
/** Whether the builder is in a valid state so far. */
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index 79c7102c4f..cd5dc2370f 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -1,7 +1,10 @@
// Copyright (c) 2012-2020 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 <addrdb.h>
#include <addrman.h>
+#include <chainparams.h>
#include <test/data/asmap.raw.h>
#include <test/util/setup_common.h>
#include <util/asmap.h>
@@ -15,30 +18,76 @@
#include <optional>
#include <string>
+using namespace std::literals;
+
+class CAddrManSerializationMock : public CAddrMan
+{
+public:
+ virtual void Serialize(CDataStream& s) const = 0;
+
+ CAddrManSerializationMock()
+ : CAddrMan(/* deterministic */ true, /* consistency_check_ratio */ 100)
+ {}
+};
+
+class CAddrManUncorrupted : public CAddrManSerializationMock
+{
+public:
+ void Serialize(CDataStream& s) const override
+ {
+ CAddrMan::Serialize(s);
+ }
+};
+
+class CAddrManCorrupted : public CAddrManSerializationMock
+{
+public:
+ void Serialize(CDataStream& s) const override
+ {
+ // Produces corrupt output that claims addrman has 20 addrs when it only has one addr.
+ unsigned char nVersion = 1;
+ s << nVersion;
+ s << ((unsigned char)32);
+ s << uint256::ONE;
+ s << 10; // nNew
+ s << 10; // nTried
+
+ int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
+ s << nUBuckets;
+
+ CService serv;
+ BOOST_CHECK(Lookup("252.1.1.1", serv, 7777, false));
+ CAddress addr = CAddress(serv, NODE_NONE);
+ CNetAddr resolved;
+ BOOST_CHECK(LookupHost("252.2.2.2", resolved, false));
+ CAddrInfo info = CAddrInfo(addr, resolved);
+ s << info;
+ }
+};
+
+static CDataStream AddrmanToStream(const CAddrManSerializationMock& _addrman)
+{
+ CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
+ ssPeersIn << Params().MessageStart();
+ ssPeersIn << _addrman;
+ std::string str = ssPeersIn.str();
+ std::vector<unsigned char> vchData(str.begin(), str.end());
+ return CDataStream(vchData, SER_DISK, CLIENT_VERSION);
+}
+
class CAddrManTest : public CAddrMan
{
private:
bool deterministic;
public:
explicit CAddrManTest(bool makeDeterministic = true,
- std::vector<bool> asmap = std::vector<bool>())
+ std::vector<bool> asmap = std::vector<bool>())
+ : CAddrMan(makeDeterministic, /* consistency_check_ratio */ 100)
{
- if (makeDeterministic) {
- // Set addrman addr placement to be deterministic.
- MakeDeterministic();
- }
deterministic = makeDeterministic;
m_asmap = asmap;
}
- //! Ensure that bucket placement is always the same for testing purposes.
- void MakeDeterministic()
- {
- LOCK(cs);
- nKey.SetNull();
- insecure_rand = FastRandomContext(true);
- }
-
CAddrInfo* Find(const CNetAddr& addr, int* pnId = nullptr)
{
LOCK(cs);
@@ -83,16 +132,6 @@ public:
int64_t nLastTry = GetAdjustedTime()-61;
Attempt(addr, count_failure, nLastTry);
}
-
- void Clear()
- {
- CAddrMan::Clear();
- if (deterministic) {
- LOCK(cs);
- nKey.SetNull();
- insecure_rand = FastRandomContext(true);
- }
- }
};
static CNetAddr ResolveIP(const std::string& ip)
@@ -126,27 +165,27 @@ BOOST_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(addrman_simple)
{
- CAddrManTest addrman;
+ auto addrman = std::make_unique<CAddrManTest>();
CNetAddr source = ResolveIP("252.2.2.2");
// Test: Does Addrman respond correctly when empty.
- BOOST_CHECK_EQUAL(addrman.size(), 0U);
- CAddrInfo addr_null = addrman.Select();
+ BOOST_CHECK_EQUAL(addrman->size(), 0U);
+ CAddrInfo addr_null = addrman->Select();
BOOST_CHECK_EQUAL(addr_null.ToString(), "[::]:0");
// Test: Does Addrman::Add work as expected.
CService addr1 = ResolveService("250.1.1.1", 8333);
- BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 1U);
- CAddrInfo addr_ret1 = addrman.Select();
+ BOOST_CHECK(addrman->Add({CAddress(addr1, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman->size(), 1U);
+ CAddrInfo addr_ret1 = addrman->Select();
BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333");
// Test: Does IP address deduplication work correctly.
// Expected dup IP should not be added.
CService addr1_dup = ResolveService("250.1.1.1", 8333);
- BOOST_CHECK(!addrman.Add(CAddress(addr1_dup, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 1U);
+ BOOST_CHECK(!addrman->Add({CAddress(addr1_dup, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman->size(), 1U);
// Test: New table has one addr and we add a diff addr we should
@@ -156,21 +195,16 @@ BOOST_AUTO_TEST_CASE(addrman_simple)
// success.
CService addr2 = ResolveService("250.1.1.2", 8333);
- BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source));
- BOOST_CHECK(addrman.size() >= 1);
-
- // Test: AddrMan::Clear() should empty the new table.
- addrman.Clear();
- BOOST_CHECK_EQUAL(addrman.size(), 0U);
- CAddrInfo addr_null2 = addrman.Select();
- BOOST_CHECK_EQUAL(addr_null2.ToString(), "[::]:0");
+ BOOST_CHECK(addrman->Add({CAddress(addr2, NODE_NONE)}, source));
+ BOOST_CHECK(addrman->size() >= 1);
- // Test: AddrMan::Add multiple addresses works as expected
+ // Test: reset addrman and test AddrMan::Add multiple addresses works as expected
+ addrman = std::make_unique<CAddrManTest>();
std::vector<CAddress> vAddr;
vAddr.push_back(CAddress(ResolveService("250.1.1.3", 8333), NODE_NONE));
vAddr.push_back(CAddress(ResolveService("250.1.1.4", 8333), NODE_NONE));
- BOOST_CHECK(addrman.Add(vAddr, source));
- BOOST_CHECK(addrman.size() >= 1);
+ BOOST_CHECK(addrman->Add(vAddr, source));
+ BOOST_CHECK(addrman->size() >= 1);
}
BOOST_AUTO_TEST_CASE(addrman_ports)
@@ -183,11 +217,11 @@ BOOST_AUTO_TEST_CASE(addrman_ports)
// Test 7; Addr with same IP but diff port does not replace existing addr.
CService addr1 = ResolveService("250.1.1.1", 8333);
- BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr1, NODE_NONE)}, source));
BOOST_CHECK_EQUAL(addrman.size(), 1U);
CService addr1_port = ResolveService("250.1.1.1", 8334);
- BOOST_CHECK(!addrman.Add(CAddress(addr1_port, NODE_NONE), source));
+ BOOST_CHECK(!addrman.Add({CAddress(addr1_port, NODE_NONE)}, source));
BOOST_CHECK_EQUAL(addrman.size(), 1U);
CAddrInfo addr_ret2 = addrman.Select();
BOOST_CHECK_EQUAL(addr_ret2.ToString(), "250.1.1.1:8333");
@@ -210,7 +244,7 @@ BOOST_AUTO_TEST_CASE(addrman_select)
// Test: Select from new with 1 addr in new.
CService addr1 = ResolveService("250.1.1.1", 8333);
- BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr1, NODE_NONE)}, source));
BOOST_CHECK_EQUAL(addrman.size(), 1U);
bool newOnly = true;
@@ -234,20 +268,20 @@ BOOST_AUTO_TEST_CASE(addrman_select)
CService addr3 = ResolveService("250.3.2.2", 9999);
CService addr4 = ResolveService("250.3.3.3", 9999);
- BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), ResolveService("250.3.1.1", 8333)));
- BOOST_CHECK(addrman.Add(CAddress(addr3, NODE_NONE), ResolveService("250.3.1.1", 8333)));
- BOOST_CHECK(addrman.Add(CAddress(addr4, NODE_NONE), ResolveService("250.4.1.1", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr2, NODE_NONE)}, ResolveService("250.3.1.1", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr3, NODE_NONE)}, ResolveService("250.3.1.1", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr4, NODE_NONE)}, ResolveService("250.4.1.1", 8333)));
// Add three addresses to tried table.
CService addr5 = ResolveService("250.4.4.4", 8333);
CService addr6 = ResolveService("250.4.5.5", 7777);
CService addr7 = ResolveService("250.4.6.6", 8333);
- BOOST_CHECK(addrman.Add(CAddress(addr5, NODE_NONE), ResolveService("250.3.1.1", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr5, NODE_NONE)}, ResolveService("250.3.1.1", 8333)));
addrman.Good(CAddress(addr5, NODE_NONE));
- BOOST_CHECK(addrman.Add(CAddress(addr6, NODE_NONE), ResolveService("250.3.1.1", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr6, NODE_NONE)}, ResolveService("250.3.1.1", 8333)));
addrman.Good(CAddress(addr6, NODE_NONE));
- BOOST_CHECK(addrman.Add(CAddress(addr7, NODE_NONE), ResolveService("250.1.1.3", 8333)));
+ BOOST_CHECK(addrman.Add({CAddress(addr7, NODE_NONE)}, ResolveService("250.1.1.3", 8333)));
addrman.Good(CAddress(addr7, NODE_NONE));
// Test: 6 addrs + 1 addr from last test = 7.
@@ -267,24 +301,27 @@ BOOST_AUTO_TEST_CASE(addrman_new_collisions)
CNetAddr source = ResolveIP("252.2.2.2");
- BOOST_CHECK_EQUAL(addrman.size(), 0U);
+ uint32_t num_addrs{0};
+
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs);
- for (unsigned int i = 1; i < 18; i++) {
- CService addr = ResolveService("250.1.1." + ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ while (num_addrs < 22) { // Magic number! 250.1.1.1 - 250.1.1.22 do not collide with deterministic key = 1
+ CService addr = ResolveService("250.1.1." + ToString(++num_addrs));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
//Test: No collision in new table yet.
- BOOST_CHECK_EQUAL(addrman.size(), i);
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs);
}
//Test: new table collision!
- CService addr1 = ResolveService("250.1.1.18");
- BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 17U);
-
- CService addr2 = ResolveService("250.1.1.19");
- BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 18U);
+ CService addr1 = ResolveService("250.1.1." + ToString(++num_addrs));
+ uint32_t collisions{1};
+ BOOST_CHECK(addrman.Add({CAddress(addr1, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions);
+
+ CService addr2 = ResolveService("250.1.1." + ToString(++num_addrs));
+ BOOST_CHECK(addrman.Add({CAddress(addr2, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions);
}
BOOST_AUTO_TEST_CASE(addrman_tried_collisions)
@@ -293,25 +330,28 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions)
CNetAddr source = ResolveIP("252.2.2.2");
- BOOST_CHECK_EQUAL(addrman.size(), 0U);
+ uint32_t num_addrs{0};
- for (unsigned int i = 1; i < 80; i++) {
- CService addr = ResolveService("250.1.1." + ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs);
+
+ while (num_addrs < 64) { // Magic number! 250.1.1.1 - 250.1.1.64 do not collide with deterministic key = 1
+ CService addr = ResolveService("250.1.1." + ToString(++num_addrs));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(CAddress(addr, NODE_NONE));
//Test: No collision in tried table yet.
- BOOST_CHECK_EQUAL(addrman.size(), i);
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs);
}
//Test: tried table collision!
- CService addr1 = ResolveService("250.1.1.80");
- BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 79U);
-
- CService addr2 = ResolveService("250.1.1.81");
- BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 80U);
+ CService addr1 = ResolveService("250.1.1." + ToString(++num_addrs));
+ uint32_t collisions{1};
+ BOOST_CHECK(addrman.Add({CAddress(addr1, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions);
+
+ CService addr2 = ResolveService("250.1.1." + ToString(++num_addrs));
+ BOOST_CHECK(addrman.Add({CAddress(addr2, NODE_NONE)}, source));
+ BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions);
}
BOOST_AUTO_TEST_CASE(addrman_find)
@@ -327,9 +367,9 @@ BOOST_AUTO_TEST_CASE(addrman_find)
CNetAddr source1 = ResolveIP("250.1.2.1");
CNetAddr source2 = ResolveIP("250.1.2.2");
- BOOST_CHECK(addrman.Add(addr1, source1));
- BOOST_CHECK(!addrman.Add(addr2, source2));
- BOOST_CHECK(addrman.Add(addr3, source1));
+ BOOST_CHECK(addrman.Add({addr1}, source1));
+ BOOST_CHECK(!addrman.Add({addr2}, source2));
+ BOOST_CHECK(addrman.Add({addr3}, source1));
// Test: ensure Find returns an IP matching what we searched on.
CAddrInfo* info1 = addrman.Find(addr1);
@@ -411,11 +451,8 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
CNetAddr source2 = ResolveIP("250.2.3.3");
// Test: Ensure GetAddr works with new addresses.
- BOOST_CHECK(addrman.Add(addr1, source1));
- BOOST_CHECK(addrman.Add(addr2, source2));
- BOOST_CHECK(addrman.Add(addr3, source1));
- BOOST_CHECK(addrman.Add(addr4, source2));
- BOOST_CHECK(addrman.Add(addr5, source1));
+ BOOST_CHECK(addrman.Add({addr1, addr3, addr5}, source1));
+ BOOST_CHECK(addrman.Add({addr2, addr4}, source2));
BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ std::nullopt).size(), 5U);
// Net processing asks for 23% of addresses. 23% of 5 is 1 rounded down.
@@ -436,7 +473,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
// Ensure that for all addrs in addrman, isTerrible == false.
addr.nTime = GetAdjustedTime();
- addrman.Add(addr, ResolveIP(strAddr));
+ addrman.Add({addr}, ResolveIP(strAddr));
if (i % 8 == 0)
addrman.Good(addr);
}
@@ -722,23 +759,23 @@ BOOST_AUTO_TEST_CASE(addrman_serialization)
{
std::vector<bool> asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
- CAddrManTest addrman_asmap1(true, asmap1);
- CAddrManTest addrman_asmap1_dup(true, asmap1);
- CAddrManTest addrman_noasmap;
+ auto addrman_asmap1 = std::make_unique<CAddrManTest>(true, asmap1);
+ auto addrman_asmap1_dup = std::make_unique<CAddrManTest>(true, asmap1);
+ auto addrman_noasmap = std::make_unique<CAddrManTest>();
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
CAddress addr = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
CNetAddr default_source;
- addrman_asmap1.Add(addr, default_source);
+ addrman_asmap1->Add({addr}, default_source);
- stream << addrman_asmap1;
+ stream << *addrman_asmap1;
// serizalizing/deserializing addrman with the same asmap
- stream >> addrman_asmap1_dup;
+ stream >> *addrman_asmap1_dup;
- std::pair<int, int> bucketAndEntry_asmap1 = addrman_asmap1.GetBucketAndEntry(addr);
- std::pair<int, int> bucketAndEntry_asmap1_dup = addrman_asmap1_dup.GetBucketAndEntry(addr);
+ std::pair<int, int> bucketAndEntry_asmap1 = addrman_asmap1->GetBucketAndEntry(addr);
+ std::pair<int, int> bucketAndEntry_asmap1_dup = addrman_asmap1_dup->GetBucketAndEntry(addr);
BOOST_CHECK(bucketAndEntry_asmap1.second != -1);
BOOST_CHECK(bucketAndEntry_asmap1_dup.second != -1);
@@ -746,40 +783,39 @@ BOOST_AUTO_TEST_CASE(addrman_serialization)
BOOST_CHECK(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second);
// deserializing asmaped peers.dat to non-asmaped addrman
- stream << addrman_asmap1;
- stream >> addrman_noasmap;
- std::pair<int, int> bucketAndEntry_noasmap = addrman_noasmap.GetBucketAndEntry(addr);
+ stream << *addrman_asmap1;
+ stream >> *addrman_noasmap;
+ std::pair<int, int> bucketAndEntry_noasmap = addrman_noasmap->GetBucketAndEntry(addr);
BOOST_CHECK(bucketAndEntry_noasmap.second != -1);
BOOST_CHECK(bucketAndEntry_asmap1.first != bucketAndEntry_noasmap.first);
BOOST_CHECK(bucketAndEntry_asmap1.second != bucketAndEntry_noasmap.second);
// deserializing non-asmaped peers.dat to asmaped addrman
- addrman_asmap1.Clear();
- addrman_noasmap.Clear();
- addrman_noasmap.Add(addr, default_source);
- stream << addrman_noasmap;
- stream >> addrman_asmap1;
- std::pair<int, int> bucketAndEntry_asmap1_deser = addrman_asmap1.GetBucketAndEntry(addr);
+ addrman_asmap1 = std::make_unique<CAddrManTest>(true, asmap1);
+ addrman_noasmap = std::make_unique<CAddrManTest>();
+ addrman_noasmap->Add({addr}, default_source);
+ stream << *addrman_noasmap;
+ stream >> *addrman_asmap1;
+ std::pair<int, int> bucketAndEntry_asmap1_deser = addrman_asmap1->GetBucketAndEntry(addr);
BOOST_CHECK(bucketAndEntry_asmap1_deser.second != -1);
BOOST_CHECK(bucketAndEntry_asmap1_deser.first != bucketAndEntry_noasmap.first);
BOOST_CHECK(bucketAndEntry_asmap1_deser.first == bucketAndEntry_asmap1_dup.first);
BOOST_CHECK(bucketAndEntry_asmap1_deser.second == bucketAndEntry_asmap1_dup.second);
// used to map to different buckets, now maps to the same bucket.
- addrman_asmap1.Clear();
- addrman_noasmap.Clear();
+ addrman_asmap1 = std::make_unique<CAddrManTest>(true, asmap1);
+ addrman_noasmap = std::make_unique<CAddrManTest>();
CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE);
- addrman_noasmap.Add(addr, default_source);
- addrman_noasmap.Add(addr2, default_source);
- std::pair<int, int> bucketAndEntry_noasmap_addr1 = addrman_noasmap.GetBucketAndEntry(addr1);
- std::pair<int, int> bucketAndEntry_noasmap_addr2 = addrman_noasmap.GetBucketAndEntry(addr2);
+ addrman_noasmap->Add({addr, addr2}, default_source);
+ std::pair<int, int> bucketAndEntry_noasmap_addr1 = addrman_noasmap->GetBucketAndEntry(addr1);
+ std::pair<int, int> bucketAndEntry_noasmap_addr2 = addrman_noasmap->GetBucketAndEntry(addr2);
BOOST_CHECK(bucketAndEntry_noasmap_addr1.first != bucketAndEntry_noasmap_addr2.first);
BOOST_CHECK(bucketAndEntry_noasmap_addr1.second != bucketAndEntry_noasmap_addr2.second);
- stream << addrman_noasmap;
- stream >> addrman_asmap1;
- std::pair<int, int> bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1.GetBucketAndEntry(addr1);
- std::pair<int, int> bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1.GetBucketAndEntry(addr2);
+ stream << *addrman_noasmap;
+ stream >> *addrman_asmap1;
+ std::pair<int, int> bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1->GetBucketAndEntry(addr1);
+ std::pair<int, int> bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1->GetBucketAndEntry(addr2);
BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.first == bucketAndEntry_asmap1_deser_addr2.first);
BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second);
}
@@ -788,7 +824,7 @@ BOOST_AUTO_TEST_CASE(remove_invalid)
{
// Confirm that invalid addresses are ignored in unserialization.
- CAddrManTest addrman;
+ auto addrman = std::make_unique<CAddrManTest>();
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
const CAddress new1{ResolveService("5.5.5.5"), NODE_NONE};
@@ -796,12 +832,12 @@ BOOST_AUTO_TEST_CASE(remove_invalid)
const CAddress tried1{ResolveService("7.7.7.7"), NODE_NONE};
const CAddress tried2{ResolveService("8.8.8.8"), NODE_NONE};
- addrman.Add({new1, tried1, new2, tried2}, CNetAddr{});
- addrman.Good(tried1);
- addrman.Good(tried2);
- BOOST_REQUIRE_EQUAL(addrman.size(), 4);
+ addrman->Add({new1, tried1, new2, tried2}, CNetAddr{});
+ addrman->Good(tried1);
+ addrman->Good(tried2);
+ BOOST_REQUIRE_EQUAL(addrman->size(), 4);
- stream << addrman;
+ stream << *addrman;
const std::string str{stream.str()};
size_t pos;
@@ -820,9 +856,9 @@ BOOST_AUTO_TEST_CASE(remove_invalid)
BOOST_REQUIRE(pos + sizeof(tried2_raw_replacement) <= stream.size());
memcpy(stream.data() + pos, tried2_raw_replacement, sizeof(tried2_raw_replacement));
- addrman.Clear();
- stream >> addrman;
- BOOST_CHECK_EQUAL(addrman.size(), 2);
+ addrman = std::make_unique<CAddrManTest>();
+ stream >> *addrman;
+ BOOST_CHECK_EQUAL(addrman->size(), 2);
}
BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision)
@@ -838,7 +874,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision)
CNetAddr source = ResolveIP("252.2.2.2");
for (unsigned int i = 1; i < 23; i++) {
CService addr = ResolveService("250.1.1."+ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
// No collisions yet.
@@ -861,11 +897,11 @@ BOOST_AUTO_TEST_CASE(addrman_noevict)
{
CAddrManTest addrman;
- // Add twenty two addresses.
+ // Add 35 addresses.
CNetAddr source = ResolveIP("252.2.2.2");
- for (unsigned int i = 1; i < 23; i++) {
+ for (unsigned int i = 1; i < 36; i++) {
CService addr = ResolveService("250.1.1."+ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
// No collision yet.
@@ -873,22 +909,22 @@ BOOST_AUTO_TEST_CASE(addrman_noevict)
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
}
- // Collision between 23 and 19.
- CService addr23 = ResolveService("250.1.1.23");
- BOOST_CHECK(addrman.Add(CAddress(addr23, NODE_NONE), source));
- addrman.Good(addr23);
+ // Collision between 36 and 19.
+ CService addr36 = ResolveService("250.1.1.36");
+ BOOST_CHECK(addrman.Add({CAddress(addr36, NODE_NONE)}, source));
+ addrman.Good(addr36);
- BOOST_CHECK(addrman.size() == 23);
- BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.19:0");
+ BOOST_CHECK(addrman.size() == 36);
+ BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().ToString(), "250.1.1.19:0");
- // 23 should be discarded and 19 not evicted.
+ // 36 should be discarded and 19 not evicted.
addrman.ResolveCollisions();
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
// Lets create two collisions.
- for (unsigned int i = 24; i < 33; i++) {
+ for (unsigned int i = 37; i < 59; i++) {
CService addr = ResolveService("250.1.1."+ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
BOOST_CHECK(addrman.size() == i);
@@ -896,17 +932,17 @@ BOOST_AUTO_TEST_CASE(addrman_noevict)
}
// Cause a collision.
- CService addr33 = ResolveService("250.1.1.33");
- BOOST_CHECK(addrman.Add(CAddress(addr33, NODE_NONE), source));
- addrman.Good(addr33);
- BOOST_CHECK(addrman.size() == 33);
+ CService addr59 = ResolveService("250.1.1.59");
+ BOOST_CHECK(addrman.Add({CAddress(addr59, NODE_NONE)}, source));
+ addrman.Good(addr59);
+ BOOST_CHECK(addrman.size() == 59);
- BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.27:0");
+ BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().ToString(), "250.1.1.10:0");
// Cause a second collision.
- BOOST_CHECK(!addrman.Add(CAddress(addr23, NODE_NONE), source));
- addrman.Good(addr23);
- BOOST_CHECK(addrman.size() == 33);
+ BOOST_CHECK(!addrman.Add({CAddress(addr36, NODE_NONE)}, source));
+ addrman.Good(addr36);
+ BOOST_CHECK(addrman.size() == 59);
BOOST_CHECK(addrman.SelectTriedCollision().ToString() != "[::]:0");
addrman.ResolveCollisions();
@@ -922,11 +958,11 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks)
// Empty addrman should return blank addrman info.
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
- // Add twenty two addresses.
+ // Add 35 addresses
CNetAddr source = ResolveIP("252.2.2.2");
- for (unsigned int i = 1; i < 23; i++) {
+ for (unsigned int i = 1; i < 36; i++) {
CService addr = ResolveService("250.1.1."+ToString(i));
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
// No collision yet.
@@ -934,38 +970,111 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks)
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
}
- // Collision between 23 and 19.
- CService addr = ResolveService("250.1.1.23");
- BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source));
+ // Collision between 36 and 19.
+ CService addr = ResolveService("250.1.1.36");
+ BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
- BOOST_CHECK(addrman.size() == 23);
+ BOOST_CHECK_EQUAL(addrman.size(), 36);
CAddrInfo info = addrman.SelectTriedCollision();
- BOOST_CHECK(info.ToString() == "250.1.1.19:0");
+ BOOST_CHECK_EQUAL(info.ToString(), "250.1.1.19:0");
// Ensure test of address fails, so that it is evicted.
addrman.SimConnFail(info);
- // Should swap 23 for 19.
+ // Should swap 36 for 19.
addrman.ResolveCollisions();
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
- // If 23 was swapped for 19, then this should cause no collisions.
- BOOST_CHECK(!addrman.Add(CAddress(addr, NODE_NONE), source));
+ // If 36 was swapped for 19, then this should cause no collisions.
+ BOOST_CHECK(!addrman.Add({CAddress(addr, NODE_NONE)}, source));
addrman.Good(addr);
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
- // If we insert 19 is should collide with 23.
+ // If we insert 19 it should collide with 36
CService addr19 = ResolveService("250.1.1.19");
- BOOST_CHECK(!addrman.Add(CAddress(addr19, NODE_NONE), source));
+ BOOST_CHECK(!addrman.Add({CAddress(addr19, NODE_NONE)}, source));
addrman.Good(addr19);
- BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.23:0");
+ BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().ToString(), "250.1.1.36:0");
addrman.ResolveCollisions();
BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0");
}
+BOOST_AUTO_TEST_CASE(caddrdb_read)
+{
+ CAddrManUncorrupted addrmanUncorrupted;
+
+ CService addr1, addr2, addr3;
+ BOOST_CHECK(Lookup("250.7.1.1", addr1, 8333, false));
+ BOOST_CHECK(Lookup("250.7.2.2", addr2, 9999, false));
+ BOOST_CHECK(Lookup("250.7.3.3", addr3, 9999, false));
+ BOOST_CHECK(Lookup("250.7.3.3"s, addr3, 9999, false));
+ BOOST_CHECK(!Lookup("250.7.3.3\0example.com"s, addr3, 9999, false));
+
+ // Add three addresses to new table.
+ CService source;
+ BOOST_CHECK(Lookup("252.5.1.1", source, 8333, false));
+ std::vector<CAddress> addresses{CAddress(addr1, NODE_NONE), CAddress(addr2, NODE_NONE), CAddress(addr3, NODE_NONE)};
+ BOOST_CHECK(addrmanUncorrupted.Add(addresses, source));
+ BOOST_CHECK(addrmanUncorrupted.size() == 3);
+
+ // Test that the de-serialization does not throw an exception.
+ CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted);
+ bool exceptionThrown = false;
+ CAddrMan addrman1(/* deterministic */ false, /* consistency_check_ratio */ 100);
+
+ BOOST_CHECK(addrman1.size() == 0);
+ try {
+ unsigned char pchMsgTmp[4];
+ ssPeers1 >> pchMsgTmp;
+ ssPeers1 >> addrman1;
+ } catch (const std::exception&) {
+ exceptionThrown = true;
+ }
+
+ BOOST_CHECK(addrman1.size() == 3);
+ BOOST_CHECK(exceptionThrown == false);
+
+ // Test that CAddrDB::Read creates an addrman with the correct number of addrs.
+ CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted);
+
+ CAddrMan addrman2(/* deterministic */ false, /* consistency_check_ratio */ 100);
+ BOOST_CHECK(addrman2.size() == 0);
+ BOOST_CHECK(CAddrDB::Read(addrman2, ssPeers2));
+ BOOST_CHECK(addrman2.size() == 3);
+}
+
+
+BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted)
+{
+ CAddrManCorrupted addrmanCorrupted;
+
+ // Test that the de-serialization of corrupted addrman throws an exception.
+ CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted);
+ bool exceptionThrown = false;
+ CAddrMan addrman1(/* deterministic */ false, /* consistency_check_ratio */ 100);
+ BOOST_CHECK(addrman1.size() == 0);
+ try {
+ unsigned char pchMsgTmp[4];
+ ssPeers1 >> pchMsgTmp;
+ ssPeers1 >> addrman1;
+ } catch (const std::exception&) {
+ exceptionThrown = true;
+ }
+ // Even through de-serialization failed addrman is not left in a clean state.
+ BOOST_CHECK(addrman1.size() == 1);
+ BOOST_CHECK(exceptionThrown);
+
+ // Test that CAddrDB::Read fails if peers.dat is corrupt
+ CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted);
+
+ CAddrMan addrman2(/* deterministic */ false, /* consistency_check_ratio */ 100);
+ BOOST_CHECK(addrman2.size() == 0);
+ BOOST_CHECK(!CAddrDB::Read(addrman2, ssPeers2));
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp
index fb16c92647..a89868e1ef 100644
--- a/src/test/bip32_tests.cpp
+++ b/src/test/bip32_tests.cpp
@@ -14,6 +14,8 @@
#include <string>
#include <vector>
+namespace {
+
struct TestDerivation {
std::string pub;
std::string prv;
@@ -99,7 +101,26 @@ TestVector test4 =
"xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1",
0);
-static void RunTest(const TestVector &test) {
+const std::vector<std::string> TEST5 = {
+ "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm",
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH",
+ "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn",
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ",
+ "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4",
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J",
+ "xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv",
+ "xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ",
+ "xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN",
+ "xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8",
+ "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4",
+ "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9",
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx",
+ "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G",
+ "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY",
+ "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL"
+};
+
+void RunTest(const TestVector &test) {
std::vector<unsigned char> seed = ParseHex(test.strHexMaster);
CExtKey key;
CExtPubKey pubkey;
@@ -133,6 +154,8 @@ static void RunTest(const TestVector &test) {
}
}
+} // namespace
+
BOOST_FIXTURE_TEST_SUITE(bip32_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(bip32_test1) {
@@ -151,4 +174,13 @@ BOOST_AUTO_TEST_CASE(bip32_test4) {
RunTest(test4);
}
+BOOST_AUTO_TEST_CASE(bip32_test5) {
+ for (const auto& str : TEST5) {
+ auto dec_extkey = DecodeExtKey(str);
+ auto dec_extpubkey = DecodeExtPubKey(str);
+ BOOST_CHECK_MESSAGE(!dec_extkey.key.IsValid(), "Decoding '" + str + "' as xprv should fail");
+ BOOST_CHECK_MESSAGE(!dec_extpubkey.pubkey.IsValid(), "Decoding '" + str + "' as xpub should fail");
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp
index edec5f0a31..5b3b39fdb8 100644
--- a/src/test/crypto_tests.cpp
+++ b/src/test/crypto_tests.cpp
@@ -617,7 +617,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa
ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size());
// create a chacha20 instance to compare against
- ChaCha20 cmp_ctx(aead_K_2.data(), 32);
+ ChaCha20 cmp_ctx(aead_K_1.data(), 32);
// encipher
bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true);
@@ -708,8 +708,8 @@ BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector)
"b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080");
TestChaCha20Poly1305AEAD(true, 255,
"ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9",
- "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017",
"3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a",
"f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd");
diff --git a/src/test/fuzz/addrdb.cpp b/src/test/fuzz/addrdb.cpp
deleted file mode 100644
index d15c785673..0000000000
--- a/src/test/fuzz/addrdb.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2020 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 <addrdb.h>
-#include <test/fuzz/FuzzedDataProvider.h>
-#include <test/fuzz/fuzz.h>
-#include <test/fuzz/util.h>
-
-#include <cassert>
-#include <cstdint>
-#include <optional>
-#include <string>
-#include <vector>
-
-FUZZ_TARGET(addrdb)
-{
- FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
-
- // The point of this code is to exercise all CBanEntry constructors.
- const CBanEntry ban_entry = [&] {
- switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 2)) {
- case 0:
- return CBanEntry{fuzzed_data_provider.ConsumeIntegral<int64_t>()};
- break;
- case 1: {
- const std::optional<CBanEntry> ban_entry = ConsumeDeserializable<CBanEntry>(fuzzed_data_provider);
- if (ban_entry) {
- return *ban_entry;
- }
- break;
- }
- }
- return CBanEntry{};
- }();
- (void)ban_entry; // currently unused
-}
diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp
index 344d1dde8e..95aa53bff4 100644
--- a/src/test/fuzz/addrman.cpp
+++ b/src/test/fuzz/addrman.cpp
@@ -12,6 +12,7 @@
#include <time.h>
#include <util/asmap.h>
+#include <cassert>
#include <cstdint>
#include <optional>
#include <string>
@@ -25,10 +26,201 @@ void initialize_addrman()
class CAddrManDeterministic : public CAddrMan
{
public:
- void MakeDeterministic(const uint256& random_seed)
+ FuzzedDataProvider& m_fuzzed_data_provider;
+
+ explicit CAddrManDeterministic(FuzzedDataProvider& fuzzed_data_provider)
+ : CAddrMan(/* deterministic */ true, /* consistency_check_ratio */ 0)
+ , m_fuzzed_data_provider(fuzzed_data_provider)
+ {
+ WITH_LOCK(cs, insecure_rand = FastRandomContext{ConsumeUInt256(fuzzed_data_provider)});
+ if (fuzzed_data_provider.ConsumeBool()) {
+ m_asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
+ if (!SanityCheckASMap(m_asmap)) {
+ m_asmap.clear();
+ }
+ }
+ }
+
+ /**
+ * Generate a random address. Always returns a valid address.
+ */
+ CNetAddr RandAddr() EXCLUSIVE_LOCKS_REQUIRED(cs)
+ {
+ CNetAddr addr;
+ if (m_fuzzed_data_provider.remaining_bytes() > 1 && m_fuzzed_data_provider.ConsumeBool()) {
+ addr = ConsumeNetAddr(m_fuzzed_data_provider);
+ } else {
+ // The networks [1..6] correspond to CNetAddr::BIP155Network (private).
+ static const std::map<uint8_t, uint8_t> net_len_map = {{1, ADDR_IPV4_SIZE},
+ {2, ADDR_IPV6_SIZE},
+ {4, ADDR_TORV3_SIZE},
+ {5, ADDR_I2P_SIZE},
+ {6, ADDR_CJDNS_SIZE}};
+ uint8_t net = insecure_rand.randrange(5) + 1; // [1..5]
+ if (net == 3) {
+ net = 6;
+ }
+
+ CDataStream s(SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT);
+
+ s << net;
+ s << insecure_rand.randbytes(net_len_map.at(net));
+
+ s >> addr;
+ }
+
+ // Return a dummy IPv4 5.5.5.5 if we generated an invalid address.
+ if (!addr.IsValid()) {
+ in_addr v4_addr = {};
+ v4_addr.s_addr = 0x05050505;
+ addr = CNetAddr{v4_addr};
+ }
+
+ return addr;
+ }
+
+ /**
+ * Fill this addrman with lots of addresses from lots of sources.
+ */
+ void Fill()
+ {
+ LOCK(cs);
+
+ // Add some of the addresses directly to the "tried" table.
+
+ // 0, 1, 2, 3 corresponding to 0%, 100%, 50%, 33%
+ const size_t n = m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 3);
+
+ const size_t num_sources = m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(10, 50);
+ CNetAddr prev_source;
+ // Use insecure_rand inside the loops instead of m_fuzzed_data_provider because when
+ // the latter is exhausted it just returns 0.
+ for (size_t i = 0; i < num_sources; ++i) {
+ const auto source = RandAddr();
+ const size_t num_addresses = insecure_rand.randrange(500) + 1; // [1..500]
+
+ for (size_t j = 0; j < num_addresses; ++j) {
+ const auto addr = CAddress{CService{RandAddr(), 8333}, NODE_NETWORK};
+ const auto time_penalty = insecure_rand.randrange(100000001);
+#if 1
+ // 2.83 sec to fill.
+ if (n > 0 && mapInfo.size() % n == 0 && mapAddr.find(addr) == mapAddr.end()) {
+ // Add to the "tried" table (if the bucket slot is free).
+ const CAddrInfo dummy{addr, source};
+ const int bucket = dummy.GetTriedBucket(nKey, m_asmap);
+ const int bucket_pos = dummy.GetBucketPosition(nKey, false, bucket);
+ if (vvTried[bucket][bucket_pos] == -1) {
+ int id;
+ CAddrInfo* addr_info = Create(addr, source, &id);
+ vvTried[bucket][bucket_pos] = id;
+ addr_info->fInTried = true;
+ ++nTried;
+ }
+ } else {
+ // Add to the "new" table.
+ Add_(addr, source, time_penalty);
+ }
+#else
+ // 261.91 sec to fill.
+ Add_(addr, source, time_penalty);
+ if (n > 0 && mapInfo.size() % n == 0) {
+ Good_(addr, false, GetTime());
+ }
+#endif
+ // Add 10% of the addresses from more than one source.
+ if (insecure_rand.randrange(10) == 0 && prev_source.IsValid()) {
+ Add_(addr, prev_source, time_penalty);
+ }
+ }
+ prev_source = source;
+ }
+ }
+
+ /**
+ * Compare with another AddrMan.
+ * This compares:
+ * - the values in `mapInfo` (the keys aka ids are ignored)
+ * - vvNew entries refer to the same addresses
+ * - vvTried entries refer to the same addresses
+ */
+ bool operator==(const CAddrManDeterministic& other)
{
- WITH_LOCK(cs, insecure_rand = FastRandomContext{random_seed});
- Clear();
+ LOCK2(cs, other.cs);
+
+ if (mapInfo.size() != other.mapInfo.size() || nNew != other.nNew ||
+ nTried != other.nTried) {
+ return false;
+ }
+
+ // Check that all values in `mapInfo` are equal to all values in `other.mapInfo`.
+ // Keys may be different.
+
+ using CAddrInfoHasher = std::function<size_t(const CAddrInfo&)>;
+ using CAddrInfoEq = std::function<bool(const CAddrInfo&, const CAddrInfo&)>;
+
+ CNetAddrHash netaddr_hasher;
+
+ CAddrInfoHasher addrinfo_hasher = [&netaddr_hasher](const CAddrInfo& a) {
+ return netaddr_hasher(static_cast<CNetAddr>(a)) ^ netaddr_hasher(a.source) ^
+ a.nLastSuccess ^ a.nAttempts ^ a.nRefCount ^ a.fInTried;
+ };
+
+ CAddrInfoEq addrinfo_eq = [](const CAddrInfo& lhs, const CAddrInfo& rhs) {
+ return static_cast<CNetAddr>(lhs) == static_cast<CNetAddr>(rhs) &&
+ lhs.source == rhs.source && lhs.nLastSuccess == rhs.nLastSuccess &&
+ lhs.nAttempts == rhs.nAttempts && lhs.nRefCount == rhs.nRefCount &&
+ lhs.fInTried == rhs.fInTried;
+ };
+
+ using Addresses = std::unordered_set<CAddrInfo, CAddrInfoHasher, CAddrInfoEq>;
+
+ const size_t num_addresses{mapInfo.size()};
+
+ Addresses addresses{num_addresses, addrinfo_hasher, addrinfo_eq};
+ for (const auto& [id, addr] : mapInfo) {
+ addresses.insert(addr);
+ }
+
+ Addresses other_addresses{num_addresses, addrinfo_hasher, addrinfo_eq};
+ for (const auto& [id, addr] : other.mapInfo) {
+ other_addresses.insert(addr);
+ }
+
+ if (addresses != other_addresses) {
+ return false;
+ }
+
+ auto IdsReferToSameAddress = [&](int id, int other_id) EXCLUSIVE_LOCKS_REQUIRED(cs, other.cs) {
+ if (id == -1 && other_id == -1) {
+ return true;
+ }
+ if ((id == -1 && other_id != -1) || (id != -1 && other_id == -1)) {
+ return false;
+ }
+ return mapInfo.at(id) == other.mapInfo.at(other_id);
+ };
+
+ // Check that `vvNew` contains the same addresses as `other.vvNew`. Notice - `vvNew[i][j]`
+ // contains just an id and the address is to be found in `mapInfo.at(id)`. The ids
+ // themselves may differ between `vvNew` and `other.vvNew`.
+ for (size_t i = 0; i < ADDRMAN_NEW_BUCKET_COUNT; ++i) {
+ for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) {
+ if (!IdsReferToSameAddress(vvNew[i][j], other.vvNew[i][j])) {
+ return false;
+ }
+ }
+ }
+
+ // Same for `vvTried`.
+ for (size_t i = 0; i < ADDRMAN_TRIED_BUCKET_COUNT; ++i) {
+ for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) {
+ if (!IdsReferToSameAddress(vvTried[i][j], other.vvTried[i][j])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
};
@@ -36,45 +228,29 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider));
- CAddrManDeterministic addr_man;
- addr_man.MakeDeterministic(ConsumeUInt256(fuzzed_data_provider));
- if (fuzzed_data_provider.ConsumeBool()) {
- addr_man.m_asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
- if (!SanityCheckASMap(addr_man.m_asmap)) {
- addr_man.m_asmap.clear();
- }
- }
+ auto addr_man_ptr = std::make_unique<CAddrManDeterministic>(fuzzed_data_provider);
if (fuzzed_data_provider.ConsumeBool()) {
const std::vector<uint8_t> serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
CDataStream ds(serialized_data, SER_DISK, INIT_PROTO_VERSION);
const auto ser_version{fuzzed_data_provider.ConsumeIntegral<int32_t>()};
ds.SetVersion(ser_version);
try {
- ds >> addr_man;
+ ds >> *addr_man_ptr;
} catch (const std::ios_base::failure&) {
- addr_man.Clear();
+ addr_man_ptr = std::make_unique<CAddrManDeterministic>(fuzzed_data_provider);
}
}
+ CAddrManDeterministic& addr_man = *addr_man_ptr;
while (fuzzed_data_provider.ConsumeBool()) {
CallOneOf(
fuzzed_data_provider,
[&] {
- addr_man.Clear();
- },
- [&] {
addr_man.ResolveCollisions();
},
[&] {
(void)addr_man.SelectTriedCollision();
},
[&] {
- const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider);
- const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider);
- if (opt_address && opt_net_addr) {
- addr_man.Add(*opt_address, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000));
- }
- },
- [&] {
std::vector<CAddress> addresses;
while (fuzzed_data_provider.ConsumeBool()) {
const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider);
@@ -123,3 +299,21 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman)
CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION);
data_stream << const_addr_man;
}
+
+// Check that serialize followed by unserialize produces the same addrman.
+FUZZ_TARGET_INIT(addrman_serdeser, initialize_addrman)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ SetMockTime(ConsumeTime(fuzzed_data_provider));
+
+ CAddrManDeterministic addr_man1{fuzzed_data_provider};
+ CAddrManDeterministic addr_man2{fuzzed_data_provider};
+ addr_man2.m_asmap = addr_man1.m_asmap;
+
+ CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION);
+
+ addr_man1.Fill();
+ data_stream << addr_man1;
+ data_stream >> addr_man2;
+ assert(addr_man1 == addr_man2);
+}
diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp
index de211f601f..561cc83c72 100644
--- a/src/test/fuzz/banman.cpp
+++ b/src/test/fuzz/banman.cpp
@@ -41,10 +41,6 @@ static bool operator==(const CBanEntry& lhs, const CBanEntry& rhs)
FUZZ_TARGET_INIT(banman, initialize_banman)
{
- // The complexity is O(N^2), where N is the input size, because each call
- // might call DumpBanlist (or other methods that are at least linear
- // complexity of the input size).
- int limit_max_ops{300};
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
fs::path banlist_file = gArgs.GetDataDirNet() / "fuzzed_banlist";
@@ -63,7 +59,11 @@ FUZZ_TARGET_INIT(banman, initialize_banman)
{
BanMan ban_man{banlist_file, /* client_interface */ nullptr, /* default_ban_time */ ConsumeBanTimeOffset(fuzzed_data_provider)};
- while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
+ // The complexity is O(N^2), where N is the input size, because each call
+ // might call DumpBanlist (or other methods that are at least linear
+ // complexity of the input size).
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
+ {
CallOneOf(
fuzzed_data_provider,
[&] {
@@ -108,9 +108,7 @@ FUZZ_TARGET_INIT(banman, initialize_banman)
BanMan ban_man_read{banlist_file, /* client_interface */ nullptr, /* default_ban_time */ 0};
banmap_t banmap_read;
ban_man_read.GetBanned(banmap_read);
- // Assert temporarily disabled to allow the remainder of the fuzz test to run while a
- // fix is being worked on. See https://github.com/bitcoin/bitcoin/pull/22517
- (void)(banmap == banmap_read);
+ assert(banmap == banmap_read);
}
}
fs::remove(banlist_file.string() + ".json");
diff --git a/src/test/fuzz/blockfilter.cpp b/src/test/fuzz/blockfilter.cpp
index 7fa06085f8..96f049625d 100644
--- a/src/test/fuzz/blockfilter.cpp
+++ b/src/test/fuzz/blockfilter.cpp
@@ -36,9 +36,10 @@ FUZZ_TARGET(blockfilter)
(void)gcs_filter.GetEncoded();
(void)gcs_filter.Match(ConsumeRandomLengthByteVector(fuzzed_data_provider));
GCSFilter::ElementSet element_set;
- while (fuzzed_data_provider.ConsumeBool()) {
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 30000)
+ {
element_set.insert(ConsumeRandomLengthByteVector(fuzzed_data_provider));
- gcs_filter.MatchAny(element_set);
}
+ gcs_filter.MatchAny(element_set);
}
}
diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp
index bbec5943af..0e323ddc20 100644
--- a/src/test/fuzz/connman.cpp
+++ b/src/test/fuzz/connman.cpp
@@ -25,7 +25,7 @@ FUZZ_TARGET_INIT(connman, initialize_connman)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
- CAddrMan addrman;
+ CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0);
CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), addrman, fuzzed_data_provider.ConsumeBool()};
CNetAddr random_netaddr;
CNode random_node = ConsumeNode(fuzzed_data_provider);
diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp
index f83747e424..84b95117e2 100644
--- a/src/test/fuzz/crypto.cpp
+++ b/src/test/fuzz/crypto.cpp
@@ -19,10 +19,6 @@
FUZZ_TARGET(crypto)
{
- // Hashing is expensive with sanitizers enabled, so limit the number of
- // calls
- int limit_max_ops{30};
-
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
std::vector<uint8_t> data = ConsumeRandomLengthByteVector(fuzzed_data_provider);
if (data.empty()) {
@@ -40,7 +36,8 @@ FUZZ_TARGET(crypto)
SHA3_256 sha3;
CSipHasher sip_hasher{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
- while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 30)
+ {
CallOneOf(
fuzzed_data_provider,
[&] {
diff --git a/src/test/fuzz/data_stream.cpp b/src/test/fuzz/data_stream.cpp
index 473caec6ff..53400082ab 100644
--- a/src/test/fuzz/data_stream.cpp
+++ b/src/test/fuzz/data_stream.cpp
@@ -21,6 +21,6 @@ FUZZ_TARGET_INIT(data_stream_addr_man, initialize_data_stream_addr_man)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CDataStream data_stream = ConsumeDataStream(fuzzed_data_provider);
- CAddrMan addr_man;
+ CAddrMan addr_man(/* deterministic */ false, /* consistency_check_ratio */ 0);
CAddrDB::Read(addr_man, data_stream);
}
diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp
index d5b56cb7cd..cfbbe77311 100644
--- a/src/test/fuzz/deserialize.cpp
+++ b/src/test/fuzz/deserialize.cpp
@@ -188,17 +188,13 @@ FUZZ_TARGET_DESERIALIZE(blockmerkleroot, {
BlockMerkleRoot(block, &mutated);
})
FUZZ_TARGET_DESERIALIZE(addrman_deserialize, {
- CAddrMan am;
+ CAddrMan am(/* deterministic */ false, /* consistency_check_ratio */ 0);
DeserializeFromFuzzingInput(buffer, am);
})
FUZZ_TARGET_DESERIALIZE(blockheader_deserialize, {
CBlockHeader bh;
DeserializeFromFuzzingInput(buffer, bh);
})
-FUZZ_TARGET_DESERIALIZE(banentry_deserialize, {
- CBanEntry be;
- DeserializeFromFuzzingInput(buffer, be);
-})
FUZZ_TARGET_DESERIALIZE(txundo_deserialize, {
CTxUndo tu;
DeserializeFromFuzzingInput(buffer, tu);
diff --git a/src/test/fuzz/fuzz.h b/src/test/fuzz/fuzz.h
index 2bad77bdc1..c91c33da67 100644
--- a/src/test/fuzz/fuzz.h
+++ b/src/test/fuzz/fuzz.h
@@ -11,6 +11,13 @@
#include <functional>
#include <string_view>
+/**
+ * Can be used to limit a theoretically unbounded loop. This caps the runtime
+ * to avoid timeouts or OOMs.
+ */
+#define LIMITED_WHILE(condition, limit) \
+ for (unsigned _count{limit}; (condition) && _count; --_count)
+
using FuzzBufferType = Span<const uint8_t>;
using TypeTestOneInput = std::function<void(FuzzBufferType)>;
diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp
index e28e2feb0a..5d26529837 100644
--- a/src/test/fuzz/integer.cpp
+++ b/src/test/fuzz/integer.cpp
@@ -83,9 +83,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
(void)FormatISO8601Date(i64);
(void)FormatISO8601DateTime(i64);
{
- int64_t parsed_money;
- if (ParseMoney(FormatMoney(i64), parsed_money)) {
- assert(parsed_money == i64);
+ if (std::optional<CAmount> parsed = ParseMoney(FormatMoney(i64))) {
+ assert(parsed.value() == i64);
}
}
(void)GetSizeOfCompactSize(u64);
@@ -126,9 +125,8 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
(void)ToLower(ch);
(void)ToUpper(ch);
{
- int64_t parsed_money;
- if (ParseMoney(ValueFromAmount(i64).getValStr(), parsed_money)) {
- assert(parsed_money == i64);
+ if (std::optional<CAmount> parsed = ParseMoney(ValueFromAmount(i64).getValStr())) {
+ assert(parsed.value() == i64);
}
}
if (i32 >= 0 && i32 <= 16) {
diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp
index 20d8581312..9a579c053f 100644
--- a/src/test/fuzz/net.cpp
+++ b/src/test/fuzz/net.cpp
@@ -38,9 +38,6 @@ FUZZ_TARGET_INIT(net, initialize_net)
node.CloseSocketDisconnect();
},
[&] {
- node.MaybeSetAddrName(fuzzed_data_provider.ConsumeRandomLengthString(32));
- },
- [&] {
const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
if (!SanityCheckASMap(asmap)) {
return;
@@ -82,7 +79,6 @@ FUZZ_TARGET_INIT(net, initialize_net)
}
(void)node.GetAddrLocal();
- (void)node.GetAddrName();
(void)node.GetId();
(void)node.GetLocalNonce();
(void)node.GetLocalServices();
diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp
index 2c546e9b4a..69e58c3f63 100644
--- a/src/test/fuzz/parse_numbers.cpp
+++ b/src/test/fuzz/parse_numbers.cpp
@@ -12,8 +12,7 @@ FUZZ_TARGET(parse_numbers)
{
const std::string random_string(buffer.begin(), buffer.end());
- CAmount amount;
- (void)ParseMoney(random_string, amount);
+ (void)ParseMoney(random_string);
double d;
(void)ParseDouble(random_string, &d);
diff --git a/src/test/fuzz/prevector.cpp b/src/test/fuzz/prevector.cpp
index 447f32ed16..d4b3ed501f 100644
--- a/src/test/fuzz/prevector.cpp
+++ b/src/test/fuzz/prevector.cpp
@@ -206,14 +206,11 @@ public:
FUZZ_TARGET(prevector)
{
- // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
- // inputs.
- int limit_max_ops{3000};
-
FuzzedDataProvider prov(buffer.data(), buffer.size());
prevector_tester<8, int> test;
- while (--limit_max_ops >= 0 && prov.remaining_bytes()) {
+ LIMITED_WHILE(prov.remaining_bytes(), 3000)
+ {
switch (prov.ConsumeIntegralInRange<int>(0, 13 + 3 * (test.size() > 0))) {
case 0:
test.insert(prov.ConsumeIntegralInRange<size_t>(0, test.size()), prov.ConsumeIntegral<int>());
diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp
index 3b33115e72..b9ed497e68 100644
--- a/src/test/fuzz/rolling_bloom_filter.cpp
+++ b/src/test/fuzz/rolling_bloom_filter.cpp
@@ -16,16 +16,13 @@
FUZZ_TARGET(rolling_bloom_filter)
{
- // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
- // inputs.
- int limit_max_ops{3000};
-
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
CRollingBloomFilter rolling_bloom_filter{
fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 1000),
0.999 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max())};
- while (--limit_max_ops >= 0 && fuzzed_data_provider.remaining_bytes() > 0) {
+ LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 3000)
+ {
CallOneOf(
fuzzed_data_provider,
[&] {
diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp
index fe850a6959..684324c36e 100644
--- a/src/test/fuzz/script_sign.cpp
+++ b/src/test/fuzz/script_sign.cpp
@@ -13,6 +13,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <util/translation.h>
#include <cassert>
#include <cstdint>
@@ -135,7 +136,7 @@ FUZZ_TARGET_INIT(script_sign, initialize_script_sign)
}
coins[*outpoint] = *coin;
}
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
(void)SignTransaction(sign_transaction_tx_to, &provider, coins, fuzzed_data_provider.ConsumeIntegral<int>(), input_errors);
}
}
diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp
index b25dcfcd3b..0f53939eac 100644
--- a/src/test/fuzz/system.cpp
+++ b/src/test/fuzz/system.cpp
@@ -31,7 +31,8 @@ FUZZ_TARGET(system)
SetupHelpOptions(args_manager);
}
- while (fuzzed_data_provider.ConsumeBool()) {
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 3000)
+ {
CallOneOf(
fuzzed_data_provider,
[&] {
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index dadf772bc1..6201cc813c 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -112,10 +112,6 @@ void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chain
FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
{
- // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
- // inputs.
- int limit_max_ops{300};
-
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto& node = g_setup->m_node;
auto& chainstate = node.chainman->ActiveChainstate();
@@ -146,7 +142,8 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
return c.out.nValue;
};
- while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
+ {
{
// Total supply is the mempool fee + all outpoints
CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())};
@@ -289,10 +286,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
{
- // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
- // inputs.
- int limit_max_ops{300};
-
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto& node = g_setup->m_node;
auto& chainstate = node.chainman->ActiveChainstate();
@@ -313,7 +306,8 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
- while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
+ {
const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
if (fuzzed_data_provider.ConsumeBool()) {
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index acbbf357d2..29938d4ede 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -2,8 +2,6 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <addrdb.h>
-#include <addrman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <cstdint>
@@ -29,65 +27,6 @@
using namespace std::literals;
-class CAddrManSerializationMock : public CAddrMan
-{
-public:
- virtual void Serialize(CDataStream& s) const = 0;
-
- //! Ensure that bucket placement is always the same for testing purposes.
- void MakeDeterministic()
- {
- LOCK(cs);
- nKey.SetNull();
- insecure_rand = FastRandomContext(true);
- }
-};
-
-class CAddrManUncorrupted : public CAddrManSerializationMock
-{
-public:
- void Serialize(CDataStream& s) const override
- {
- CAddrMan::Serialize(s);
- }
-};
-
-class CAddrManCorrupted : public CAddrManSerializationMock
-{
-public:
- void Serialize(CDataStream& s) const override
- {
- // Produces corrupt output that claims addrman has 20 addrs when it only has one addr.
- unsigned char nVersion = 1;
- s << nVersion;
- s << ((unsigned char)32);
- s << nKey;
- s << 10; // nNew
- s << 10; // nTried
-
- int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
- s << nUBuckets;
-
- CService serv;
- BOOST_CHECK(Lookup("252.1.1.1", serv, 7777, false));
- CAddress addr = CAddress(serv, NODE_NONE);
- CNetAddr resolved;
- BOOST_CHECK(LookupHost("252.2.2.2", resolved, false));
- CAddrInfo info = CAddrInfo(addr, resolved);
- s << info;
- }
-};
-
-static CDataStream AddrmanToStream(const CAddrManSerializationMock& _addrman)
-{
- CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
- ssPeersIn << Params().MessageStart();
- ssPeersIn << _addrman;
- std::string str = ssPeersIn.str();
- std::vector<unsigned char> vchData(str.begin(), str.end());
- return CDataStream(vchData, SER_DISK, CLIENT_VERSION);
-}
-
BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(cnode_listen_port)
@@ -102,82 +41,6 @@ BOOST_AUTO_TEST_CASE(cnode_listen_port)
BOOST_CHECK(port == altPort);
}
-BOOST_AUTO_TEST_CASE(caddrdb_read)
-{
- CAddrManUncorrupted addrmanUncorrupted;
- addrmanUncorrupted.MakeDeterministic();
-
- CService addr1, addr2, addr3;
- BOOST_CHECK(Lookup("250.7.1.1", addr1, 8333, false));
- BOOST_CHECK(Lookup("250.7.2.2", addr2, 9999, false));
- BOOST_CHECK(Lookup("250.7.3.3", addr3, 9999, false));
- BOOST_CHECK(Lookup("250.7.3.3"s, addr3, 9999, false));
- BOOST_CHECK(!Lookup("250.7.3.3\0example.com"s, addr3, 9999, false));
-
- // Add three addresses to new table.
- CService source;
- BOOST_CHECK(Lookup("252.5.1.1", source, 8333, false));
- BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source));
- BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source));
- BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source));
-
- // Test that the de-serialization does not throw an exception.
- CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted);
- bool exceptionThrown = false;
- CAddrMan addrman1;
-
- BOOST_CHECK(addrman1.size() == 0);
- try {
- unsigned char pchMsgTmp[4];
- ssPeers1 >> pchMsgTmp;
- ssPeers1 >> addrman1;
- } catch (const std::exception&) {
- exceptionThrown = true;
- }
-
- BOOST_CHECK(addrman1.size() == 3);
- BOOST_CHECK(exceptionThrown == false);
-
- // Test that CAddrDB::Read creates an addrman with the correct number of addrs.
- CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted);
-
- CAddrMan addrman2;
- BOOST_CHECK(addrman2.size() == 0);
- BOOST_CHECK(CAddrDB::Read(addrman2, ssPeers2));
- BOOST_CHECK(addrman2.size() == 3);
-}
-
-
-BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted)
-{
- CAddrManCorrupted addrmanCorrupted;
- addrmanCorrupted.MakeDeterministic();
-
- // Test that the de-serialization of corrupted addrman throws an exception.
- CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted);
- bool exceptionThrown = false;
- CAddrMan addrman1;
- BOOST_CHECK(addrman1.size() == 0);
- try {
- unsigned char pchMsgTmp[4];
- ssPeers1 >> pchMsgTmp;
- ssPeers1 >> addrman1;
- } catch (const std::exception&) {
- exceptionThrown = true;
- }
- // Even through de-serialization failed addrman is not left in a clean state.
- BOOST_CHECK(addrman1.size() == 1);
- BOOST_CHECK(exceptionThrown);
-
- // Test that CAddrDB::Read leaves addrman in a clean state if de-serialization fails.
- CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted);
-
- CAddrMan addrman2;
- BOOST_CHECK(addrman2.size() == 0);
- BOOST_CHECK(!CAddrDB::Read(addrman2, ssPeers2));
- BOOST_CHECK(addrman2.size() == 0);
-}
-
BOOST_AUTO_TEST_CASE(cnode_simple_test)
{
SOCKET hSocket = INVALID_SOCKET;
@@ -764,37 +627,42 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
{
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
- BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
+ BOOST_CHECK(IsReachable(NET_IPV4));
+ BOOST_CHECK(IsReachable(NET_IPV6));
+ BOOST_CHECK(IsReachable(NET_ONION));
+ BOOST_CHECK(IsReachable(NET_I2P));
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false);
+ SetReachable(NET_I2P, false);
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false);
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false);
- BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false);
+ BOOST_CHECK(!IsReachable(NET_IPV4));
+ BOOST_CHECK(!IsReachable(NET_IPV6));
+ BOOST_CHECK(!IsReachable(NET_ONION));
+ BOOST_CHECK(!IsReachable(NET_I2P));
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true);
+ SetReachable(NET_I2P, true);
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
- BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
- BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
+ BOOST_CHECK(IsReachable(NET_IPV4));
+ BOOST_CHECK(IsReachable(NET_IPV6));
+ BOOST_CHECK(IsReachable(NET_ONION));
+ BOOST_CHECK(IsReachable(NET_I2P));
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal)
{
- BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true);
- BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
+ BOOST_CHECK(IsReachable(NET_UNROUTABLE));
+ BOOST_CHECK(IsReachable(NET_INTERNAL));
SetReachable(NET_UNROUTABLE, false);
SetReachable(NET_INTERNAL, false);
- BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); // Ignored for both networks
- BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
+ BOOST_CHECK(IsReachable(NET_UNROUTABLE)); // Ignored for both networks
+ BOOST_CHECK(IsReachable(NET_INTERNAL));
}
CNetAddr UtilBuildAddress(unsigned char p1, unsigned char p2, unsigned char p3, unsigned char p4)
@@ -813,10 +681,10 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr)
CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001); // 1.1.1.1
SetReachable(NET_IPV4, true);
- BOOST_CHECK_EQUAL(IsReachable(addr), true);
+ BOOST_CHECK(IsReachable(addr));
SetReachable(NET_IPV4, false);
- BOOST_CHECK_EQUAL(IsReachable(addr), false);
+ BOOST_CHECK(!IsReachable(addr));
SetReachable(NET_IPV4, true); // have to reset this, because this is stateful.
}
@@ -828,12 +696,12 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle)
SetReachable(NET_IPV4, true);
- BOOST_CHECK_EQUAL(IsLocal(addr), false);
- BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true);
- BOOST_CHECK_EQUAL(IsLocal(addr), true);
+ BOOST_CHECK(!IsLocal(addr));
+ BOOST_CHECK(AddLocal(addr, 1000));
+ BOOST_CHECK(IsLocal(addr));
RemoveLocal(addr);
- BOOST_CHECK_EQUAL(IsLocal(addr), false);
+ BOOST_CHECK(!IsLocal(addr));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp
index 56e2aa63b9..2c39cbffb9 100644
--- a/src/test/script_tests.cpp
+++ b/src/test/script_tests.cpp
@@ -1160,7 +1160,7 @@ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction&
SignatureData data;
data.MergeSignatureData(scriptSig1);
data.MergeSignatureData(scriptSig2);
- ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue), txout.scriptPubKey, data);
+ ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue, SIGHASH_DEFAULT), txout.scriptPubKey, data);
return data;
}
diff --git a/src/test/serfloat_tests.cpp b/src/test/serfloat_tests.cpp
index 7876c0bcda..15612e2950 100644
--- a/src/test/serfloat_tests.cpp
+++ b/src/test/serfloat_tests.cpp
@@ -102,11 +102,12 @@ BOOST_AUTO_TEST_CASE(double_serfloat_tests) {
Python code to generate the below hashes:
def reversed_hex(x):
- return binascii.hexlify(''.join(reversed(x)))
+ return bytes(reversed(x)).hex()
+
def dsha256(x):
return hashlib.sha256(hashlib.sha256(x).digest()).digest()
- reversed_hex(dsha256(''.join(struct.pack('<d', x) for x in range(0,1000)))) == '43d0c82591953c4eafe114590d392676a01585d25b25d433557f0d7878b23f96'
+ reversed_hex(dsha256(b''.join(struct.pack('<d', x) for x in range(0,1000)))) == '43d0c82591953c4eafe114590d392676a01585d25b25d433557f0d7878b23f96'
*/
BOOST_AUTO_TEST_CASE(doubles)
{
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index 571f792a53..97fd0600fa 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -561,7 +561,7 @@ SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutabl
SignatureData sigdata;
sigdata = DataFromTransaction(input1, 0, tx->vout[0]);
sigdata.MergeSignatureData(DataFromTransaction(input2, 0, tx->vout[0]));
- ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&input1, 0, tx->vout[0].nValue), tx->vout[0].scriptPubKey, sigdata);
+ ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&input1, 0, tx->vout[0].nValue, SIGHASH_ALL), tx->vout[0].scriptPubKey, sigdata);
return sigdata;
}
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 2d044af184..c9bb863a7b 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -193,7 +193,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
- m_node.addrman = std::make_unique<CAddrMan>();
+ m_node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ 0);
m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
m_node.connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); // Deterministic randomness for tests.
m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman,
@@ -292,7 +292,7 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio
input_coins.insert({outpoint_to_spend, utxo_to_spend});
// - Default signature hashing type
int nHashType = SIGHASH_ALL;
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
// If submit=true, add transaction to the mempool.
diff --git a/src/test/util/wallet.cpp b/src/test/util/wallet.cpp
index fd6012e9fe..061659818f 100644
--- a/src/test/util/wallet.cpp
+++ b/src/test/util/wallet.cpp
@@ -8,6 +8,7 @@
#include <outputtype.h>
#include <script/standard.h>
#ifdef ENABLE_WALLET
+#include <util/translation.h>
#include <wallet/wallet.h>
#endif
@@ -18,7 +19,7 @@ std::string getnewaddress(CWallet& w)
{
constexpr auto output_type = OutputType::BECH32;
CTxDestination dest;
- std::string error;
+ bilingual_str error;
if (!w.GetNewDestination(output_type, "", dest, error)) assert(false);
return EncodeDestination(dest);
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 7ce38519cf..4f9d10cf1d 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -1222,86 +1222,59 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney)
BOOST_AUTO_TEST_CASE(util_ParseMoney)
{
- CAmount ret = 0;
- BOOST_CHECK(ParseMoney("0.0", ret));
- BOOST_CHECK_EQUAL(ret, 0);
-
- BOOST_CHECK(ParseMoney("12345.6789", ret));
- BOOST_CHECK_EQUAL(ret, (COIN/10000)*123456789);
-
- BOOST_CHECK(ParseMoney("100000000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*100000000);
- BOOST_CHECK(ParseMoney("10000000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*10000000);
- BOOST_CHECK(ParseMoney("1000000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*1000000);
- BOOST_CHECK(ParseMoney("100000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*100000);
- BOOST_CHECK(ParseMoney("10000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*10000);
- BOOST_CHECK(ParseMoney("1000.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*1000);
- BOOST_CHECK(ParseMoney("100.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*100);
- BOOST_CHECK(ParseMoney("10.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN*10);
- BOOST_CHECK(ParseMoney("1.00", ret));
- BOOST_CHECK_EQUAL(ret, COIN);
- BOOST_CHECK(ParseMoney("1", ret));
- BOOST_CHECK_EQUAL(ret, COIN);
- BOOST_CHECK(ParseMoney(" 1", ret));
- BOOST_CHECK_EQUAL(ret, COIN);
- BOOST_CHECK(ParseMoney("1 ", ret));
- BOOST_CHECK_EQUAL(ret, COIN);
- BOOST_CHECK(ParseMoney(" 1 ", ret));
- BOOST_CHECK_EQUAL(ret, COIN);
- BOOST_CHECK(ParseMoney("0.1", ret));
- BOOST_CHECK_EQUAL(ret, COIN/10);
- BOOST_CHECK(ParseMoney("0.01", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100);
- BOOST_CHECK(ParseMoney("0.001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/1000);
- BOOST_CHECK(ParseMoney("0.0001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/10000);
- BOOST_CHECK(ParseMoney("0.00001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100000);
- BOOST_CHECK(ParseMoney("0.000001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/1000000);
- BOOST_CHECK(ParseMoney("0.0000001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/10000000);
- BOOST_CHECK(ParseMoney("0.00000001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100000000);
- BOOST_CHECK(ParseMoney(" 0.00000001 ", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100000000);
- BOOST_CHECK(ParseMoney("0.00000001 ", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100000000);
- BOOST_CHECK(ParseMoney(" 0.00000001", ret));
- BOOST_CHECK_EQUAL(ret, COIN/100000000);
-
- // Parsing amount that can not be represented in ret should fail
- BOOST_CHECK(!ParseMoney("0.000000001", ret));
+ BOOST_CHECK_EQUAL(ParseMoney("0.0").value(), 0);
+
+ BOOST_CHECK_EQUAL(ParseMoney("12345.6789").value(), (COIN/10000)*123456789);
+
+ BOOST_CHECK_EQUAL(ParseMoney("10000000.00").value(), COIN*10000000);
+ BOOST_CHECK_EQUAL(ParseMoney("1000000.00").value(), COIN*1000000);
+ BOOST_CHECK_EQUAL(ParseMoney("100000.00").value(), COIN*100000);
+ BOOST_CHECK_EQUAL(ParseMoney("10000.00").value(), COIN*10000);
+ BOOST_CHECK_EQUAL(ParseMoney("1000.00").value(), COIN*1000);
+ BOOST_CHECK_EQUAL(ParseMoney("100.00").value(), COIN*100);
+ BOOST_CHECK_EQUAL(ParseMoney("10.00").value(), COIN*10);
+ BOOST_CHECK_EQUAL(ParseMoney("1.00").value(), COIN);
+ BOOST_CHECK_EQUAL(ParseMoney("1").value(), COIN);
+ BOOST_CHECK_EQUAL(ParseMoney(" 1").value(), COIN);
+ BOOST_CHECK_EQUAL(ParseMoney("1 ").value(), COIN);
+ BOOST_CHECK_EQUAL(ParseMoney(" 1 ").value(), COIN);
+ BOOST_CHECK_EQUAL(ParseMoney("0.1").value(), COIN/10);
+ BOOST_CHECK_EQUAL(ParseMoney("0.01").value(), COIN/100);
+ BOOST_CHECK_EQUAL(ParseMoney("0.001").value(), COIN/1000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.0001").value(), COIN/10000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.00001").value(), COIN/100000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.000001").value(), COIN/1000000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.0000001").value(), COIN/10000000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.00000001").value(), COIN/100000000);
+ BOOST_CHECK_EQUAL(ParseMoney(" 0.00000001 ").value(), COIN/100000000);
+ BOOST_CHECK_EQUAL(ParseMoney("0.00000001 ").value(), COIN/100000000);
+ BOOST_CHECK_EQUAL(ParseMoney(" 0.00000001").value(), COIN/100000000);
+
+ // Parsing amount that can not be represented should fail
+ BOOST_CHECK(!ParseMoney("100000000.00"));
+ BOOST_CHECK(!ParseMoney("0.000000001"));
// Parsing empty string should fail
- BOOST_CHECK(!ParseMoney("", ret));
- BOOST_CHECK(!ParseMoney(" ", ret));
- BOOST_CHECK(!ParseMoney(" ", ret));
+ BOOST_CHECK(!ParseMoney(""));
+ BOOST_CHECK(!ParseMoney(" "));
+ BOOST_CHECK(!ParseMoney(" "));
// Parsing two numbers should fail
- BOOST_CHECK(!ParseMoney("1 2", ret));
- BOOST_CHECK(!ParseMoney(" 1 2 ", ret));
- BOOST_CHECK(!ParseMoney(" 1.2 3 ", ret));
- BOOST_CHECK(!ParseMoney(" 1 2.3 ", ret));
+ BOOST_CHECK(!ParseMoney("1 2"));
+ BOOST_CHECK(!ParseMoney(" 1 2 "));
+ BOOST_CHECK(!ParseMoney(" 1.2 3 "));
+ BOOST_CHECK(!ParseMoney(" 1 2.3 "));
// Attempted 63 bit overflow should fail
- BOOST_CHECK(!ParseMoney("92233720368.54775808", ret));
+ BOOST_CHECK(!ParseMoney("92233720368.54775808"));
// Parsing negative amounts must fail
- BOOST_CHECK(!ParseMoney("-1", ret));
+ BOOST_CHECK(!ParseMoney("-1"));
// Parsing strings with embedded NUL characters should fail
- BOOST_CHECK(!ParseMoney("\0-1"s, ret));
- BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR, ret));
- BOOST_CHECK(!ParseMoney("1\0"s, ret));
+ BOOST_CHECK(!ParseMoney("\0-1"s));
+ BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR));
+ BOOST_CHECK(!ParseMoney("1\0"s));
}
BOOST_AUTO_TEST_CASE(util_IsHex)
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index c5a4bbf1b0..d5a888ac67 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -151,33 +151,17 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
}
}
-bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents /* = true */) const
+bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
+ size_t entry_count,
+ setEntries& setAncestors,
+ CTxMemPoolEntry::Parents& staged_ancestors,
+ uint64_t limitAncestorCount,
+ uint64_t limitAncestorSize,
+ uint64_t limitDescendantCount,
+ uint64_t limitDescendantSize,
+ std::string &errString) const
{
- CTxMemPoolEntry::Parents staged_ancestors;
- const CTransaction &tx = entry.GetTx();
-
- if (fSearchForParents) {
- // Get parents of this transaction that are in the mempool
- // GetMemPoolParents() is only valid for entries in the mempool, so we
- // iterate mapTx to find parents.
- for (unsigned int i = 0; i < tx.vin.size(); i++) {
- std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash);
- if (piter) {
- staged_ancestors.insert(**piter);
- if (staged_ancestors.size() + 1 > limitAncestorCount) {
- errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
- return false;
- }
- }
- }
- } else {
- // If we're not searching for parents, we require this to be an
- // entry in the mempool already.
- txiter it = mapTx.iterator_to(entry);
- staged_ancestors = it->GetMemPoolParentsConst();
- }
-
- size_t totalSizeWithAncestors = entry.GetTxSize();
+ size_t totalSizeWithAncestors = entry_size;
while (!staged_ancestors.empty()) {
const CTxMemPoolEntry& stage = staged_ancestors.begin()->get();
@@ -187,10 +171,10 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr
staged_ancestors.erase(stage);
totalSizeWithAncestors += stageit->GetTxSize();
- if (stageit->GetSizeWithDescendants() + entry.GetTxSize() > limitDescendantSize) {
+ if (stageit->GetSizeWithDescendants() + entry_size > limitDescendantSize) {
errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantSize);
return false;
- } else if (stageit->GetCountWithDescendants() + 1 > limitDescendantCount) {
+ } else if (stageit->GetCountWithDescendants() + entry_count > limitDescendantCount) {
errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantCount);
return false;
} else if (totalSizeWithAncestors > limitAncestorSize) {
@@ -206,7 +190,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr
if (setAncestors.count(parent_it) == 0) {
staged_ancestors.insert(parent);
}
- if (staged_ancestors.size() + setAncestors.size() + 1 > limitAncestorCount) {
+ if (staged_ancestors.size() + setAncestors.size() + entry_count > limitAncestorCount) {
errString = strprintf("too many unconfirmed ancestors [limit: %u]", limitAncestorCount);
return false;
}
@@ -216,6 +200,80 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr
return true;
}
+bool CTxMemPool::CheckPackageLimits(const Package& package,
+ uint64_t limitAncestorCount,
+ uint64_t limitAncestorSize,
+ uint64_t limitDescendantCount,
+ uint64_t limitDescendantSize,
+ std::string &errString) const
+{
+ CTxMemPoolEntry::Parents staged_ancestors;
+ size_t total_size = 0;
+ for (const auto& tx : package) {
+ total_size += GetVirtualTransactionSize(*tx);
+ for (const auto& input : tx->vin) {
+ std::optional<txiter> piter = GetIter(input.prevout.hash);
+ if (piter) {
+ staged_ancestors.insert(**piter);
+ if (staged_ancestors.size() + package.size() > limitAncestorCount) {
+ errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
+ return false;
+ }
+ }
+ }
+ }
+ // When multiple transactions are passed in, the ancestors and descendants of all transactions
+ // considered together must be within limits even if they are not interdependent. This may be
+ // stricter than the limits for each individual transaction.
+ setEntries setAncestors;
+ const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(),
+ setAncestors, staged_ancestors,
+ limitAncestorCount, limitAncestorSize,
+ limitDescendantCount, limitDescendantSize, errString);
+ // It's possible to overestimate the ancestor/descendant totals.
+ if (!ret) errString.insert(0, "possibly ");
+ return ret;
+}
+
+bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
+ setEntries &setAncestors,
+ uint64_t limitAncestorCount,
+ uint64_t limitAncestorSize,
+ uint64_t limitDescendantCount,
+ uint64_t limitDescendantSize,
+ std::string &errString,
+ bool fSearchForParents /* = true */) const
+{
+ CTxMemPoolEntry::Parents staged_ancestors;
+ const CTransaction &tx = entry.GetTx();
+
+ if (fSearchForParents) {
+ // Get parents of this transaction that are in the mempool
+ // GetMemPoolParents() is only valid for entries in the mempool, so we
+ // iterate mapTx to find parents.
+ for (unsigned int i = 0; i < tx.vin.size(); i++) {
+ std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash);
+ if (piter) {
+ staged_ancestors.insert(**piter);
+ if (staged_ancestors.size() + 1 > limitAncestorCount) {
+ errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
+ return false;
+ }
+ }
+ }
+ } else {
+ // If we're not searching for parents, we require this to already be an
+ // entry in the mempool and use the entry's cached parents.
+ txiter it = mapTx.iterator_to(entry);
+ staged_ancestors = it->GetMemPoolParentsConst();
+ }
+
+ return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /* entry_count */ 1,
+ setAncestors, staged_ancestors,
+ limitAncestorCount, limitAncestorSize,
+ limitDescendantCount, limitDescendantSize, errString);
+}
+
void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors)
{
CTxMemPoolEntry::Parents parents = it->GetMemPoolParents();
diff --git a/src/txmempool.h b/src/txmempool.h
index ae4b16d377..0a84a6e6b1 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -18,6 +18,7 @@
#include <coins.h>
#include <indirectmap.h>
#include <policy/feerate.h>
+#include <policy/packages.h>
#include <primitives/transaction.h>
#include <random.h>
#include <sync.h>
@@ -585,6 +586,25 @@ private:
*/
std::set<uint256> m_unbroadcast_txids GUARDED_BY(cs);
+
+ /**
+ * Helper function to calculate all in-mempool ancestors of staged_ancestors and apply ancestor
+ * and descendant limits (including staged_ancestors thsemselves, entry_size and entry_count).
+ * param@[in] entry_size Virtual size to include in the limits.
+ * param@[in] entry_count How many entries to include in the limits.
+ * param@[in] staged_ancestors Should contain entries in the mempool.
+ * param@[out] setAncestors Will be populated with all mempool ancestors.
+ */
+ bool CalculateAncestorsAndCheckLimits(size_t entry_size,
+ size_t entry_count,
+ setEntries& setAncestors,
+ CTxMemPoolEntry::Parents &staged_ancestors,
+ uint64_t limitAncestorCount,
+ uint64_t limitAncestorSize,
+ uint64_t limitDescendantCount,
+ uint64_t limitDescendantSize,
+ std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+
public:
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs);
std::map<uint256, CAmount> mapDeltas GUARDED_BY(cs);
@@ -681,6 +701,28 @@ public:
*/
bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, setEntries& setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string& errString, bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ /** Calculate all in-mempool ancestors of a set of transactions not already in the mempool and
+ * check ancestor and descendant limits. Heuristics are used to estimate the ancestor and
+ * descendant count of all entries if the package were to be added to the mempool. The limits
+ * are applied to the union of all package transactions. For example, if the package has 3
+ * transactions and limitAncestorCount = 25, the union of all 3 sets of ancestors (including the
+ * transactions themselves) must be <= 22.
+ * @param[in] package Transaction package being evaluated for acceptance
+ * to mempool. The transactions need not be direct
+ * ancestors/descendants of each other.
+ * @param[in] limitAncestorCount Max number of txns including ancestors.
+ * @param[in] limitAncestorSize Max virtual size including ancestors.
+ * @param[in] limitDescendantCount Max number of txns including descendants.
+ * @param[in] limitDescendantSize Max virtual size including descendants.
+ * @param[out] errString Populated with error reason if a limit is hit.
+ */
+ bool CheckPackageLimits(const Package& package,
+ uint64_t limitAncestorCount,
+ uint64_t limitAncestorSize,
+ uint64_t limitDescendantCount,
+ uint64_t limitDescendantSize,
+ std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+
/** Populate setDescendants with all in-mempool descendants of hash.
* Assumes that setDescendants includes all in-mempool descendants of anything
* already in it. */
diff --git a/src/util/hasher.h b/src/util/hasher.h
index fa2fea30d8..9b79a1b5f1 100644
--- a/src/util/hasher.h
+++ b/src/util/hasher.h
@@ -33,10 +33,6 @@ public:
SaltedOutpointHasher();
/**
- * This *must* return size_t. With Boost 1.46 on 32-bit systems the
- * unordered_map will behave unpredictably if the custom hasher returns a
- * uint64_t, resulting in failures when syncing the chain (#4634).
- *
* Having the hash noexcept allows libstdc++'s unordered_map to recalculate
* the hash during rehash, so it does not have to cache the value. This
* reduces node's memory by sizeof(size_t). The required recalculation has
diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp
index 3f9ce7dce4..d3f4029607 100644
--- a/src/util/moneystr.cpp
+++ b/src/util/moneystr.cpp
@@ -5,10 +5,13 @@
#include <util/moneystr.h>
+#include <amount.h>
#include <tinyformat.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <optional>
+
std::string FormatMoney(const CAmount n)
{
// Note: not using straight sprintf here because we do NOT want
@@ -35,14 +38,14 @@ std::string FormatMoney(const CAmount n)
}
-bool ParseMoney(const std::string& money_string, CAmount& nRet)
+std::optional<CAmount> ParseMoney(const std::string& money_string)
{
if (!ValidAsCString(money_string)) {
- return false;
+ return std::nullopt;
}
const std::string str = TrimString(money_string);
if (str.empty()) {
- return false;
+ return std::nullopt;
}
std::string strWhole;
@@ -62,21 +65,25 @@ bool ParseMoney(const std::string& money_string, CAmount& nRet)
break;
}
if (IsSpace(*p))
- return false;
+ return std::nullopt;
if (!IsDigit(*p))
- return false;
+ return std::nullopt;
strWhole.insert(strWhole.end(), *p);
}
if (*p) {
- return false;
+ return std::nullopt;
}
if (strWhole.size() > 10) // guard against 63 bit overflow
- return false;
+ return std::nullopt;
if (nUnits < 0 || nUnits > COIN)
- return false;
+ return std::nullopt;
int64_t nWhole = atoi64(strWhole);
- CAmount nValue = nWhole*COIN + nUnits;
- nRet = nValue;
- return true;
+ CAmount value = nWhole * COIN + nUnits;
+
+ if (!MoneyRange(value)) {
+ return std::nullopt;
+ }
+
+ return value;
}
diff --git a/src/util/moneystr.h b/src/util/moneystr.h
index 2aedbee358..b71dffd0db 100644
--- a/src/util/moneystr.h
+++ b/src/util/moneystr.h
@@ -12,6 +12,7 @@
#include <amount.h>
#include <attributes.h>
+#include <optional>
#include <string>
/* Do not use these functions to represent or parse monetary amounts to or from
@@ -19,6 +20,6 @@
*/
std::string FormatMoney(const CAmount n);
/** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/
-[[nodiscard]] bool ParseMoney(const std::string& str, CAmount& nRet);
+std::optional<CAmount> ParseMoney(const std::string& str);
#endif // BITCOIN_UTIL_MONEYSTR_H
diff --git a/src/util/rbf.h b/src/util/rbf.h
index 6a20b37de5..4eb44b904f 100644
--- a/src/util/rbf.h
+++ b/src/util/rbf.h
@@ -11,8 +11,15 @@ class CTransaction;
static const uint32_t MAX_BIP125_RBF_SEQUENCE = 0xfffffffd;
-// Check whether the sequence numbers on this transaction are signaling
-// opt-in to replace-by-fee, according to BIP 125
+/** Check whether the sequence numbers on this transaction are signaling
+* opt-in to replace-by-fee, according to BIP 125.
+* Allow opt-out of transaction replacement by setting
+* nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs.
+*
+* SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by
+* non-replaceable transactions. All inputs rather than just one
+* is for the sake of multi-party protocols, where we don't
+* want a single party to be able to disable replacement. */
bool SignalsOptInRBF(const CTransaction &tx);
#endif // BITCOIN_UTIL_RBF_H
diff --git a/src/util/string.h b/src/util/string.h
index b26facc502..5617e4acc1 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -65,6 +65,14 @@ inline std::string Join(const std::vector<std::string>& list, const std::string&
}
/**
+ * Create an unordered multi-line list of items.
+ */
+inline std::string MakeUnorderedList(const std::vector<std::string>& items)
+{
+ return Join(items, "\n", [](const std::string& item) { return "- " + item; });
+}
+
+/**
* Check if a string does not contain any embedded NUL (\0) characters
*/
[[nodiscard]] inline bool ValidAsCString(const std::string& str) noexcept
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 258ba2f235..4e16a83c87 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -502,11 +502,11 @@ bool ArgsManager::InitSettings(std::string& error)
std::vector<std::string> errors;
if (!ReadSettingsFile(&errors)) {
- error = strprintf("Failed loading settings file:\n- %s\n", Join(errors, "\n- "));
+ error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors));
return false;
}
if (!WriteSettingsFile(&errors)) {
- error = strprintf("Failed saving settings file:\n- %s\n", Join(errors, "\n- "));
+ error = strprintf("Failed saving settings file:\n%s\n", MakeUnorderedList(errors));
return false;
}
return true;
@@ -904,6 +904,11 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
fsbridge::ifstream stream(GetConfigFile(confPath));
+ // not ok to have a config file specified that cannot be opened
+ if (IsArgSet("-conf") && !stream.good()) {
+ error = strprintf("specified config file \"%s\" could not be opened.", confPath);
+ return false;
+ }
// ok to not have a config file
if (stream.good()) {
if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) {
diff --git a/src/util/system.h b/src/util/system.h
index 3547bad585..3c1399629c 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -205,6 +205,7 @@ protected:
*/
bool UseDefaultSection(const std::string& arg) const EXCLUSIVE_LOCKS_REQUIRED(cs_args);
+ public:
/**
* Get setting value.
*
@@ -219,7 +220,6 @@ protected:
*/
std::vector<util::SettingsValue> GetSettingsList(const std::string& arg) const;
-public:
ArgsManager();
~ArgsManager();
diff --git a/src/util/translation.h b/src/util/translation.h
index 99899ef3c2..62388b568c 100644
--- a/src/util/translation.h
+++ b/src/util/translation.h
@@ -28,6 +28,12 @@ struct bilingual_str {
{
return original.empty();
}
+
+ void clear()
+ {
+ original.clear();
+ translated.clear();
+ }
};
inline bilingual_str operator+(bilingual_str lhs, const bilingual_str& rhs)
diff --git a/src/validation.cpp b/src/validation.cpp
index 1b3d00bc6d..753b824167 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -25,6 +25,7 @@
#include <node/coinstats.h>
#include <node/ui_interface.h>
#include <policy/policy.h>
+#include <policy/rbf.h>
#include <policy/settings.h>
#include <pow.h>
#include <primitives/block.h>
@@ -474,8 +475,10 @@ private:
bool m_replacement_transaction;
CAmount m_base_fees;
CAmount m_modified_fees;
- CAmount m_conflicting_fees;
- size_t m_conflicting_size;
+ /** Total modified fees of all transactions being replaced. */
+ CAmount m_conflicting_fees{0};
+ /** Total virtual size of all transactions being replaced. */
+ size_t m_conflicting_size{0};
const CTransactionRef& m_ptx;
const uint256& m_hash;
@@ -602,14 +605,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
if (!setConflicts.count(ptxConflicting->GetHash()))
{
- // Allow opt-out of transaction replacement by setting
- // nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs.
- //
- // SEQUENCE_FINAL-1 is picked to still allow use of nLockTime by
- // non-replaceable transactions. All inputs rather than just one
- // is for the sake of multi-party protocols, where we don't
- // want a single party to be able to disable replacement.
- //
// Transactions that don't explicitly signal replaceability are
// *not* replaceable with the current logic, even if one of their
// unconfirmed ancestors signals replaceability. This diverges
@@ -617,16 +612,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Applications relying on first-seen mempool behavior should
// check all unconfirmed ancestors; otherwise an opt-in ancestor
// might be replaced, causing removal of this descendant.
- bool fReplacementOptOut = true;
- for (const CTxIn &_txin : ptxConflicting->vin)
- {
- if (_txin.nSequence <= MAX_BIP125_RBF_SEQUENCE)
- {
- fReplacementOptOut = false;
- break;
- }
- }
- if (fReplacementOptOut) {
+ if (!SignalsOptInRBF(*ptxConflicting)) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
}
@@ -796,11 +782,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
}
- // Check if it's economically rational to mine this transaction rather
- // than the ones it replaces.
- nConflictingFees = 0;
- nConflictingSize = 0;
- uint64_t nConflictingCount = 0;
// If we don't hold the lock allConflicting might be incomplete; the
// subsequent RemoveStaged() and addUnchecked() calls don't guarantee
@@ -808,9 +789,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
fReplacementTransaction = setConflicts.size();
if (fReplacementTransaction)
{
+ std::string err_string;
CFeeRate newFeeRate(nModifiedFees, nSize);
- std::set<uint256> setConflictsParents;
- const int maxDescendantsToVisit = 100;
for (const auto& mi : setIterConflicting) {
// Don't allow the replacement to reduce the feerate of the
// mempool.
@@ -835,33 +815,26 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
newFeeRate.ToString(),
oldFeeRate.ToString()));
}
+ }
+ // Calculate all conflicting entries and enforce Rule #5.
+ if (!GetEntriesForConflicts(tx, m_pool, setIterConflicting, allConflicting, err_string)) {
+ return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements", err_string);
+ }
+
+ // Check if it's economically rational to mine this transaction rather
+ // than the ones it replaces.
+ for (CTxMemPool::txiter it : allConflicting) {
+ nConflictingFees += it->GetModifiedFee();
+ nConflictingSize += it->GetTxSize();
+ }
+
+ std::set<uint256> setConflictsParents;
+ for (const auto& mi : setIterConflicting) {
for (const CTxIn &txin : mi->GetTx().vin)
{
setConflictsParents.insert(txin.prevout.hash);
}
-
- nConflictingCount += mi->GetCountWithDescendants();
- }
- // This potentially overestimates the number of actual descendants
- // but we just want to be conservative to avoid doing too much
- // work.
- if (nConflictingCount <= maxDescendantsToVisit) {
- // If not too many to replace, then calculate the set of
- // transactions that would have to be evicted
- for (CTxMemPool::txiter it : setIterConflicting) {
- m_pool.CalculateDescendants(it, allConflicting);
- }
- for (CTxMemPool::txiter it : allConflicting) {
- nConflictingFees += it->GetModifiedFee();
- nConflictingSize += it->GetTxSize();
- }
- } else {
- return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements",
- strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
- hash.ToString(),
- nConflictingCount,
- maxDescendantsToVisit));
}
for (unsigned int j = 0; j < tx.vin.size(); j++)
@@ -1079,6 +1052,19 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
m_viewmempool.PackageAddTransaction(ws.m_ptx);
}
+ // Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
+ // because it's unnecessary. Also, CPFP carve out can increase the limit for individual
+ // transactions, but this exemption is not extended to packages in CheckPackageLimits().
+ std::string err_string;
+ if (txns.size() > 1 &&
+ !m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants,
+ m_limit_descendant_size, err_string)) {
+ // All transactions must have individually passed mempool ancestor and descendant limits
+ // inside of PreChecks(), so this is separate from an individual transaction error.
+ package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string);
+ return PackageMempoolAcceptResult(package_state, std::move(results));
+ }
+
for (Workspace& ws : workspaces) {
PrecomputedTransactionData txdata;
if (!PolicyScriptChecks(args, ws, txdata)) {
@@ -2957,10 +2943,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
- {
- LOCK(cs_nBlockSequenceId);
- pindex->nSequenceId = nBlockSequenceId++;
- }
+ pindex->nSequenceId = nBlockSequenceId++;
if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
setBlockIndexCandidates.insert(pindex);
}
diff --git a/src/validation.h b/src/validation.h
index 9d8d7c06a9..ef48156309 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -199,7 +199,8 @@ struct PackageMempoolAcceptResult
/**
* Map from wtxid to finished MempoolAcceptResults. The client is responsible
* for keeping track of the transaction objects themselves. If a result is not
- * present, it means validation was unfinished for that transaction.
+ * present, it means validation was unfinished for that transaction. If there
+ * was a package-wide error (see result in m_state), m_tx_results will be empty.
*/
std::map<const uint256, const MempoolAcceptResult> m_tx_results;
@@ -227,7 +228,8 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo
* @param[in] txns Group of transactions which may be independent or contain
* parent-child dependencies. The transactions must not conflict
* with each other, i.e., must not spend the same inputs. If any
-* dependencies exist, parents must appear before children.
+* dependencies exist, parents must appear anywhere in the list
+* before their children.
* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction.
* If a transaction fails, validation will exit early and some results may be missing.
*/
@@ -556,9 +558,8 @@ protected:
* Every received block is assigned a unique and increasing identifier, so we
* know which one to give priority in case of a fork.
*/
- RecursiveMutex cs_nBlockSequenceId;
/** Blocks loaded from disk are assigned id 0, so start the counter at 1. */
- int32_t nBlockSequenceId = 1;
+ int32_t nBlockSequenceId GUARDED_BY(::cs_main) = 1;
/** Decreasing counter (used by subsequent preciousblock calls). */
int32_t nBlockReverseSequenceId = -1;
/** chainwork for the last block that preciousblock has been applied to. */
@@ -747,7 +748,7 @@ public:
void PruneBlockIndexCandidates();
- void UnloadBlockIndex();
+ void UnloadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Check whether we are doing an initial block download (synchronizing from disk or network) */
bool IsInitialBlockDownload() const;
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 6d502e1df1..1699424657 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -195,7 +195,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
//the selection random.
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
{
- nTotal += groups[i].m_value;
+ nTotal += groups[i].GetSelectionAmount();
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
@@ -205,7 +205,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
nBest = nTotal;
vfBest = vfIncluded;
}
- nTotal -= groups[i].m_value;
+ nTotal -= groups[i].GetSelectionAmount();
vfIncluded[i] = false;
}
}
@@ -341,3 +341,30 @@ CAmount OutputGroup::GetSelectionAmount() const
{
return m_subtract_fee_outputs ? m_value : effective_value;
}
+
+CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
+{
+ // This function should not be called with empty inputs as that would mean the selection failed
+ assert(!inputs.empty());
+
+ // Always consider the cost of spending an input now vs in the future.
+ CAmount waste = 0;
+ CAmount selected_effective_value = 0;
+ for (const CInputCoin& coin : inputs) {
+ waste += coin.m_fee - coin.m_long_term_fee;
+ selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
+ }
+
+ if (change_cost) {
+ // Consider the cost of making change and spending it in the future
+ // If we aren't making change, the caller should've set change_cost to 0
+ assert(change_cost > 0);
+ waste += change_cost;
+ } else {
+ // When we are not making change (change_cost == 0), consider the excess we are throwing away to fees
+ assert(selected_effective_value >= target);
+ waste += selected_effective_value - target;
+ }
+
+ return waste;
+}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 7a3fb82139..35617d455b 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -166,6 +166,21 @@ struct OutputGroup
CAmount GetSelectionAmount() const;
};
+/** Compute the waste for this result given the cost of change
+ * and the opportunity cost of spending these inputs now vs in the future.
+ * If change exists, waste = change_cost + inputs * (effective_feerate - long_term_feerate)
+ * If no change, waste = excess + inputs * (effective_feerate - long_term_feerate)
+ * where excess = selected_effective_value - target
+ * change_cost = effective_feerate * change_output_size + long_term_feerate * change_spend_size
+ *
+ * @param[in] inputs The selected inputs
+ * @param[in] change_cost The cost of creating change and spending it in the future. Only used if there is change. Must be 0 if there is no change.
+ * @param[in] target The amount targeted by the coin selection algorithm.
+ * @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
+ * @return The waste
+ */
+[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
+
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret);
// Original coin selection algorithm as a fallback
diff --git a/src/wallet/context.h b/src/wallet/context.h
index a83591154f..a382fb9021 100644
--- a/src/wallet/context.h
+++ b/src/wallet/context.h
@@ -5,11 +5,22 @@
#ifndef BITCOIN_WALLET_CONTEXT_H
#define BITCOIN_WALLET_CONTEXT_H
+#include <sync.h>
+
+#include <functional>
+#include <list>
+#include <memory>
+#include <vector>
+
class ArgsManager;
+class CWallet;
namespace interfaces {
class Chain;
+class Wallet;
} // namespace interfaces
+using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>;
+
//! WalletContext struct containing references to state shared between CWallet
//! instances, like the reference to the chain interface, and the list of opened
//! wallets.
@@ -22,7 +33,10 @@ class Chain;
//! behavior.
struct WalletContext {
interfaces::Chain* chain{nullptr};
- ArgsManager* args{nullptr};
+ ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
+ Mutex wallets_mutex;
+ std::vector<std::shared_ptr<CWallet>> wallets GUARDED_BY(wallets_mutex);
+ std::list<LoadWalletFn> wallet_load_fns GUARDED_BY(wallets_mutex);
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the WalletContext struct doesn't need to #include class
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index eb0d6316c0..bb5f0cceff 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -45,6 +45,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.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);
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting many (possibly all) or none, instead of selecting on a per-output basis. Privacy is improved as addresses are mostly swept with fewer transactions and outputs are aggregated in clean change addresses. It may result in higher fees due to less optimal coin selection caused by this added limitation and possibly a larger-than-necessary number of inputs being used. Always enabled for wallets with \"avoid_reuse\" enabled, otherwise default: %u.", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.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);
+ argsman.AddArg("-consolidatefeerate=<amt>", strprintf("The maximum feerate (in %s/kvB) at which transaction building may use more inputs than strictly necessary so that the wallet's UTXO pool can be reduced (default: %s).", CURRENCY_UNIT, FormatMoney(DEFAULT_CONSOLIDATE_FEERATE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kvB) 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",
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index e33adf94c9..0d4b98ecaf 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -16,6 +16,7 @@
#include <uint256.h>
#include <util/check.h>
#include <util/system.h>
+#include <util/translation.h>
#include <util/ui_change_type.h>
#include <wallet/context.h>
#include <wallet/feebumper.h>
@@ -109,7 +110,7 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
class WalletImpl : public Wallet
{
public:
- explicit WalletImpl(const std::shared_ptr<CWallet>& wallet) : m_wallet(wallet) {}
+ explicit WalletImpl(WalletContext& context, const std::shared_ptr<CWallet>& wallet) : m_context(context), m_wallet(wallet) {}
bool encryptWallet(const SecureString& wallet_passphrase) override
{
@@ -130,7 +131,7 @@ public:
bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override
{
LOCK(m_wallet->cs_wallet);
- std::string error;
+ bilingual_str error;
return m_wallet->GetNewDestination(type, label, dest, error);
}
bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override
@@ -457,7 +458,7 @@ public:
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
void remove() override
{
- RemoveWallet(m_wallet, false /* load_on_start */);
+ RemoveWallet(m_context, m_wallet, false /* load_on_start */);
}
bool isLegacy() override { return m_wallet->IsLegacy(); }
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
@@ -493,6 +494,7 @@ public:
}
CWallet* wallet() override { return m_wallet.get(); }
+ WalletContext& m_context;
std::shared_ptr<CWallet> m_wallet;
};
@@ -504,7 +506,7 @@ public:
m_context.chain = &chain;
m_context.args = &args;
}
- ~WalletClientImpl() override { UnloadWallets(); }
+ ~WalletClientImpl() override { UnloadWallets(m_context); }
//! ChainClient methods
void registerRpcs() override
@@ -518,11 +520,11 @@ public:
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
}
}
- bool verify() override { return VerifyWallets(*m_context.chain); }
- bool load() override { return LoadWallets(*m_context.chain); }
- void start(CScheduler& scheduler) override { return StartWallets(scheduler, *Assert(m_context.args)); }
- void flush() override { return FlushWallets(); }
- void stop() override { return StopWallets(); }
+ bool verify() override { return VerifyWallets(m_context); }
+ bool load() override { return LoadWallets(m_context); }
+ void start(CScheduler& scheduler) override { return StartWallets(m_context, scheduler); }
+ void flush() override { return FlushWallets(m_context); }
+ void stop() override { return StopWallets(m_context); }
void setMockTime(int64_t time) override { return SetMockTime(time); }
//! WalletClient methods
@@ -534,14 +536,14 @@ public:
options.require_create = true;
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
- return MakeWallet(CreateWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
+ return MakeWallet(m_context, CreateWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
options.require_existing = true;
- return MakeWallet(LoadWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
+ return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
std::string getWalletDir() override
{
@@ -558,15 +560,16 @@ public:
std::vector<std::unique_ptr<Wallet>> getWallets() override
{
std::vector<std::unique_ptr<Wallet>> wallets;
- for (const auto& wallet : GetWallets()) {
- wallets.emplace_back(MakeWallet(wallet));
+ for (const auto& wallet : GetWallets(m_context)) {
+ wallets.emplace_back(MakeWallet(m_context, wallet));
}
return wallets;
}
std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) override
{
- return HandleLoadWallet(std::move(fn));
+ return HandleLoadWallet(m_context, std::move(fn));
}
+ WalletContext* context() override { return &m_context; }
WalletContext m_context;
const std::vector<std::string> m_wallet_filenames;
@@ -577,7 +580,7 @@ public:
} // namespace wallet
namespace interfaces {
-std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? std::make_unique<wallet::WalletImpl>(wallet) : nullptr; }
+std::unique_ptr<Wallet> MakeWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet) { return wallet ? std::make_unique<wallet::WalletImpl>(context, wallet) : nullptr; }
std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args)
{
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index dbf9fd46b6..85cdbb67c9 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -8,18 +8,23 @@
#include <fs.h>
#include <interfaces/chain.h>
#include <scheduler.h>
+#include <util/check.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
+#include <wallet/context.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
#include <univalue.h>
-bool VerifyWallets(interfaces::Chain& chain)
+bool VerifyWallets(WalletContext& context)
{
- if (gArgs.IsArgSet("-walletdir")) {
- fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
+ interfaces::Chain& chain = *context.chain;
+ ArgsManager& args = *Assert(context.args);
+
+ if (args.IsArgSet("-walletdir")) {
+ fs::path wallet_dir = args.GetArg("-walletdir", "");
boost::system::error_code error;
// The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory
fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
@@ -34,7 +39,7 @@ bool VerifyWallets(interfaces::Chain& chain)
chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
return false;
}
- gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string());
+ args.ForceSetArg("-walletdir", canonical_wallet_dir.string());
}
LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
@@ -43,25 +48,27 @@ bool VerifyWallets(interfaces::Chain& chain)
// For backwards compatibility if an unnamed top level wallet exists in the
// wallets directory, include it in the default list of wallets to load.
- if (!gArgs.IsArgSet("wallet")) {
+ if (!args.IsArgSet("wallet")) {
DatabaseOptions options;
DatabaseStatus status;
bilingual_str error_string;
options.require_existing = true;
options.verify = false;
if (MakeWalletDatabase("", options, status, error_string)) {
- gArgs.LockSettings([&](util::Settings& settings) {
- util::SettingsValue wallets(util::SettingsValue::VARR);
- wallets.push_back(""); // Default wallet name is ""
- settings.rw_settings["wallet"] = wallets;
- });
+ util::SettingsValue wallets(util::SettingsValue::VARR);
+ wallets.push_back(""); // Default wallet name is ""
+ // Pass write=false because no need to write file and probably
+ // better not to. If unnamed wallet needs to be added next startup
+ // and the setting is empty, this code will just run again.
+ chain.updateRwSetting("wallet", wallets, /* write= */ false);
}
}
// Keep track of each wallet absolute path to detect duplicates.
std::set<fs::path> wallet_paths;
- for (const auto& wallet_file : gArgs.GetArgs("-wallet")) {
+ for (const auto& wallet : chain.getSettingsList("wallet")) {
+ const auto& wallet_file = wallet.get_str();
const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), wallet_file);
if (!wallet_paths.insert(path).second) {
@@ -87,11 +94,13 @@ bool VerifyWallets(interfaces::Chain& chain)
return true;
}
-bool LoadWallets(interfaces::Chain& chain)
+bool LoadWallets(WalletContext& context)
{
+ interfaces::Chain& chain = *context.chain;
try {
std::set<fs::path> wallet_paths;
- for (const std::string& name : gArgs.GetArgs("-wallet")) {
+ for (const auto& wallet : chain.getSettingsList("wallet")) {
+ const auto& name = wallet.get_str();
if (!wallet_paths.insert(name).second) {
continue;
}
@@ -106,13 +115,13 @@ bool LoadWallets(interfaces::Chain& chain)
continue;
}
chain.initMessage(_("Loading wallet…").translated);
- std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings) : nullptr;
+ std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(context, name, std::move(database), options.create_flags, error, warnings) : nullptr;
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
if (!pwallet) {
chain.initError(error);
return false;
}
- AddWallet(pwallet);
+ AddWallet(context, pwallet);
}
return true;
} catch (const std::runtime_error& e) {
@@ -121,41 +130,41 @@ bool LoadWallets(interfaces::Chain& chain)
}
}
-void StartWallets(CScheduler& scheduler, const ArgsManager& args)
+void StartWallets(WalletContext& context, CScheduler& scheduler)
{
- for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
pwallet->postInitProcess();
}
// Schedule periodic wallet flushes and tx rebroadcasts
- if (args.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
- scheduler.scheduleEvery(MaybeCompactWalletDB, std::chrono::milliseconds{500});
+ if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
+ scheduler.scheduleEvery([&context] { MaybeCompactWalletDB(context); }, std::chrono::milliseconds{500});
}
- scheduler.scheduleEvery(MaybeResendWalletTxs, std::chrono::milliseconds{1000});
+ scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, std::chrono::milliseconds{1000});
}
-void FlushWallets()
+void FlushWallets(WalletContext& context)
{
- for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
pwallet->Flush();
}
}
-void StopWallets()
+void StopWallets(WalletContext& context)
{
- for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
pwallet->Close();
}
}
-void UnloadWallets()
+void UnloadWallets(WalletContext& context)
{
- auto wallets = GetWallets();
+ auto wallets = GetWallets(context);
while (!wallets.empty()) {
auto wallet = wallets.back();
wallets.pop_back();
std::vector<bilingual_str> warnings;
- RemoveWallet(wallet, std::nullopt, warnings);
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt, warnings);
UnloadWallet(std::move(wallet));
}
}
diff --git a/src/wallet/load.h b/src/wallet/load.h
index 7910f0d6e1..e207bc2e09 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -11,27 +11,28 @@
class ArgsManager;
class CScheduler;
+struct WalletContext;
namespace interfaces {
class Chain;
} // namespace interfaces
//! Responsible for reading and validating the -wallet arguments and verifying the wallet database.
-bool VerifyWallets(interfaces::Chain& chain);
+bool VerifyWallets(WalletContext& context);
//! Load wallet databases.
-bool LoadWallets(interfaces::Chain& chain);
+bool LoadWallets(WalletContext& context);
//! Complete startup of wallets.
-void StartWallets(CScheduler& scheduler, const ArgsManager& args);
+void StartWallets(WalletContext& context, CScheduler& scheduler);
//! Flush all wallets in preparation for shutdown.
-void FlushWallets();
+void FlushWallets(WalletContext& context);
//! Stop all wallets. Wallets will be flushed first.
-void StopWallets();
+void StopWallets(WalletContext& context);
//! Close all wallets.
-void UnloadWallets();
+void UnloadWallets(WalletContext& context);
#endif // BITCOIN_WALLET_LOAD_H
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index ac60504419..72c60c8fe2 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chain.h>
+#include <clientversion.h>
#include <core_io.h>
#include <interfaces/chain.h>
#include <key_io.h>
@@ -783,7 +784,7 @@ RPCHelpMan dumpwallet()
std::sort(vKeyBirth.begin(), vKeyBirth.end());
// produce output
- file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD);
+ file << strprintf("# Wallet dump created by %s %s\n", PACKAGE_NAME, FormatFullVersion());
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString());
file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time));
@@ -1760,8 +1761,10 @@ RPCHelpMan listdescriptors()
{
return RPCHelpMan{
"listdescriptors",
- "\nList descriptors imported into a descriptor-enabled wallet.",
- {},
+ "\nList descriptors imported into a descriptor-enabled wallet.\n",
+ {
+ {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
+ },
RPCResult{RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
{RPCResult::Type::ARR, "descriptors", "Array of descriptor objects",
@@ -1781,6 +1784,7 @@ RPCHelpMan listdescriptors()
}},
RPCExamples{
HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
+ + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
@@ -1791,6 +1795,11 @@ RPCHelpMan listdescriptors()
throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
}
+ const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
+ if (priv) {
+ EnsureWalletIsUnlocked(*wallet);
+ }
+
LOCK(wallet->cs_wallet);
UniValue descriptors(UniValue::VARR);
@@ -1804,8 +1813,9 @@ RPCHelpMan listdescriptors()
LOCK(desc_spk_man->cs_desc_man);
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
std::string descriptor;
- if (!desc_spk_man->GetDescriptorString(descriptor)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string.");
+
+ if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
}
spk.pushKV("desc", descriptor);
spk.pushKV("timestamp", wallet_descriptor.creation_time);
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 43b67076cd..916f811f9b 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -96,14 +96,16 @@ bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string&
std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
{
CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
+ WalletContext& context = EnsureWalletContext(request.context);
+
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
- std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name);
+ std::shared_ptr<CWallet> pwallet = GetWallet(context, wallet_name);
if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
return pwallet;
}
- std::vector<std::shared_ptr<CWallet>> wallets = GetWallets();
+ std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(context);
if (wallets.size() == 1) {
return wallets[0];
}
@@ -276,9 +278,9 @@ static RPCHelpMan getnewaddress()
}
CTxDestination dest;
- std::string error;
+ bilingual_str error;
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
}
return EncodeDestination(dest);
@@ -324,9 +326,9 @@ static RPCHelpMan getrawchangeaddress()
}
CTxDestination dest;
- std::string error;
+ bilingual_str error;
if (!pwallet->GetNewChangeDestination(output_type, dest, error)) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error);
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
}
return EncodeDestination(dest);
},
@@ -2562,7 +2564,8 @@ static RPCHelpMan listwallets()
{
UniValue obj(UniValue::VARR);
- for (const std::shared_ptr<CWallet>& wallet : GetWallets()) {
+ WalletContext& context = EnsureWalletContext(request.context);
+ for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
LOCK(wallet->cs_wallet);
obj.push_back(wallet->GetName());
}
@@ -2572,6 +2575,37 @@ static RPCHelpMan listwallets()
};
}
+static std::tuple<std::shared_ptr<CWallet>, std::vector<bilingual_str>> LoadWalletHelper(WalletContext& context, UniValue load_on_start_param, const std::string wallet_name)
+{
+ DatabaseOptions options;
+ DatabaseStatus status;
+ options.require_existing = true;
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+ std::optional<bool> load_on_start = load_on_start_param.isNull() ? std::nullopt : std::optional<bool>(load_on_start_param.get_bool());
+ std::shared_ptr<CWallet> const wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
+
+ if (!wallet) {
+ // Map bad format to not found, since bad format is returned when the
+ // wallet directory exists, but doesn't contain a data file.
+ RPCErrorCode code = RPC_WALLET_ERROR;
+ switch (status) {
+ case DatabaseStatus::FAILED_NOT_FOUND:
+ case DatabaseStatus::FAILED_BAD_FORMAT:
+ code = RPC_WALLET_NOT_FOUND;
+ break;
+ case DatabaseStatus::FAILED_ALREADY_LOADED:
+ code = RPC_WALLET_ALREADY_LOADED;
+ break;
+ default: // RPC_WALLET_ERROR is returned for all other cases.
+ break;
+ }
+ throw JSONRPCError(code, error.original);
+ }
+
+ return { wallet, warnings };
+}
+
static RPCHelpMan loadwallet()
{
return RPCHelpMan{"loadwallet",
@@ -2598,30 +2632,7 @@ static RPCHelpMan loadwallet()
WalletContext& context = EnsureWalletContext(request.context);
const std::string name(request.params[0].get_str());
- DatabaseOptions options;
- DatabaseStatus status;
- options.require_existing = true;
- bilingual_str error;
- std::vector<bilingual_str> warnings;
- std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
- std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, name, load_on_start, options, status, error, warnings);
- if (!wallet) {
- // Map bad format to not found, since bad format is returned when the
- // wallet directory exists, but doesn't contain a data file.
- RPCErrorCode code = RPC_WALLET_ERROR;
- switch (status) {
- case DatabaseStatus::FAILED_NOT_FOUND:
- case DatabaseStatus::FAILED_BAD_FORMAT:
- code = RPC_WALLET_NOT_FOUND;
- break;
- case DatabaseStatus::FAILED_ALREADY_LOADED:
- code = RPC_WALLET_ALREADY_LOADED;
- break;
- default: // RPC_WALLET_ERROR is returned for all other cases.
- break;
- }
- throw JSONRPCError(code, error.original);
- }
+ auto [wallet, warnings] = LoadWalletHelper(context, request.params[1], name);
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
@@ -2780,7 +2791,7 @@ static RPCHelpMan createwallet()
options.create_passphrase = passphrase;
bilingual_str error;
std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
- std::shared_ptr<CWallet> wallet = CreateWallet(*context.chain, request.params[0].get_str(), load_on_start, options, status, error, warnings);
+ std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
if (!wallet) {
RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
throw JSONRPCError(code, error.original);
@@ -2795,6 +2806,68 @@ static RPCHelpMan createwallet()
};
}
+static RPCHelpMan restorewallet()
+{
+ return RPCHelpMan{
+ "restorewallet",
+ "\nRestore and loads a wallet from backup.\n",
+ {
+ {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
+ {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
+ {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
+ {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
+ + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+
+ WalletContext& context = EnsureWalletContext(request.context);
+
+ std::string backup_file = request.params[1].get_str();
+
+ if (!fs::exists(backup_file)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Backup file does not exist");
+ }
+
+ std::string wallet_name = request.params[0].get_str();
+
+ const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), wallet_name);
+
+ if (fs::exists(wallet_path)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name already exists.");
+ }
+
+ if (!TryCreateDirectories(wallet_path)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Failed to create database path '%s'. Database already exists.", wallet_path.string()));
+ }
+
+ auto wallet_file = wallet_path / "wallet.dat";
+
+ fs::copy_file(backup_file, wallet_file, fs::copy_option::fail_if_exists);
+
+ auto [wallet, warnings] = LoadWalletHelper(context, request.params[2], wallet_name);
+
+ UniValue obj(UniValue::VOBJ);
+ obj.pushKV("name", wallet->GetName());
+ obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
+
+ return obj;
+
+},
+ };
+}
+
static RPCHelpMan unloadwallet()
{
return RPCHelpMan{"unloadwallet",
@@ -2822,7 +2895,8 @@ static RPCHelpMan unloadwallet()
wallet_name = request.params[0].get_str();
}
- std::shared_ptr<CWallet> wallet = GetWallet(wallet_name);
+ WalletContext& context = EnsureWalletContext(request.context);
+ std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
}
@@ -2832,7 +2906,7 @@ static RPCHelpMan unloadwallet()
// is destroyed (see CheckUniqueFileid).
std::vector<bilingual_str> warnings;
std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
- if (!RemoveWallet(wallet, load_on_start, warnings)) {
+ if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
@@ -3392,7 +3466,7 @@ RPCHelpMan signrawtransactionwithwallet()
int nHashType = ParseSighashString(request.params[2]);
// Script verification errors
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
bool complete = pwallet->SignTransaction(mtx, coins, nHashType, input_errors);
UniValue result(UniValue::VOBJ);
@@ -3875,7 +3949,7 @@ RPCHelpMan getaddressinfo()
DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey));
if (desc_spk_man) {
std::string desc_str;
- if (desc_spk_man->GetDescriptorString(desc_str)) {
+ if (desc_spk_man->GetDescriptorString(desc_str, /* priv */ false)) {
ret.pushKV("parent_desc", desc_str);
}
}
@@ -4639,6 +4713,7 @@ static const CRPCCommand commands[] =
{ "wallet", &bumpfee, },
{ "wallet", &psbtbumpfee, },
{ "wallet", &createwallet, },
+ { "wallet", &restorewallet, },
{ "wallet", &dumpprivkey, },
{ "wallet", &dumpwallet, },
{ "wallet", &encryptwallet, },
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 73433214f1..fe41f9b8cc 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -20,10 +20,10 @@
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
-bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
+bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
- error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
+ error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");
return false;
}
assert(type != OutputType::BECH32M);
@@ -34,7 +34,7 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestinat
// Generate a new key that is added to wallet
CPubKey new_key;
if (!GetKeyFromPool(new_key, type)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
+ error = _("Error: Keypool ran out, please call keypoolrefill first");
return false;
}
LearnRelatedScripts(new_key, type);
@@ -295,22 +295,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
return true;
}
-bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error)
+bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
- error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
+ error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");
return false;
}
assert(type != OutputType::BECH32M);
LOCK(cs_KeyStore);
if (!CanGetAddresses(internal)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
+ error = _("Error: Keypool ran out, please call keypoolrefill first");
return false;
}
if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
+ error = _("Error: Keypool ran out, please call keypoolrefill first");
return false;
}
address = GetDestinationForKey(keypool.vchPubKey, type);
@@ -592,7 +592,7 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig
}
}
-bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
+bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const
{
return ::SignTransaction(tx, this, coins, sighash, input_errors);
}
@@ -1613,11 +1613,11 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
+bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
{
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
if (!CanGetAddresses()) {
- error = "No addresses available";
+ error = _("No addresses available");
return false;
}
{
@@ -1636,12 +1636,12 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
std::vector<CScript> scripts_temp;
if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) {
// We can't generate anymore keys
- error = "Error: Keypool ran out, please call keypoolrefill first";
+ error = _("Error: Keypool ran out, please call keypoolrefill first");
return false;
}
if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
// We can't generate anymore keys
- error = "Error: Keypool ran out, please call keypoolrefill first";
+ error = _("Error: Keypool ran out, please call keypoolrefill first");
return false;
}
@@ -1721,7 +1721,7 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
return true;
}
-bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error)
+bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
{
LOCK(cs_desc_man);
bool result = GetNewDestination(type, address, error);
@@ -2046,7 +2046,7 @@ bool DescriptorScriptPubKeyMan::CanProvide(const CScript& script, SignatureData&
return IsMine(script);
}
-bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
+bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const
{
std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>();
for (const auto& coin_pair : coins) {
@@ -2258,13 +2258,20 @@ const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
return script_pub_keys;
}
-bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out) const
+bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool priv) const
{
LOCK(cs_desc_man);
FlatSigningProvider provider;
provider.keys = GetKeys();
+ if (priv) {
+ // For the private version, always return the master key to avoid
+ // exposing child private keys. The risk implications of exposing child
+ // private keys together with the parent xpub may be non-obvious for users.
+ return m_wallet_descriptor.descriptor->ToPrivateString(provider, out);
+ }
+
return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache);
}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index e329e0cf8f..ef74638751 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -148,17 +148,6 @@ public:
}
};
-class KeyIDHasher
-{
-public:
- KeyIDHasher() {}
-
- size_t operator()(const CKeyID& id) const
- {
- return id.GetUint64(0);
- }
-};
-
/*
* A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet.
* It contains the scripts and keys related to the scriptPubKeys it manages.
@@ -174,14 +163,14 @@ protected:
public:
explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
virtual ~ScriptPubKeyMan() {};
- virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; }
+ virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) { return false; }
virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
//! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it.
virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; }
virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; }
- virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { return false; }
+ virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) { return false; }
virtual void KeepDestination(int64_t index, const OutputType& type) {}
virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {}
@@ -230,7 +219,7 @@ public:
virtual bool CanProvide(const CScript& script, SignatureData& sigdata) { return false; }
/** Creates new signatures and adds them to the transaction. Returns whether all inputs were signed */
- virtual bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const { return false; }
+ virtual bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const { return false; }
/** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
@@ -355,13 +344,13 @@ private:
public:
using ScriptPubKeyMan::ScriptPubKeyMan;
- bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
+ bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override;
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override;
void KeepDestination(int64_t index, const OutputType& type) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override;
@@ -396,7 +385,7 @@ public:
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
- bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
+ bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
@@ -559,13 +548,13 @@ public:
mutable RecursiveMutex cs_desc_man;
- bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
+ bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override;
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override;
// Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file
@@ -601,7 +590,7 @@ public:
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
- bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
+ bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
@@ -621,7 +610,7 @@ public:
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
const std::vector<CScript> GetScriptPubKeys() const;
- bool GetDescriptorString(std::string& out) const;
+ bool GetDescriptorString(std::string& out, const bool priv) const;
void UpgradeDescriptorCache();
};
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 6a8df437ae..928335da2b 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -357,17 +357,44 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
{
setCoinsRet.clear();
nValueRet = 0;
+ // Vector of results for use with waste calculation
+ // In order: calculated waste, selected inputs, selected input value (sum of input values)
+ // TODO: Use a struct representing the selection result
+ std::vector<std::tuple<CAmount, std::set<CInputCoin>, CAmount>> results;
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
std::vector<OutputGroup> positive_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, true /* positive_only */);
- if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, setCoinsRet, nValueRet)) {
- return true;
+ std::set<CInputCoin> bnb_coins;
+ CAmount bnb_value;
+ if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, bnb_coins, bnb_value)) {
+ const auto waste = GetSelectionWaste(bnb_coins, /* cost of change */ CAmount(0), nTargetValue, !coin_selection_params.m_subtract_fee_outputs);
+ results.emplace_back(std::make_tuple(waste, std::move(bnb_coins), bnb_value));
}
+
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
std::vector<OutputGroup> all_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, false /* positive_only */);
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
// So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
- return KnapsackSolver(nTargetValue + coin_selection_params.m_change_fee, all_groups, setCoinsRet, nValueRet);
+ std::set<CInputCoin> knapsack_coins;
+ CAmount knapsack_value;
+ if (KnapsackSolver(nTargetValue + coin_selection_params.m_change_fee, all_groups, knapsack_coins, knapsack_value)) {
+ const auto waste = GetSelectionWaste(knapsack_coins, coin_selection_params.m_cost_of_change, nTargetValue + coin_selection_params.m_change_fee, !coin_selection_params.m_subtract_fee_outputs);
+ results.emplace_back(std::make_tuple(waste, std::move(knapsack_coins), knapsack_value));
+ }
+
+ if (results.size() == 0) {
+ // No solution found
+ return false;
+ }
+
+ // Choose the result with the least waste
+ // If the waste is the same, choose the one which spends more inputs.
+ const auto& best_result = std::min_element(results.begin(), results.end(), [](const auto& a, const auto& b) {
+ return std::get<0>(a) < std::get<0>(b) || (std::get<0>(a) == std::get<0>(b) && std::get<1>(a).size() > std::get<1>(b).size());
+ });
+ setCoinsRet = std::get<1>(*best_result);
+ nValueRet = std::get<2>(*best_result);
+ return true;
}
bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const
@@ -586,6 +613,9 @@ bool CWallet::CreateTransactionInternal(
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
+ // Set the long term feerate estimate to the wallet's consolidate feerate
+ coin_selection_params.m_long_term_feerate = m_consolidate_feerate;
+
CAmount recipients_sum = 0;
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
ReserveDestination reservedest(this, change_type);
@@ -618,9 +648,9 @@ bool CWallet::CreateTransactionInternal(
// Reserve a new key pair from key pool. If it fails, provide a dummy
// destination in case we don't need change.
CTxDestination dest;
- std::string dest_err;
+ bilingual_str dest_err;
if (!reservedest.GetReservedDestination(dest, true, dest_err)) {
- error = strprintf(_("Transaction needs a change address, but we can't generate it. %s"), dest_err);
+ error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err;
}
scriptChange = GetScriptForDestination(dest);
// A valid destination implies a change script (and
@@ -659,11 +689,6 @@ bool CWallet::CreateTransactionInternal(
return false;
}
- // Get long term estimate
- CCoinControl cc_temp;
- cc_temp.m_confirm_target = chain().estimateMaxBlocks();
- coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr);
-
// Calculate the cost of change
// Cost of change is the cost of creating the change output + cost of spending the change output in the future.
// For creating the change output now, we use the effective feerate.
@@ -778,6 +803,10 @@ bool CWallet::CreateTransactionInternal(
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
}
+ // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when
+ // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
+ assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount);
+
// Update nFeeRet in case fee_needed changed due to dropping the change output
if (fee_needed <= change_and_fee - change_amount) {
nFeeRet = change_and_fee - change_amount;
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index c65ebad52f..7b2169a5b6 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -7,6 +7,7 @@
#include <primitives/transaction.h>
#include <random.h>
#include <test/util/setup_common.h>
+#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/coinselection.h>
#include <wallet/test/wallet_test_fixture.h>
@@ -48,12 +49,16 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>&
set.emplace_back(MakeTransactionRef(tx), nInput);
}
-static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
+static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fee = 0, CAmount long_term_fee = 0)
{
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
- set.emplace(MakeTransactionRef(tx), nInput);
+ CInputCoin coin(MakeTransactionRef(tx), nInput);
+ coin.effective_value = nValue - fee;
+ coin.m_fee = fee;
+ coin.m_long_term_fee = long_term_fee;
+ set.insert(coin);
}
static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
@@ -66,7 +71,7 @@ static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bo
tx.vout[nInput].nValue = nValue;
if (spendable) {
CTxDestination dest;
- std::string error;
+ bilingual_str error;
const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
assert(destination_ok);
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
@@ -136,6 +141,13 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
return static_groups;
}
+inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinEligibilityFilter& filter)
+{
+ static std::vector<OutputGroup> static_groups;
+ static_groups = testWallet.GroupOutputs(vCoins, coin_selection_params, filter, /* positive_only */false);
+ return static_groups;
+}
+
// Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE(bnb_search_test)
{
@@ -280,14 +292,14 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
empty_wallet();
add_coin(1);
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
- BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(vCoins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change, setCoinsRet, nValueRet));
// Test fees subtracted from output:
empty_wallet();
add_coin(1 * CENT);
vCoins.at(0).nInputBytes = 40;
coin_selection_params_bnb.m_subtract_fee_outputs = true;
- BOOST_CHECK(testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb));
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(vCoins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// Make sure that can use BnB when there are preset inputs
@@ -322,24 +334,24 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
empty_wallet();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(!KnapsackSolver(1 * CENT, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
add_coin(1*CENT, 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!testWallet.AttemptSelection( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(!KnapsackSolver(1 * CENT, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
// but we can find a new 1 cent
- BOOST_CHECK( testWallet.AttemptSelection( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(1 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
add_coin(2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!testWallet.AttemptSelection( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(!KnapsackSolver(3 * CENT, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
// we can make 3 cents of new coins
- BOOST_CHECK( testWallet.AttemptSelection( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(3 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
add_coin(5*CENT); // add a mature 5 cent coin,
@@ -349,33 +361,33 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!testWallet.AttemptSelection(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(!KnapsackSolver(38 * CENT, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!testWallet.AttemptSelection(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(!KnapsackSolver(38 * CENT, KnapsackGroupOutputs(filter_standard_extra), setCoinsRet, nValueRet));
// but we can make 37 cents if we accept new coins from ourself
- BOOST_CHECK( testWallet.AttemptSelection(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(37 * CENT, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
// and we can make 38 cents if we accept all new coins
- BOOST_CHECK( testWallet.AttemptSelection(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(38 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- BOOST_CHECK( testWallet.AttemptSelection(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(34 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- BOOST_CHECK( testWallet.AttemptSelection( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(7 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- BOOST_CHECK( testWallet.AttemptSelection( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(8 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK(nValueRet == 8 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- BOOST_CHECK( testWallet.AttemptSelection( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(9 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -389,30 +401,30 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- BOOST_CHECK( testWallet.AttemptSelection(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
- BOOST_CHECK(!testWallet.AttemptSelection(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(71 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
+ BOOST_CHECK(!KnapsackSolver(72 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(16 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(16 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- BOOST_CHECK( testWallet.AttemptSelection(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(16 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- BOOST_CHECK( testWallet.AttemptSelection(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(11 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -421,11 +433,11 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin( 2*COIN);
add_coin( 3*COIN);
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- BOOST_CHECK( testWallet.AttemptSelection(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(95 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
- BOOST_CHECK( testWallet.AttemptSelection(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(195 * CENT, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -440,14 +452,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
- BOOST_CHECK( testWallet.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(MIN_CHANGE, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
// but if we add a bigger coin, small change is avoided
add_coin(1111*MIN_CHANGE);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(1 * MIN_CHANGE, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// if we add more small coins:
@@ -455,7 +467,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 7 / 10);
// and try again to make 1.0 * MIN_CHANGE
- BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(1 * MIN_CHANGE, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
@@ -464,7 +476,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int j = 0; j < 20; j++)
add_coin(50000 * COIN);
- BOOST_CHECK( testWallet.AttemptSelection(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(500000 * COIN, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
@@ -477,7 +489,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 7 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.AttemptSelection(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(1 * MIN_CHANGE, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -487,7 +499,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 8 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.AttemptSelection(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(MIN_CHANGE, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
@@ -498,12 +510,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 100);
// trying to make 100.01 from these three coins
- BOOST_CHECK(testWallet.AttemptSelection(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(MIN_CHANGE * 10001 / 100, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- BOOST_CHECK(testWallet.AttemptSelection(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(MIN_CHANGE * 9990 / 100, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
}
@@ -517,7 +529,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- BOOST_CHECK(testWallet.AttemptSelection(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(2000, KnapsackGroupOutputs(filter_confirmed), setCoinsRet, nValueRet));
if (amt - 2000 < MIN_CHANGE) {
// needs more than one input:
@@ -602,7 +614,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(1000 * COIN);
add_coin(3 * COIN);
- BOOST_CHECK(testWallet.AttemptSelection(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params));
+ BOOST_CHECK(KnapsackSolver(1003 * COIN, KnapsackGroupOutputs(filter_standard), setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -650,4 +662,73 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
}
}
+BOOST_AUTO_TEST_CASE(waste_test)
+{
+ CoinSet selection;
+ const CAmount fee{100};
+ const CAmount change_cost{125};
+ const CAmount fee_diff{40};
+ const CAmount in_amt{3 * COIN};
+ const CAmount target{2 * COIN};
+ const CAmount excess{in_amt - fee * 2 - target};
+
+ // Waste with change is the change cost and difference between fee and long term fee
+ add_coin(1 * COIN, 1, selection, fee, fee - fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee - fee_diff);
+ const CAmount waste1 = GetSelectionWaste(selection, change_cost, target);
+ BOOST_CHECK_EQUAL(fee_diff * 2 + change_cost, waste1);
+ selection.clear();
+
+ // Waste without change is the excess and difference between fee and long term fee
+ add_coin(1 * COIN, 1, selection, fee, fee - fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee - fee_diff);
+ const CAmount waste_nochange1 = GetSelectionWaste(selection, 0, target);
+ BOOST_CHECK_EQUAL(fee_diff * 2 + excess, waste_nochange1);
+ selection.clear();
+
+ // Waste with change and fee == long term fee is just cost of change
+ add_coin(1 * COIN, 1, selection, fee, fee);
+ add_coin(2 * COIN, 2, selection, fee, fee);
+ BOOST_CHECK_EQUAL(change_cost, GetSelectionWaste(selection, change_cost, target));
+ selection.clear();
+
+ // Waste without change and fee == long term fee is just the excess
+ add_coin(1 * COIN, 1, selection, fee, fee);
+ add_coin(2 * COIN, 2, selection, fee, fee);
+ BOOST_CHECK_EQUAL(excess, GetSelectionWaste(selection, 0, target));
+ selection.clear();
+
+ // Waste will be greater when fee is greater, but long term fee is the same
+ add_coin(1 * COIN, 1, selection, fee * 2, fee - fee_diff);
+ add_coin(2 * COIN, 2, selection, fee * 2, fee - fee_diff);
+ const CAmount waste2 = GetSelectionWaste(selection, change_cost, target);
+ BOOST_CHECK_GT(waste2, waste1);
+ selection.clear();
+
+ // Waste with change is the change cost and difference between fee and long term fee
+ // With long term fee greater than fee, waste should be less than when long term fee is less than fee
+ add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
+ const CAmount waste3 = GetSelectionWaste(selection, change_cost, target);
+ BOOST_CHECK_EQUAL(fee_diff * -2 + change_cost, waste3);
+ BOOST_CHECK_LT(waste3, waste1);
+ selection.clear();
+
+ // Waste without change is the excess and difference between fee and long term fee
+ // With long term fee greater than fee, waste should be less than when long term fee is less than fee
+ add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
+ const CAmount waste_nochange2 = GetSelectionWaste(selection, 0, target);
+ BOOST_CHECK_EQUAL(fee_diff * -2 + excess, waste_nochange2);
+ BOOST_CHECK_LT(waste_nochange2, waste_nochange1);
+ selection.clear();
+
+ // 0 Waste only when fee == long term fee, no change, and no excess
+ add_coin(1 * COIN, 1, selection, fee, fee);
+ add_coin(2 * COIN, 2, selection, fee, fee);
+ const CAmount exact_target = in_amt - 2 * fee;
+ BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, 0, exact_target));
+
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 17f5264b45..16cb7e0baf 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -25,7 +25,11 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file)
std::string test_name = "test_name.dat";
const fs::path datadir = gArgs.GetDataDirNet();
fs::path file_path = datadir / test_name;
+#if BOOST_VERSION >= 107700
+ std::ofstream f(BOOST_FILESYSTEM_C_STR(file_path));
+#else
std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR);
+#endif // BOOST_VERSION >= 107700
f.close();
std::string filename;
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index dd9354848d..53c972c46d 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -32,7 +32,11 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam
fs::create_directories(m_walletdir_path_cases["default"]);
fs::create_directories(m_walletdir_path_cases["custom"]);
fs::create_directories(m_walletdir_path_cases["relative"]);
+#if BOOST_VERSION >= 107700
+ std::ofstream f(BOOST_FILESYSTEM_C_STR(m_walletdir_path_cases["file"]));
+#else
std::ofstream f(m_walletdir_path_cases["file"].BOOST_FILESYSTEM_C_STR);
+#endif // BOOST_VERSION >= 107700
f.close();
}
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 75a08b6f74..bc5498c88e 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -20,6 +20,7 @@
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
+#include <wallet/context.h>
#include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h>
@@ -30,8 +31,6 @@ RPCHelpMan importmulti();
RPCHelpMan dumpwallet();
RPCHelpMan importwallet();
-extern RecursiveMutex cs_wallets;
-
// Ensure that fee levels defined in the wallet are at least as high
// as the default levels for node policy.
static_assert(DEFAULT_TRANSACTION_MINFEE >= DEFAULT_MIN_RELAY_TX_FEE, "wallet minimum fee is smaller than default relay fee");
@@ -39,15 +38,15 @@ static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wa
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
-static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain* chain)
+static std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
{
DatabaseOptions options;
DatabaseStatus status;
bilingual_str error;
std::vector<bilingual_str> warnings;
auto database = MakeWalletDatabase("", options, status, error);
- auto wallet = CWallet::Create(chain, "", std::move(database), options.create_flags, error, warnings);
- if (chain) {
+ auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
+ if (context.chain) {
wallet->postInitProcess();
}
return wallet;
@@ -69,7 +68,7 @@ static CMutableTransaction TestSimpleSpend(const CTransaction& from, uint32_t in
keystore.AddKey(key);
std::map<COutPoint, Coin> coins;
coins[mtx.vin[0].prevout].out = from.vout[index];
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
BOOST_CHECK(SignTransaction(mtx, &keystore, coins, SIGHASH_ALL, input_errors));
return mtx;
}
@@ -200,7 +199,9 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
- AddWallet(wallet);
+ WalletContext context;
+ context.args = &gArgs;
+ AddWallet(context, wallet);
UniValue keys;
keys.setArray();
UniValue key;
@@ -218,6 +219,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
key.pushKV("internal", UniValue(true));
keys.push_back(key);
JSONRPCRequest request;
+ request.context = &context;
request.params.setArray();
request.params.push_back(keys);
@@ -231,7 +233,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
"downloading and rescanning the relevant blocks (see -reindex and -rescan "
"options).\"}},{\"success\":true}]",
0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
- RemoveWallet(wallet, std::nullopt);
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt);
}
}
@@ -258,6 +260,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
+ WalletContext context;
+ context.args = &gArgs;
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
@@ -265,15 +269,16 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
- AddWallet(wallet);
+ AddWallet(context, wallet);
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
JSONRPCRequest request;
+ request.context = &context;
request.params.setArray();
request.params.push_back(backup_file);
::dumpwallet().HandleRequest(request);
- RemoveWallet(wallet, std::nullopt);
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt);
}
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
@@ -283,13 +288,16 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
+ WalletContext context;
+ context.args = &gArgs;
JSONRPCRequest request;
+ request.context = &context;
request.params.setArray();
request.params.push_back(backup_file);
- AddWallet(wallet);
+ AddWallet(context, wallet);
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
::importwallet().HandleRequest(request);
- RemoveWallet(wallet, std::nullopt);
+ RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt);
BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U);
BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U);
@@ -589,7 +597,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
CTxDestination dest;
- std::string error;
+ bilingual_str error;
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
}
@@ -679,7 +687,10 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
{
gArgs.ForceSetArg("-unsafesqlitesync", "1");
// Create new wallet with known key and unload it.
- auto wallet = TestLoadWallet(m_node.chain.get());
+ WalletContext context;
+ context.args = &gArgs;
+ context.chain = m_node.chain.get();
+ auto wallet = TestLoadWallet(context);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
@@ -719,7 +730,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// Reload wallet and make sure new transactions are detected despite events
// being blocked
- wallet = TestLoadWallet(m_node.chain.get());
+ wallet = TestLoadWallet(context);
BOOST_CHECK(rescan_completed);
BOOST_CHECK_EQUAL(addtx_count, 2);
{
@@ -746,20 +757,20 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// deadlock during the sync and simulates a new block notification happening
// as soon as possible.
addtx_count = 0;
- auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet, cs_wallets) {
+ auto handler = HandleLoadWallet(context, [&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet, context.wallets_mutex) {
BOOST_CHECK(rescan_completed);
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
- LEAVE_CRITICAL_SECTION(cs_wallets);
+ LEAVE_CRITICAL_SECTION(context.wallets_mutex);
LEAVE_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
SyncWithValidationInterfaceQueue();
ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
- ENTER_CRITICAL_SECTION(cs_wallets);
+ ENTER_CRITICAL_SECTION(context.wallets_mutex);
});
- wallet = TestLoadWallet(m_node.chain.get());
+ wallet = TestLoadWallet(context);
BOOST_CHECK_EQUAL(addtx_count, 4);
{
LOCK(wallet->cs_wallet);
@@ -773,7 +784,9 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
{
- auto wallet = TestLoadWallet(nullptr);
+ WalletContext context;
+ context.args = &gArgs;
+ auto wallet = TestLoadWallet(context);
BOOST_CHECK(wallet);
UnloadWallet(std::move(wallet));
}
@@ -781,7 +794,10 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
gArgs.ForceSetArg("-unsafesqlitesync", "1");
- auto wallet = TestLoadWallet(m_node.chain.get());
+ WalletContext context;
+ context.args = &gArgs;
+ context.chain = m_node.chain.get();
+ auto wallet = TestLoadWallet(context);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 741540f724..6f3dcf2afa 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -33,6 +33,7 @@
#include <util/string.h>
#include <util/translation.h>
#include <wallet/coincontrol.h>
+#include <wallet/context.h>
#include <wallet/fees.h>
#include <wallet/external_signer_scriptpubkeyman.h>
@@ -54,10 +55,6 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
},
};
-RecursiveMutex cs_wallets;
-static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
-static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
-
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name)
{
util::SettingsValue setting_value = chain.getRwSetting("wallet");
@@ -94,19 +91,29 @@ static void UpdateWalletSetting(interfaces::Chain& chain,
}
}
-bool AddWallet(const std::shared_ptr<CWallet>& wallet)
+/**
+ * Refresh mempool status so the wallet is in an internally consistent state and
+ * immediately knows the transaction's status: Whether it can be considered
+ * trusted and is eligible to be abandoned ...
+ */
+static void RefreshMempoolStatus(CWalletTx& tx, interfaces::Chain& chain)
{
- LOCK(cs_wallets);
+ tx.fInMempool = chain.isInMempool(tx.GetHash());
+}
+
+bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
+{
+ LOCK(context.wallets_mutex);
assert(wallet);
- std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
- if (i != vpwallets.end()) return false;
- vpwallets.push_back(wallet);
+ std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet);
+ if (i != context.wallets.end()) return false;
+ context.wallets.push_back(wallet);
wallet->ConnectScriptPubKeyManNotifiers();
wallet->NotifyCanGetAddressesChanged();
return true;
}
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings)
+bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings)
{
assert(wallet);
@@ -115,10 +122,10 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> lo
// Unregister with the validation interface which also drops shared ponters.
wallet->m_chain_notifications_handler.reset();
- LOCK(cs_wallets);
- std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
- if (i == vpwallets.end()) return false;
- vpwallets.erase(i);
+ LOCK(context.wallets_mutex);
+ std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet);
+ if (i == context.wallets.end()) return false;
+ context.wallets.erase(i);
// Write the wallet setting
UpdateWalletSetting(chain, name, load_on_start, warnings);
@@ -126,32 +133,32 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> lo
return true;
}
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start)
+bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start)
{
std::vector<bilingual_str> warnings;
- return RemoveWallet(wallet, load_on_start, warnings);
+ return RemoveWallet(context, wallet, load_on_start, warnings);
}
-std::vector<std::shared_ptr<CWallet>> GetWallets()
+std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context)
{
- LOCK(cs_wallets);
- return vpwallets;
+ LOCK(context.wallets_mutex);
+ return context.wallets;
}
-std::shared_ptr<CWallet> GetWallet(const std::string& name)
+std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name)
{
- LOCK(cs_wallets);
- for (const std::shared_ptr<CWallet>& wallet : vpwallets) {
+ LOCK(context.wallets_mutex);
+ for (const std::shared_ptr<CWallet>& wallet : context.wallets) {
if (wallet->GetName() == name) return wallet;
}
return nullptr;
}
-std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet)
+std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet)
{
- LOCK(cs_wallets);
- auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(), std::move(load_wallet));
- return interfaces::MakeHandler([it] { LOCK(cs_wallets); g_load_wallet_fns.erase(it); });
+ LOCK(context.wallets_mutex);
+ auto it = context.wallet_load_fns.emplace(context.wallet_load_fns.end(), std::move(load_wallet));
+ return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_load_fns.erase(it); });
}
static Mutex g_loading_wallet_mutex;
@@ -203,7 +210,7 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
}
namespace {
-std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
try {
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
@@ -212,18 +219,18 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std:
return nullptr;
}
- chain.initMessage(_("Loading wallet…").translated);
- std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings);
+ context.chain->initMessage(_("Loading wallet…").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(context, name, std::move(database), options.create_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
- AddWallet(wallet);
+ AddWallet(context, wallet);
wallet->postInitProcess();
// Write the wallet setting
- UpdateWalletSetting(chain, name, load_on_start, warnings);
+ UpdateWalletSetting(*context.chain, name, load_on_start, warnings);
return wallet;
} catch (const std::runtime_error& e) {
@@ -234,7 +241,7 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std:
}
} // namespace
-std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name));
if (!result.second) {
@@ -242,12 +249,12 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string&
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
- auto wallet = LoadWalletInternal(chain, name, load_on_start, options, status, error, warnings);
+ auto wallet = LoadWalletInternal(context, name, load_on_start, options, status, error, warnings);
WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
return wallet;
}
-std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
@@ -292,8 +299,8 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
}
// Make the wallet
- chain.initMessage(_("Loading wallet…").translated);
- std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), wallet_creation_flags, error, warnings);
+ context.chain->initMessage(_("Loading wallet…").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(context, name, std::move(database), wallet_creation_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_CREATE;
@@ -335,11 +342,11 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
wallet->Lock();
}
}
- AddWallet(wallet);
+ AddWallet(context, wallet);
wallet->postInitProcess();
// Write the wallet settings
- UpdateWalletSetting(chain, name, load_on_start, warnings);
+ UpdateWalletSetting(*context.chain, name, load_on_start, warnings);
status = DatabaseStatus::SUCCESS;
return wallet;
@@ -803,10 +810,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
wtx.mapValue["replaced_by_txid"] = newHash.ToString();
// Refresh mempool status without waiting for transactionRemovedFromMempool
- // notification so the wallet is in an internally consistent state and
- // immediately knows the old transaction should not be considered trusted
- // and is eligible to be abandoned
- wtx.fInMempool = chain().isInMempool(originalHash);
+ RefreshMempoolStatus(wtx, chain());
WalletBatch batch(GetDatabase());
@@ -1206,7 +1210,7 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t memp
auto it = mapWallet.find(tx->GetHash());
if (it != mapWallet.end()) {
- it->second.fInMempool = true;
+ RefreshMempoolStatus(it->second, chain());
}
}
@@ -1214,7 +1218,7 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
LOCK(cs_wallet);
auto it = mapWallet.find(tx->GetHash());
if (it != mapWallet.end()) {
- it->second.fInMempool = false;
+ RefreshMempoolStatus(it->second, chain());
}
// Handle transactions that were removed from the mempool because they
// conflict with transactions in a newly connected block.
@@ -1360,9 +1364,10 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co
bool CWallet::IsHDEnabled() const
{
// All Active ScriptPubKeyMans must be HD for this to be true
- bool result = true;
+ bool result = false;
for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
- result &= spk_man->IsHDEnabled();
+ if (!spk_man->IsHDEnabled()) return false;
+ result = true;
}
return result;
}
@@ -1795,9 +1800,9 @@ void CWallet::ResendWalletTransactions()
/** @} */ // end of mapWallet
-void MaybeResendWalletTxs()
+void MaybeResendWalletTxs(WalletContext& context)
{
- for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
pwallet->ResendWalletTransactions();
}
}
@@ -1822,11 +1827,11 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
const CWalletTx& wtx = mi->second;
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
}
- std::map<int, std::string> input_errors;
+ std::map<int, bilingual_str> input_errors;
return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors);
}
-bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
+bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const
{
// Try to sign with all ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
@@ -2128,7 +2133,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
return res;
}
-bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error)
+bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error)
{
LOCK(cs_wallet);
error.clear();
@@ -2138,7 +2143,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label,
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
} else {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
+ error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
}
if (result) {
SetAddressBook(dest, label, "receive");
@@ -2147,7 +2152,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label,
return result;
}
-bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error)
+bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
{
LOCK(cs_wallet);
error.clear();
@@ -2200,11 +2205,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
return result;
}
-bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, std::string& error)
+bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
+ error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
return false;
}
@@ -2502,8 +2507,10 @@ std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, cons
return MakeDatabase(wallet_path, options, status, error_string);
}
-std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
+ interfaces::Chain* chain = context.chain;
+ ArgsManager& args = *Assert(context.args);
const std::string& walletFile = database->Filename();
int64_t nStart = GetTimeMillis();
@@ -2585,113 +2592,124 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::st
}
}
- if (!gArgs.GetArg("-addresstype", "").empty()) {
- std::optional<OutputType> parsed = ParseOutputType(gArgs.GetArg("-addresstype", ""));
+ if (!args.GetArg("-addresstype", "").empty()) {
+ std::optional<OutputType> parsed = ParseOutputType(args.GetArg("-addresstype", ""));
if (!parsed) {
- error = strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""));
+ error = strprintf(_("Unknown address type '%s'"), args.GetArg("-addresstype", ""));
return nullptr;
}
walletInstance->m_default_address_type = parsed.value();
}
- if (!gArgs.GetArg("-changetype", "").empty()) {
- std::optional<OutputType> parsed = ParseOutputType(gArgs.GetArg("-changetype", ""));
+ if (!args.GetArg("-changetype", "").empty()) {
+ std::optional<OutputType> parsed = ParseOutputType(args.GetArg("-changetype", ""));
if (!parsed) {
- error = strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""));
+ error = strprintf(_("Unknown change type '%s'"), args.GetArg("-changetype", ""));
return nullptr;
}
walletInstance->m_default_change_type = parsed.value();
}
- if (gArgs.IsArgSet("-mintxfee")) {
- CAmount n = 0;
- if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) {
- error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""));
+ if (args.IsArgSet("-mintxfee")) {
+ std::optional<CAmount> min_tx_fee = ParseMoney(args.GetArg("-mintxfee", ""));
+ if (!min_tx_fee || min_tx_fee.value() == 0) {
+ error = AmountErrMsg("mintxfee", args.GetArg("-mintxfee", ""));
return nullptr;
- }
- if (n > HIGH_TX_FEE_PER_KB) {
+ } else if (min_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
_("This is the minimum transaction fee you pay on every transaction."));
}
- walletInstance->m_min_fee = CFeeRate(n);
+
+ walletInstance->m_min_fee = CFeeRate{min_tx_fee.value()};
}
- if (gArgs.IsArgSet("-maxapsfee")) {
- const std::string max_aps_fee{gArgs.GetArg("-maxapsfee", "")};
- CAmount n = 0;
+ if (args.IsArgSet("-maxapsfee")) {
+ const std::string max_aps_fee{args.GetArg("-maxapsfee", "")};
if (max_aps_fee == "-1") {
- n = -1;
- } else if (!ParseMoney(max_aps_fee, n)) {
+ walletInstance->m_max_aps_fee = -1;
+ } else if (std::optional<CAmount> max_fee = ParseMoney(max_aps_fee)) {
+ if (max_fee.value() > HIGH_APS_FEE) {
+ warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
+ _("This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection."));
+ }
+ walletInstance->m_max_aps_fee = max_fee.value();
+ } else {
error = AmountErrMsg("maxapsfee", max_aps_fee);
return nullptr;
}
- if (n > HIGH_APS_FEE) {
- warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
- _("This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection."));
- }
- walletInstance->m_max_aps_fee = n;
}
- if (gArgs.IsArgSet("-fallbackfee")) {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
- error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""));
+ if (args.IsArgSet("-fallbackfee")) {
+ std::optional<CAmount> fallback_fee = ParseMoney(args.GetArg("-fallbackfee", ""));
+ if (!fallback_fee) {
+ error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), args.GetArg("-fallbackfee", ""));
return nullptr;
- }
- if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ } else if (fallback_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") +
_("This is the transaction fee you may pay when fee estimates are not available."));
}
- walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
+ walletInstance->m_fallback_fee = CFeeRate{fallback_fee.value()};
}
+
// Disable fallback fee in case value was set to 0, enable if non-null value
walletInstance->m_allow_fallback_fee = walletInstance->m_fallback_fee.GetFeePerK() != 0;
- if (gArgs.IsArgSet("-discardfee")) {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) {
- error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""));
+ if (args.IsArgSet("-discardfee")) {
+ std::optional<CAmount> discard_fee = ParseMoney(args.GetArg("-discardfee", ""));
+ if (!discard_fee) {
+ error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), args.GetArg("-discardfee", ""));
return nullptr;
- }
- if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ } else if (discard_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") +
_("This is the transaction fee you may discard if change is smaller than dust at this level"));
}
- walletInstance->m_discard_rate = CFeeRate(nFeePerK);
+ walletInstance->m_discard_rate = CFeeRate{discard_fee.value()};
}
- if (gArgs.IsArgSet("-paytxfee")) {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
- error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""));
+
+ if (args.IsArgSet("-paytxfee")) {
+ std::optional<CAmount> pay_tx_fee = ParseMoney(args.GetArg("-paytxfee", ""));
+ if (!pay_tx_fee) {
+ error = AmountErrMsg("paytxfee", args.GetArg("-paytxfee", ""));
return nullptr;
- }
- if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ } else if (pay_tx_fee.value() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
_("This is the transaction fee you will pay if you send a transaction."));
}
- walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
+
+ walletInstance->m_pay_tx_fee = CFeeRate{pay_tx_fee.value(), 1000};
+
if (chain && walletInstance->m_pay_tx_fee < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
- gArgs.GetArg("-paytxfee", ""), chain->relayMinFee().ToString());
+ args.GetArg("-paytxfee", ""), chain->relayMinFee().ToString());
return nullptr;
}
}
- if (gArgs.IsArgSet("-maxtxfee")) {
- CAmount nMaxFee = 0;
- if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
- error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""));
+ if (args.IsArgSet("-maxtxfee")) {
+ std::optional<CAmount> max_fee = ParseMoney(args.GetArg("-maxtxfee", ""));
+ if (!max_fee) {
+ error = AmountErrMsg("maxtxfee", args.GetArg("-maxtxfee", ""));
return nullptr;
- }
- if (nMaxFee > HIGH_MAX_TX_FEE) {
+ } else if (max_fee.value() > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
}
- if (chain && CFeeRate(nMaxFee, 1000) < chain->relayMinFee()) {
+
+ if (chain && CFeeRate{max_fee.value(), 1000} < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
- gArgs.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString());
+ args.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString());
+ return nullptr;
+ }
+
+ walletInstance->m_default_max_tx_fee = max_fee.value();
+ }
+
+ if (gArgs.IsArgSet("-consolidatefeerate")) {
+ if (std::optional<CAmount> consolidate_feerate = ParseMoney(gArgs.GetArg("-consolidatefeerate", ""))) {
+ walletInstance->m_consolidate_feerate = CFeeRate(*consolidate_feerate);
+ } else {
+ error = AmountErrMsg("consolidatefeerate", gArgs.GetArg("-consolidatefeerate", ""));
return nullptr;
}
- walletInstance->m_default_max_tx_fee = nMaxFee;
}
if (chain && chain->relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
@@ -2699,9 +2717,9 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::st
_("The wallet will avoid paying less than the minimum relay fee."));
}
- walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
- walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
- walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
+ walletInstance->m_confirm_target = args.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
+ walletInstance->m_spend_zero_conf_change = args.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
+ walletInstance->m_signal_rbf = args.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart);
@@ -2715,13 +2733,13 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::st
}
{
- LOCK(cs_wallets);
- for (auto& load_wallet : g_load_wallet_fns) {
- load_wallet(interfaces::MakeWallet(walletInstance));
+ LOCK(context.wallets_mutex);
+ for (auto& load_wallet : context.wallet_load_fns) {
+ load_wallet(interfaces::MakeWallet(context, walletInstance));
}
}
- walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
+ walletInstance->SetBroadcastTransactions(args.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
{
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 3997751f52..607af3efb0 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -42,6 +42,8 @@
#include <boost/signals2/signal.hpp>
+struct WalletContext;
+
using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>;
struct bilingual_str;
@@ -53,14 +55,14 @@ struct bilingual_str;
//! by the shared pointer deleter.
void UnloadWallet(std::shared_ptr<CWallet>&& wallet);
-bool AddWallet(const std::shared_ptr<CWallet>& wallet);
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings);
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start);
-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 std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
-std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
-std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet);
+bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
+bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings);
+bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start);
+std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context);
+std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name);
+std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
+std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
+std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet);
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
//! -paytxfee default
@@ -71,6 +73,8 @@ static const CAmount DEFAULT_FALLBACK_FEE = 0;
static const CAmount DEFAULT_DISCARD_FEE = 10000;
//! -mintxfee default
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
+//! -consolidatefeerate default
+static const CAmount DEFAULT_CONSOLIDATE_FEERATE{10000}; // 10 sat/vbyte
/**
* maximum fee increase allowed to do partial spend avoidance, even for nodes with this feature disabled by default
*
@@ -183,7 +187,7 @@ public:
}
//! Reserve an address
- bool GetReservedDestination(CTxDestination& pubkey, bool internal, std::string& error);
+ bool GetReservedDestination(CTxDestination& pubkey, bool internal, bilingual_str& error);
//! Return reserved address
void ReturnDestination();
//! Keep the address. Do not return it's key to the keypool when this object goes out of scope
@@ -563,7 +567,7 @@ public:
/** Fetch the inputs and sign with SIGHASH_ALL. */
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Sign the tx given the input coins and sighash. */
- bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const;
+ bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const;
/**
@@ -636,6 +640,12 @@ public:
* output itself, just drop it to fees. */
CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
+ /** When the actual feerate is less than the consolidate feerate, we will tend to make transactions which
+ * consolidate inputs. When the actual feerate is greater than the consolidate feerate, we will tend to make
+ * transactions which have the lowest fees.
+ */
+ CFeeRate m_consolidate_feerate{DEFAULT_CONSOLIDATE_FEERATE};
+
/** The maximum fee amount we're willing to pay to prioritize partial spend avoidance. */
CAmount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; //!< note: this is absolute fee, not fee rate
OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
@@ -665,8 +675,8 @@ public:
*/
void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error);
- bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error);
+ bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error);
+ bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error);
isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -772,7 +782,7 @@ public:
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
- static std::shared_ptr<CWallet> Create(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings);
+ static std::shared_ptr<CWallet> Create(WalletContext& context, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings);
/**
* Wallet post-init setup
@@ -919,7 +929,7 @@ public:
* Called periodically by the schedule thread. Prompts individual wallets to resend
* their transactions. Actual rebroadcast schedule is managed by the wallets themselves.
*/
-void MaybeResendWalletTxs();
+void MaybeResendWalletTxs(WalletContext& context);
/** RAII object to check and reserve a wallet rescan */
class WalletRescanReserver
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 1e5d8dfa3a..2fabe65a93 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1004,14 +1004,14 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u
return DBErrors::LOAD_OK;
}
-void MaybeCompactWalletDB()
+void MaybeCompactWalletDB(WalletContext& context)
{
static std::atomic<bool> fOneThread(false);
if (fOneThread.exchange(true)) {
return;
}
- for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
WalletDatabase& dbh = pwallet->GetDatabase();
unsigned int nUpdateCounter = dbh.nUpdateCounter;
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 9b775eb481..25c2ec5909 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -31,6 +31,7 @@
static const bool DEFAULT_FLUSHWALLET = true;
struct CBlockLocator;
+struct WalletContext;
class CKeyPool;
class CMasterKey;
class CScript;
@@ -279,7 +280,7 @@ private:
};
//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
-void MaybeCompactWalletDB();
+void MaybeCompactWalletDB(WalletContext& context);
//! Callback for filtering key types to deserialize in ReadKeyValue
using KeyFilterFn = std::function<bool(const std::string&)>;
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index 5fcecb4882..704dd6126b 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -31,8 +31,8 @@ ASMAP = '../../src/test/data/asmap.raw' # path to unit test skeleton asmap
VERSION = 'fec61fa21a9f46f3b17bdcd660d7f4cd90b966aad3aec593c99b35f0aca15853'
def expected_messages(filename):
- return ['Opened asmap file "{}" (59 bytes) from disk'.format(filename),
- 'Using asmap version {} for IP bucketing'.format(VERSION)]
+ return [f'Opened asmap file "{filename}" (59 bytes) from disk',
+ f'Using asmap version {VERSION} for IP bucketing']
class AsmapTest(BitcoinTestFramework):
def set_test_params(self):
@@ -50,7 +50,7 @@ class AsmapTest(BitcoinTestFramework):
filename = os.path.join(self.datadir, 'my-map-file.map')
shutil.copyfile(self.asmap_raw, filename)
with self.node.assert_debug_log(expected_messages(filename)):
- self.start_node(0, ['-asmap={}'.format(filename)])
+ self.start_node(0, [f'-asmap={filename}'])
os.remove(filename)
def test_asmap_with_relative_path(self):
@@ -60,13 +60,13 @@ class AsmapTest(BitcoinTestFramework):
filename = os.path.join(self.datadir, name)
shutil.copyfile(self.asmap_raw, filename)
with self.node.assert_debug_log(expected_messages(filename)):
- self.start_node(0, ['-asmap={}'.format(name)])
+ self.start_node(0, [f'-asmap={name}'])
os.remove(filename)
def test_default_asmap(self):
shutil.copyfile(self.asmap_raw, self.default_asmap)
for arg in ['-asmap', '-asmap=']:
- self.log.info('Test bitcoind {} (using default map file)'.format(arg))
+ self.log.info(f'Test bitcoind {arg} (using default map file)')
self.stop_node(0)
with self.node.assert_debug_log(expected_messages(self.default_asmap)):
self.start_node(0, [arg])
@@ -75,7 +75,7 @@ class AsmapTest(BitcoinTestFramework):
def test_default_asmap_with_missing_file(self):
self.log.info('Test bitcoind -asmap with missing default map file')
self.stop_node(0)
- msg = "Error: Could not find asmap file \"{}\"".format(self.default_asmap)
+ msg = f"Error: Could not find asmap file \"{self.default_asmap}\""
self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
def test_empty_asmap(self):
@@ -83,7 +83,7 @@ class AsmapTest(BitcoinTestFramework):
self.stop_node(0)
with open(self.default_asmap, "w", encoding="utf-8") as f:
f.write("")
- msg = "Error: Could not parse asmap file \"{}\"".format(self.default_asmap)
+ msg = f"Error: Could not parse asmap file \"{self.default_asmap}\""
self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
os.remove(self.default_asmap)
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index e0ba835f99..978080703e 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -366,7 +366,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert_equal(load_res['warning'], '')
wallet = node_master.get_wallet_rpc("u1_v16")
info = wallet.getaddressinfo(v16_addr)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + v16_pubkey + ")"
+ descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{v16_pubkey})"
assert_equal(info["desc"], descsum_create(descriptor))
# Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
@@ -389,7 +389,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
node_master.loadwallet("u1_v17")
wallet = node_master.get_wallet_rpc("u1_v17")
info = wallet.getaddressinfo(address)
- descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")"
+ descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{pubkey})"
assert_equal(info["desc"], descsum_create(descriptor))
# Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py
index 7bfad52c24..d3276e64ee 100755
--- a/test/functional/feature_blocksdir.py
+++ b/test/functional/feature_blocksdir.py
@@ -24,10 +24,10 @@ class BlocksdirTest(BitcoinTestFramework):
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))
+ self.nodes[0].assert_start_raises_init_error([f"-blocksdir={blocksdir_path}"], f'Error: Specified blocks directory "{blocksdir_path}" does not exist.')
os.mkdir(blocksdir_path)
self.log.info("Starting with existing blocksdir ...")
- self.start_node(0, ["-blocksdir=" + blocksdir_path])
+ self.start_node(0, [f"-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, self.chain, "blocks", "blk00000.dat"))
diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py
index 7c14f5d5a6..3438027db6 100755
--- a/test/functional/feature_cltv.py
+++ b/test/functional/feature_cltv.py
@@ -4,8 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP65 (CHECKLOCKTIMEVERIFY).
-Test that the CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height
-1351.
+Test that the CHECKLOCKTIMEVERIFY soft-fork activates.
"""
from test_framework.blocktools import (
@@ -62,9 +61,9 @@ def cltv_invalidate(tx, failure_reason):
# +-------------------------------------------------+------------+--------------+
[[OP_CHECKLOCKTIMEVERIFY], None, None],
[[OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP], None, None],
- [[CScriptNum(1000), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 1296688602], # timestamp of genesis block
- [[CScriptNum(1000), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 500],
- [[CScriptNum(500), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 500],
+ [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 1296688602], # timestamp of genesis block
+ [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 50],
+ [[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 50],
][failure_reason]
cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2])
@@ -105,6 +104,7 @@ class BIP65Test(BitcoinTestFramework):
self.log.info("Mining %d blocks", CLTV_HEIGHT - 2)
wallet.generate(10)
self.nodes[0].generate(CLTV_HEIGHT - 2 - 10)
+ assert_equal(self.nodes[0].getblockcount(), CLTV_HEIGHT - 2)
self.log.info("Test that invalid-according-to-CLTV transactions can still appear in a block")
@@ -135,7 +135,7 @@ class BIP65Test(BitcoinTestFramework):
block.nVersion = 3
block.solve()
- with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000003)'.format(block.hash)]):
+ with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000003)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
@@ -173,8 +173,7 @@ class BIP65Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
- with self.nodes[0].assert_debug_log(expected_msgs=['CheckInputScripts on {} failed with {}'.format(
- block.vtx[-1].hash, expected_cltv_reject_reason)]):
+ with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with {expected_cltv_reject_reason}']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 24c8a8987a..0daa0ba3d8 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -24,7 +24,7 @@ class ConfArgsTest(BitcoinTestFramework):
inc_conf_file_path = os.path.join(self.nodes[0].datadir, 'include.conf')
with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf:
- conf.write('includeconf={}\n'.format(inc_conf_file_path))
+ conf.write(f'includeconf={inc_conf_file_path}\n')
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli=1',
@@ -43,13 +43,13 @@ class ConfArgsTest(BitcoinTestFramework):
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 %s network when in [%s] section.' % (self.chain, self.chain))
+ self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: Config setting for -wallet only applied on {self.chain} network when in [{self.chain}] section.')
main_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'bitcoin_main.conf')
- util.write_config(main_conf_file_path, n=0, chain='', extra_config='includeconf={}\n'.format(inc_conf_file_path))
+ util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('acceptnonstdtxn=1\n')
- self.nodes[0].assert_start_raises_init_error(extra_args=["-conf={}".format(main_conf_file_path)], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
+ self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('nono\n')
@@ -69,14 +69,14 @@ class ConfArgsTest(BitcoinTestFramework):
inc_conf_file2_path = os.path.join(self.nodes[0].datadir, 'include2.conf')
with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf:
- conf.write('includeconf={}\n'.format(inc_conf_file2_path))
+ conf.write(f'includeconf={inc_conf_file2_path}\n')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('testnot.datadir=1\n')
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
conf.write('[testnet]\n')
self.restart_node(0)
- self.nodes[0].stop_node(expected_stderr='Warning: ' + inc_conf_file_path + ':1 Section [testnot] is not recognized.' + os.linesep + inc_conf_file2_path + ':1 Section [testnet] is not recognized.')
+ self.nodes[0].stop_node(expected_stderr=f'Warning: {inc_conf_file_path}:1 Section [testnot] is not recognized.{os.linesep}{inc_conf_file2_path}:1 Section [testnet] is not recognized.')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
@@ -105,8 +105,8 @@ class ConfArgsTest(BitcoinTestFramework):
'Command-line arg: rpcpassword=****',
'Command-line arg: rpcuser=****',
'Command-line arg: torpassword=****',
- 'Config file arg: %s="1"' % self.chain,
- 'Config file arg: [%s] server="1"' % self.chain,
+ f'Config file arg: {self.chain}="1"',
+ f'Config file arg: [{self.chain}] server="1"',
],
unexpected_msgs=[
'alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
@@ -235,7 +235,7 @@ class ConfArgsTest(BitcoinTestFramework):
# Check that using -datadir argument on non-existent directory fails
self.nodes[0].datadir = new_data_dir
- self.nodes[0].assert_start_raises_init_error(['-datadir=' + new_data_dir], 'Error: Specified data directory "' + new_data_dir + '" does not exist.')
+ self.nodes[0].assert_start_raises_init_error([f'-datadir={new_data_dir}'], f'Error: Specified data directory "{new_data_dir}" does not exist.')
# Check that using non-existent datadir in conf file fails
conf_file = os.path.join(default_data_dir, "bitcoin.conf")
@@ -243,21 +243,25 @@ class ConfArgsTest(BitcoinTestFramework):
# datadir needs to be set before [chain] section
conf_file_contents = open(conf_file, encoding='utf8').read()
with open(conf_file, 'w', encoding='utf8') as f:
- f.write("datadir=" + new_data_dir + "\n")
+ f.write(f"datadir={new_data_dir}\n")
f.write(conf_file_contents)
- self.nodes[0].assert_start_raises_init_error(['-conf=' + conf_file], 'Error: Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.')
+ self.nodes[0].assert_start_raises_init_error([f'-conf={conf_file}'], f'Error: Error reading configuration file: specified data directory "{new_data_dir}" does not exist.')
+
+ # Check that an explicitly specified config file that cannot be opened fails
+ none_existent_conf_file = os.path.join(default_data_dir, "none_existent_bitcoin.conf")
+ self.nodes[0].assert_start_raises_init_error(['-conf=' + none_existent_conf_file], 'Error: Error reading configuration file: specified config file "' + none_existent_conf_file + '" could not be opened.')
# Create the directory and ensure the config file now works
os.mkdir(new_data_dir)
- self.start_node(0, ['-conf='+conf_file])
+ self.start_node(0, [f'-conf={conf_file}'])
self.stop_node(0)
assert os.path.exists(os.path.join(new_data_dir, self.chain, 'blocks'))
# Ensure command line argument overrides datadir in conf
os.mkdir(new_data_dir_2)
self.nodes[0].datadir = new_data_dir_2
- self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file])
+ self.start_node(0, [f'-datadir={new_data_dir_2}', f'-conf={conf_file}'])
assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'blocks'))
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index 1ac1a0563f..bc0050d47a 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -247,7 +247,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
self.send_blocks(test_blocks)
assert_equal(self.tipheight, CSV_ACTIVATION_HEIGHT - 2)
- self.log.info("Height = {}, CSV not yet active (will activate for block {}, not {})".format(self.tipheight, CSV_ACTIVATION_HEIGHT, CSV_ACTIVATION_HEIGHT - 1))
+ self.log.info(f"Height = {self.tipheight}, CSV not yet active (will activate for block {CSV_ACTIVATION_HEIGHT}, not {CSV_ACTIVATION_HEIGHT - 1})")
assert not softfork_active(self.nodes[0], 'csv')
# Test both version 1 and version 2 transactions for all tests
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index 6d8e5430f8..f0766ca7c2 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -102,7 +102,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# perhaps we generated a test case that blew up our cache?
# TODO: If this happens a lot, we should try to restart without -dbcrashratio
# and make sure that recovery happens.
- raise AssertionError("Unable to successfully restart node %d in allotted time", node_index)
+ raise AssertionError(f"Unable to successfully restart node {node_index} in allotted time")
def submit_block_catch_error(self, node_index, block):
"""Try submitting a block to the given node.
@@ -114,10 +114,10 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.nodes[node_index].submitblock(block)
return True
except (http.client.CannotSendRequest, http.client.RemoteDisconnected) as e:
- self.log.debug("node %d submitblock raised exception: %s", node_index, e)
+ self.log.debug(f"node {node_index} submitblock raised exception: {e}")
return False
except OSError as e:
- self.log.debug("node %d submitblock raised OSError exception: errno=%s", node_index, e.errno)
+ self.log.debug(f"node {node_index} submitblock raised OSError exception: errno={e.errno}")
if e.errno in [errno.EPIPE, errno.ECONNREFUSED, errno.ECONNRESET]:
# The node has likely crashed
return False
@@ -142,15 +142,15 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# Deliver each block to each other node
for i in range(3):
nodei_utxo_hash = None
- self.log.debug("Syncing blocks to node %d", i)
+ self.log.debug(f"Syncing blocks to node {i}")
for (block_hash, block) in blocks:
# Get the block from node3, and submit to node_i
- self.log.debug("submitting block %s", block_hash)
+ self.log.debug(f"submitting block {block_hash}")
if not self.submit_block_catch_error(i, block):
# TODO: more carefully check that the crash is due to -dbcrashratio
# (change the exit code perhaps, and check that here?)
self.wait_for_node_exit(i, timeout=30)
- self.log.debug("Restarting node %d after block hash %s", i, block_hash)
+ self.log.debug(f"Restarting node {i} after block hash {block_hash}")
nodei_utxo_hash = self.restart_node(i, block_hash)
assert nodei_utxo_hash is not None
self.restart_counts[i] += 1
@@ -167,7 +167,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# - we only update the utxo cache after a node restart, since flushing
# the cache is a no-op at that point
if nodei_utxo_hash is not None:
- self.log.debug("Checking txoutsetinfo matches for node %d", i)
+ self.log.debug(f"Checking txoutsetinfo matches for node {i}")
assert_equal(nodei_utxo_hash, node3_utxo_hash)
def verify_utxo_hash(self):
@@ -217,15 +217,15 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# Start by creating a lot of utxos on node3
initial_height = self.nodes[3].getblockcount()
- utxo_list = create_confirmed_utxos(self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000)
- self.log.info("Prepped %d utxo entries", len(utxo_list))
+ utxo_list = create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000)
+ self.log.info(f"Prepped {len(utxo_list)} utxo entries")
# Sync these blocks with the other nodes
block_hashes_to_sync = []
for height in range(initial_height + 1, self.nodes[3].getblockcount() + 1):
block_hashes_to_sync.append(self.nodes[3].getblockhash(height))
- self.log.debug("Syncing %d blocks with other nodes", len(block_hashes_to_sync))
+ self.log.debug(f"Syncing {len(block_hashes_to_sync)} blocks with other nodes")
# Syncing the blocks could cause nodes to crash, so the test begins here.
self.sync_node3blocks(block_hashes_to_sync)
@@ -235,33 +235,34 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# each time through the loop, generate a bunch of transactions,
# and then either mine a single new block on the tip, or some-sized reorg.
for i in range(40):
- self.log.info("Iteration %d, generating 2500 transactions %s", i, self.restart_counts)
+ self.log.info(f"Iteration {i}, generating 2500 transactions {self.restart_counts}")
# Generate a bunch of small-ish transactions
self.generate_small_transactions(self.nodes[3], 2500, utxo_list)
# Pick a random block between current tip, and starting tip
current_height = self.nodes[3].getblockcount()
random_height = random.randint(starting_tip_height, current_height)
- self.log.debug("At height %d, considering height %d", current_height, random_height)
+ self.log.debug(f"At height {current_height}, considering height {random_height}")
if random_height > starting_tip_height:
# Randomly reorg from this point with some probability (1/4 for
# tip, 1/5 for tip-1, ...)
if random.random() < 1.0 / (current_height + 4 - random_height):
- self.log.debug("Invalidating block at height %d", random_height)
+ self.log.debug(f"Invalidating block at height {random_height}")
self.nodes[3].invalidateblock(self.nodes[3].getblockhash(random_height))
# Now generate new blocks until we pass the old tip height
self.log.debug("Mining longer tip")
block_hashes = []
while current_height + 1 > self.nodes[3].getblockcount():
- block_hashes.extend(self.nodes[3].generatetoaddress(
+ block_hashes.extend(self.generatetoaddress(
+ self.nodes[3],
nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()),
# new address to avoid mining a block that has just been invalidated
address=self.nodes[3].getnewaddress(),
))
- self.log.debug("Syncing %d new blocks...", len(block_hashes))
+ self.log.debug(f"Syncing {len(block_hashes)} new blocks...")
self.sync_node3blocks(block_hashes)
utxo_list = self.nodes[3].listunspent()
- self.log.debug("Node3 utxo count: %d", len(utxo_list))
+ self.log.debug(f"Node3 utxo count: {len(utxo_list)}")
# Check that the utxo hashes agree with node3
# Useful side effect: each utxo cache gets flushed here, so that we
@@ -269,7 +270,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.verify_utxo_hash()
# Check the test coverage
- self.log.info("Restarted nodes: %s; crashes on restart: %d", self.restart_counts, self.crashed_on_restart)
+ self.log.info(f"Restarted nodes: {self.restart_counts}; crashes on restart: {self.crashed_on_restart}")
# If no nodes were restarted, we didn't test anything.
assert self.restart_counts != [0, 0, 0]
@@ -280,7 +281,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.warning("Node %d never crashed during utxo flush!", i)
+ self.log.warning(f"Node {i} never crashed during utxo flush!")
if __name__ == "__main__":
diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py
index eb027c554a..fd46385cd4 100755
--- a/test/functional/feature_dersig.py
+++ b/test/functional/feature_dersig.py
@@ -4,10 +4,11 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP66 (DER SIG).
-Test that the DERSIG soft-fork activates at (regtest) height 1251.
+Test the DERSIG soft-fork activation on regtest.
"""
from test_framework.blocktools import (
+ DERSIG_HEIGHT,
create_block,
create_coinbase,
)
@@ -23,8 +24,6 @@ from test_framework.wallet import (
MiniWalletMode,
)
-DERSIG_HEIGHT = 1251
-
# A canonical signature consists of:
# <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
@@ -90,8 +89,10 @@ class BIP66Test(BitcoinTestFramework):
block.rehash()
block.solve()
+ assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 2)
self.test_dersig_info(is_active=False) # Not active as of current tip and next block does not need to obey rules
peer.send_and_ping(msg_block(block))
+ assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 1)
self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
@@ -103,7 +104,7 @@ class BIP66Test(BitcoinTestFramework):
block.rehash()
block.solve()
- with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000002)'.format(block.hash)]):
+ with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000002)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
@@ -133,7 +134,7 @@ class BIP66Test(BitcoinTestFramework):
block.rehash()
block.solve()
- with self.nodes[0].assert_debug_log(expected_msgs=['CheckInputScripts on {} failed with non-mandatory-script-verify-flag (Non-canonical DER signature)'.format(block.vtx[-1].hash)]):
+ with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with non-mandatory-script-verify-flag (Non-canonical DER signature)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index 5322b02414..a8f3e12e07 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -72,7 +72,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
if total_in <= amount + fee:
- raise RuntimeError("Insufficient funds: need %d, have %d" % (amount + fee, total_in))
+ raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1))
tx.vout.append(CTxOut(int(amount * COIN), P2SH_2))
# These transactions don't need to be signed, but we still have to insert
@@ -124,8 +124,7 @@ def check_raw_estimates(node, fees_seen):
assert_greater_than(feerate, 0)
if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):
- raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
- % (feerate, min(fees_seen), max(fees_seen)))
+ raise AssertionError(f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})")
def check_smart_estimates(node, fees_seen):
"""Call estimatesmartfee and verify that the estimates meet certain invariants."""
@@ -138,11 +137,9 @@ def check_smart_estimates(node, fees_seen):
assert_greater_than(feerate, 0)
if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):
- raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
- % (feerate, min(fees_seen), max(fees_seen)))
+ raise AssertionError(f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})")
if feerate - delta > last_feerate:
- raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
- % (feerate, last_feerate))
+ raise AssertionError(f"Estimated fee ({feerate}) larger than last fee ({last_feerate}) for lower number of confirms")
last_feerate = feerate
if i == 0:
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 2798d11b0a..e09107802b 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -22,11 +22,11 @@ class FilelockTest(BitcoinTestFramework):
def run_test(self):
datadir = os.path.join(self.nodes[0].datadir, self.chain)
- self.log.info("Using datadir {}".format(datadir))
+ self.log.info(f"Using datadir {datadir}")
self.log.info("Check that we can't start a second bitcoind instance using the same datadir")
- expected_msg = "Error: Cannot obtain a lock on data directory {0}. {1} is probably already running.".format(datadir, self.config['environment']['PACKAGE_NAME'])
- self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
+ expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['PACKAGE_NAME']} is probably already running."
+ self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir}', '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
def check_wallet_filelock(descriptors):
@@ -38,7 +38,7 @@ class FilelockTest(BitcoinTestFramework):
expected_msg = "Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
else:
expected_msg = "Error: Error initializing wallet database environment"
- self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+ self.nodes[1].assert_start_raises_init_error(extra_args=[f'-walletdir={wallet_dir}', f'-wallet={wallet_name}', '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
if self.is_bdb_compiled():
check_wallet_filelock(False)
diff --git a/test/functional/feature_help.py b/test/functional/feature_help.py
index babe9bfc80..837e95c128 100755
--- a/test/functional/feature_help.py
+++ b/test/functional/feature_help.py
@@ -40,14 +40,14 @@ class HelpTest(BitcoinTestFramework):
# Node should exit immediately and output help to stdout.
output, _ = self.get_node_output(ret_code_expected=0)
assert b'Options' in output
- self.log.info("Help text received: {} (...)".format(output[0:60]))
+ self.log.info(f"Help text received: {output[0:60]} (...)")
self.log.info("Start bitcoin with -version for version information")
self.nodes[0].start(extra_args=['-version'])
# Node should exit immediately and output version to stdout.
output, _ = self.get_node_output(ret_code_expected=0)
assert b'version' in output
- self.log.info("Version text received: {} (...)".format(output[0:60]))
+ self.log.info(f"Version text received: {output[0:60]} (...)")
# Test that arguments not in the help results in an error
self.log.info("Start bitcoind with -fakearg to make sure it does not start")
@@ -55,7 +55,7 @@ class HelpTest(BitcoinTestFramework):
# Node should exit immediately and output an error to stderr
_, output = self.get_node_output(ret_code_expected=1)
assert b'Error parsing command line arguments' in output
- self.log.info("Error message received: {} (...)".format(output[0:60]))
+ self.log.info(f"Error message received: {output[0:60]} (...)")
if __name__ == '__main__':
diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py
index 14f64d63a2..e5db6de085 100755
--- a/test/functional/feature_loadblock.py
+++ b/test/functional/feature_loadblock.py
@@ -45,17 +45,17 @@ class LoadblockTest(BitcoinTestFramework):
self.log.info("Create linearization config file")
with open(cfg_file, "a", encoding="utf-8") as cfg:
- cfg.write("datadir={}\n".format(data_dir))
- cfg.write("rpcuser={}\n".format(node_url.username))
- cfg.write("rpcpassword={}\n".format(node_url.password))
- cfg.write("port={}\n".format(node_url.port))
- cfg.write("host={}\n".format(node_url.hostname))
- cfg.write("output_file={}\n".format(bootstrap_file))
- cfg.write("max_height=100\n")
- cfg.write("netmagic=fabfb5da\n")
- cfg.write("input={}\n".format(blocks_dir))
- cfg.write("genesis={}\n".format(genesis_block))
- cfg.write("hashlist={}\n".format(hash_list.name))
+ cfg.write(f"datadir={data_dir}\n")
+ cfg.write(f"rpcuser={node_url.username}\n")
+ cfg.write(f"rpcpassword={node_url.password}\n")
+ cfg.write(f"port={node_url.port}\n")
+ cfg.write(f"host={node_url.hostname}\n")
+ cfg.write(f"output_file={bootstrap_file}\n")
+ cfg.write(f"max_height=100\n")
+ cfg.write(f"netmagic=fabfb5da\n")
+ cfg.write(f"input={blocks_dir}\n")
+ cfg.write(f"genesis={genesis_block}\n")
+ cfg.write(f"hashlist={hash_list.name}\n")
base_dir = self.config["environment"]["SRCDIR"]
linearize_dir = os.path.join(base_dir, "contrib", "linearize")
@@ -72,7 +72,7 @@ class LoadblockTest(BitcoinTestFramework):
check=True)
self.log.info("Restart second, unsynced node with bootstrap file")
- self.restart_node(1, extra_args=["-loadblock=" + bootstrap_file])
+ self.restart_node(1, extra_args=[f"-loadblock={bootstrap_file}"])
assert_equal(self.nodes[1].getblockcount(), 100) # start_node is blocking on all block files being imported
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 100)
diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py
index afcbcf099a..722219518a 100755
--- a/test/functional/feature_logging.py
+++ b/test/functional/feature_logging.py
@@ -29,7 +29,7 @@ class LoggingTest(BitcoinTestFramework):
# test alternative log file name outside datadir
tempname = os.path.join(self.options.tmpdir, "foo.log")
- self.restart_node(0, ["-debuglogfile=%s" % tempname])
+ self.restart_node(0, [f"-debuglogfile={tempname}"])
assert os.path.isfile(tempname)
# check that invalid log (relative) will cause error
@@ -37,26 +37,26 @@ class LoggingTest(BitcoinTestFramework):
invalidname = os.path.join("foo", "foo.log")
self.stop_node(0)
exp_stderr = r"Error: Could not open debug log file \S+$"
- self.nodes[0].assert_start_raises_init_error(["-debuglogfile=%s" % (invalidname)], exp_stderr, match=ErrorMatch.FULL_REGEX)
+ self.nodes[0].assert_start_raises_init_error([f"-debuglogfile={invalidname}"], exp_stderr, match=ErrorMatch.FULL_REGEX)
assert not os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (relative) works after path exists
self.stop_node(0)
os.mkdir(invdir)
- self.start_node(0, ["-debuglogfile=%s" % (invalidname)])
+ self.start_node(0, [f"-debuglogfile={invalidname}"])
assert os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (absolute) will cause error
self.stop_node(0)
invdir = os.path.join(self.options.tmpdir, "foo")
invalidname = os.path.join(invdir, "foo.log")
- self.nodes[0].assert_start_raises_init_error(["-debuglogfile=%s" % invalidname], exp_stderr, match=ErrorMatch.FULL_REGEX)
+ self.nodes[0].assert_start_raises_init_error([f"-debuglogfile={invalidname}"], exp_stderr, match=ErrorMatch.FULL_REGEX)
assert not os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (absolute) works after path exists
self.stop_node(0)
os.mkdir(invdir)
- self.start_node(0, ["-debuglogfile=%s" % (invalidname)])
+ self.start_node(0, [f"-debuglogfile={invalidname}"])
assert os.path.isfile(os.path.join(invdir, "foo.log"))
# check that -nodebuglogfile disables logging
@@ -67,7 +67,7 @@ class LoggingTest(BitcoinTestFramework):
assert not os.path.isfile(default_log_path)
# just sanity check no crash here
- self.restart_node(0, ["-debuglogfile=%s" % os.devnull])
+ self.restart_node(0, [f"-debuglogfile={os.devnull}"])
if __name__ == '__main__':
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index d0a94658ff..bd615997cb 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -67,7 +67,7 @@ class MaxUploadTest(BitcoinTestFramework):
p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn()))
# Now mine a big block
- mine_large_block(self.nodes[0], self.utxo_cache)
+ mine_large_block(self, self.nodes[0], self.utxo_cache)
# Store the hash; we'll request this later
big_old_block = self.nodes[0].getbestblockhash()
@@ -78,7 +78,7 @@ class MaxUploadTest(BitcoinTestFramework):
self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24)
# Mine one more block, so that the prior block looks old
- mine_large_block(self.nodes[0], self.utxo_cache)
+ mine_large_block(self, self.nodes[0], self.utxo_cache)
# We'll be requesting this new block too
big_new_block = self.nodes[0].getbestblockhash()
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index 803feb509a..3950690cf0 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -45,16 +45,16 @@ class MinimumChainWorkTest(BitcoinTestFramework):
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
# minchainwork is exceeded
starting_chain_work = REGTEST_WORK_PER_BLOCK # Genesis block's work
- self.log.info("Testing relay across node %d (minChainWork = %d)", 1, self.node_min_work[1])
+ self.log.info(f"Testing relay across node 1 (minChainWork = {self.node_min_work[1]})")
starting_blockcount = self.nodes[2].getblockcount()
num_blocks_to_generate = int((self.node_min_work[1] - starting_chain_work) / REGTEST_WORK_PER_BLOCK)
- self.log.info("Generating %d blocks on node0", num_blocks_to_generate)
+ self.log.info(f"Generating {num_blocks_to_generate} blocks on node0")
hashes = self.nodes[0].generatetoaddress(num_blocks_to_generate,
self.nodes[0].get_deterministic_priv_key().address)
- self.log.info("Node0 current chain work: %s", self.nodes[0].getblockheader(hashes[-1])['chainwork'])
+ self.log.info(f"Node0 current chain work: {self.nodes[0].getblockheader(hashes[-1])['chainwork']}")
# Sleep a few seconds and verify that node2 didn't get any new blocks
# or headers. We sleep, rather than sync_blocks(node0, node1) because
@@ -63,7 +63,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
time.sleep(3)
self.log.info("Verifying node 2 has no more blocks than before")
- self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
+ self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}")
# Node2 shouldn't have any new headers yet, because node1 should not
# have relayed anything.
assert_equal(len(self.nodes[2].getchaintips()), 1)
@@ -84,7 +84,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
# continue the test.
self.sync_all()
- self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
+ self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}")
if __name__ == '__main__':
MinimumChainWorkTest().main()
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index 6fc8773ee3..3f8fe9ccd5 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -20,7 +20,7 @@ FILE_CHARS_DISALLOWED = '/\\?%*:|"<>' if os.name == 'nt' else '/'
UNCONFIRMED_HASH_STRING = 'unconfirmed'
def notify_outputname(walletname, txid):
- return txid if os.name == 'nt' else '{}_{}'.format(walletname, txid)
+ return txid if os.name == 'nt' else f'{walletname}_{txid}'
class NotificationsTest(BitcoinTestFramework):
@@ -39,11 +39,11 @@ class NotificationsTest(BitcoinTestFramework):
# -alertnotify and -blocknotify on node0, walletnotify on node1
self.extra_args = [[
- "-alertnotify=echo > {}".format(os.path.join(self.alertnotify_dir, '%s')),
- "-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s')),
+ f"-alertnotify=echo > {os.path.join(self.alertnotify_dir, '%s')}",
+ f"-blocknotify=echo > {os.path.join(self.blocknotify_dir, '%s')}",
], [
"-rescan",
- "-walletnotify=echo %h_%b > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))),
+ f"-walletnotify=echo %h_%b > {os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))}",
]]
self.wallet_names = [self.default_wallet_name, self.wallet]
super().setup_network()
@@ -54,12 +54,12 @@ class NotificationsTest(BitcoinTestFramework):
seed = "cTdGmKFWpbvpKQ7ejrdzqYT2hhjyb3GPHnLAK7wdi5Em67YLwSm9"
xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
desc_imports = [{
- "desc": descsum_create("wpkh(" + xpriv + "/0/*)"),
+ "desc": descsum_create(f"wpkh({xpriv}/0/*)"),
"timestamp": 0,
"active": True,
"keypool": True,
},{
- "desc": descsum_create("wpkh(" + xpriv + "/1/*)"),
+ "desc": descsum_create(f"wpkh({xpriv}/1/*)"),
"timestamp": 0,
"active": True,
"keypool": True,
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index f467626801..678e103e5f 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -24,7 +24,10 @@ from test_framework.blocktools import (
from test_framework.messages import CTransaction
from test_framework.script import CScript
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_rpc_error
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)"
@@ -51,6 +54,7 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.extra_args = [[
f'-segwitheight={COINBASE_MATURITY + 5}',
'-addresstype=legacy',
+ '-par=1', # Use only one script thread to get the exact reject reason for testing
]]
def skip_test_if_missing_module(self):
@@ -86,7 +90,7 @@ class NULLDUMMYTest(BitcoinTestFramework):
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49))
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
- self.block_submit(self.nodes[0], test1txs, False, True)
+ self.block_submit(self.nodes[0], test1txs, accept=True)
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47)
@@ -94,28 +98,28 @@ class NULLDUMMYTest(BitcoinTestFramework):
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)
self.log.info(f"Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [{COINBASE_MATURITY + 4}]")
- self.block_submit(self.nodes[0], [test2tx], False, True)
+ self.block_submit(self.nodes[0], [test2tx], accept=True)
self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation")
test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46)
test6txs = [CTransaction(test4tx)]
trueDummy(test4tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
- self.block_submit(self.nodes[0], [test4tx])
+ self.block_submit(self.nodes[0], [test4tx], accept=False)
self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48)
test6txs.append(CTransaction(test5tx))
test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
- self.block_submit(self.nodes[0], [test5tx], True)
+ self.block_submit(self.nodes[0], [test5tx], with_witness=True, accept=False)
self.log.info(f"Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [{COINBASE_MATURITY + 5}]")
for i in test6txs:
self.nodes[0].sendrawtransaction(i.serialize_with_witness().hex(), 0)
- self.block_submit(self.nodes[0], test6txs, True, True)
+ self.block_submit(self.nodes[0], test6txs, with_witness=True, accept=True)
- def block_submit(self, node, txs, witness=False, accept=False):
+ def block_submit(self, node, txs, *, with_witness=False, accept):
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
assert_equal(tmpl['previousblockhash'], self.lastblockhash)
assert_equal(tmpl['height'], self.lastblockheight + 1)
@@ -124,11 +128,12 @@ class NULLDUMMYTest(BitcoinTestFramework):
tx.rehash()
block.vtx.append(tx)
block.hashMerkleRoot = block.calc_merkle_root()
- witness and add_witness_commitment(block)
+ if with_witness:
+ add_witness_commitment(block)
block.rehash()
block.solve()
- assert_equal(None if accept else 'block-validation-failed', node.submitblock(block.serialize().hex()))
- if (accept):
+ assert_equal(None if accept else NULLDUMMY_ERROR, node.submitblock(block.serialize().hex()))
+ if accept:
assert_equal(node.getbestblockhash(), block.hash)
self.lastblockhash = block.hash
self.lastblocktime += 1
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index 162814815e..2fb5e328f5 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -97,14 +97,14 @@ class ProxyTest(BitcoinTestFramework):
# Note: proxies are not used to connect to local nodes. This is because the proxy to
# use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
args = [
- ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
- ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),
- '-i2psam=%s:%i' % (self.i2p_sam), '-i2pacceptincoming=0', '-proxyrandomize=0'],
- ['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
+ ['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}','-proxyrandomize=1'],
+ ['-listen', f'-proxy={self.conf1.addr[0]}:{self.conf1.addr[1]}',f'-onion={self.conf2.addr[0]}:{self.conf2.addr[1]}',
+ f'-i2psam={self.i2p_sam[0]}:{self.i2p_sam[1]}', '-i2pacceptincoming=0', '-proxyrandomize=0'],
+ ['-listen', f'-proxy={self.conf2.addr[0]}:{self.conf2.addr[1]}','-proxyrandomize=1'],
[]
]
if self.have_ipv6:
- args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
+ args[3] = ['-listen', f'-proxy=[{self.conf3.addr[0]}]:{self.conf3.addr[1]}','-proxyrandomize=0', '-noonion']
self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes()
@@ -116,7 +116,7 @@ class ProxyTest(BitcoinTestFramework):
def node_test(self, node, proxies, auth, test_onion=True):
rv = []
addr = "15.61.23.23:1234"
- self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr))
+ self.log.debug(f"Test: outgoing IPv4 connection through node for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command)
@@ -132,7 +132,7 @@ class ProxyTest(BitcoinTestFramework):
if self.have_ipv6:
addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
- self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr))
+ self.log.debug(f"Test: outgoing IPv6 connection through node for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
@@ -148,7 +148,7 @@ class ProxyTest(BitcoinTestFramework):
if test_onion:
addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
- self.log.debug("Test: outgoing onion connection through node for address {}".format(addr))
+ self.log.debug(f"Test: outgoing onion connection through node for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
@@ -162,7 +162,7 @@ class ProxyTest(BitcoinTestFramework):
self.network_test(node, addr, network=NET_ONION)
addr = "node.noumenon:8333"
- self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr))
+ self.log.debug(f"Test: outgoing DNS name connection through node for address {addr}")
node.addnode(addr, "onetry")
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
@@ -218,12 +218,12 @@ class ProxyTest(BitcoinTestFramework):
n1 = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(NETWORKS, n1.keys())
for net in ['ipv4', 'ipv6']:
- assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
+ assert_equal(n1[net]['proxy'], f'{self.conf1.addr[0]}:{self.conf1.addr[1]}')
assert_equal(n1[net]['proxy_randomize_credentials'], False)
- assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
+ assert_equal(n1['onion']['proxy'], f'{self.conf2.addr[0]}:{self.conf2.addr[1]}')
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['reachable'], True)
- assert_equal(n1['i2p']['proxy'], '%s:%i' % (self.i2p_sam))
+ assert_equal(n1['i2p']['proxy'], f'{self.i2p_sam[0]}:{self.i2p_sam[1]}')
assert_equal(n1['i2p']['proxy_randomize_credentials'], False)
assert_equal(n1['i2p']['reachable'], True)
@@ -234,7 +234,7 @@ class ProxyTest(BitcoinTestFramework):
expected_proxy = ''
expected_randomize = False
else:
- expected_proxy = '%s:%i' % (self.conf2.addr)
+ expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
@@ -248,7 +248,7 @@ class ProxyTest(BitcoinTestFramework):
if net == NET_I2P:
expected_proxy = ''
else:
- expected_proxy = '[%s]:%i' % (self.conf3.addr)
+ expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index cedb7b57ca..2f0868e733 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -127,10 +127,28 @@ class PruneTest(BitcoinTestFramework):
self.sync_blocks(self.nodes[0:5])
+ def test_invalid_command_line_options(self):
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: Prune cannot be configured with a negative value.',
+ extra_args=['-prune=-1'],
+ )
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: Prune configured below the minimum of 550 MiB. Please use a higher number.',
+ extra_args=['-prune=549'],
+ )
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: Prune mode is incompatible with -txindex.',
+ extra_args=['-prune=550', '-txindex'],
+ )
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: Prune mode is incompatible with -coinstatsindex.',
+ extra_args=['-prune=550', '-coinstatsindex'],
+ )
+
def test_height_min(self):
assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early"
self.log.info("Success")
- self.log.info("Though we're already using more than 550MiB, current usage: %d" % calc_usage(self.prunedir))
+ self.log.info(f"Though we're already using more than 550MiB, current usage: {calc_usage(self.prunedir)}")
self.log.info("Mining 25 more blocks should cause the first block file to be pruned")
# Pruning doesn't run until we're allocating another chunk, 20 full blocks past the height cutoff will ensure this
mine_large_blocks(self.nodes[0], 25)
@@ -140,7 +158,7 @@ class PruneTest(BitcoinTestFramework):
self.log.info("Success")
usage = calc_usage(self.prunedir)
- self.log.info("Usage should be below target: %d" % usage)
+ self.log.info(f"Usage should be below target: {usage}")
assert_greater_than(550, usage)
def create_chain_with_staleblocks(self):
@@ -163,18 +181,18 @@ class PruneTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
self.sync_blocks(self.nodes[0:3])
- self.log.info("Usage can be over target because of high stale rate: %d" % calc_usage(self.prunedir))
+ self.log.info(f"Usage can be over target because of high stale rate: {calc_usage(self.prunedir)}")
def reorg_test(self):
# Node 1 will mine a 300 block chain starting 287 blocks back from Node 0 and Node 2's tip
# This will cause Node 2 to do a reorg requiring 288 blocks of undo data to the reorg_test chain
height = self.nodes[1].getblockcount()
- self.log.info("Current block height: %d" % height)
+ self.log.info(f"Current block height: {height}")
self.forkheight = height - 287
self.forkhash = self.nodes[1].getblockhash(self.forkheight)
- self.log.info("Invalidating block %s at height %d" % (self.forkhash, self.forkheight))
+ self.log.info(f"Invalidating block {self.forkhash} at height {self.forkheight}")
self.nodes[1].invalidateblock(self.forkhash)
# We've now switched to our previously mined-24 block fork on node 1, but that's not what we want
@@ -186,7 +204,7 @@ class PruneTest(BitcoinTestFramework):
curhash = self.nodes[1].getblockhash(self.forkheight - 1)
assert self.nodes[1].getblockcount() == self.forkheight - 1
- self.log.info("New best height: %d" % self.nodes[1].getblockcount())
+ self.log.info(f"New best height: {self.nodes[1].getblockcount()}")
# Disconnect node1 and generate the new chain
self.disconnect_nodes(0, 1)
@@ -200,8 +218,8 @@ class PruneTest(BitcoinTestFramework):
self.connect_nodes(1, 2)
self.sync_blocks(self.nodes[0:3], timeout=120)
- self.log.info("Verify height on node 2: %d" % self.nodes[2].getblockcount())
- self.log.info("Usage possibly still high because of stale blocks in block files: %d" % calc_usage(self.prunedir))
+ self.log.info(f"Verify height on node 2: {self.nodes[2].getblockcount()}")
+ self.log.info(f"Usage possibly still high because of stale blocks in block files: {calc_usage(self.prunedir)}")
self.log.info("Mine 220 more large blocks so we have requisite history")
@@ -209,7 +227,7 @@ class PruneTest(BitcoinTestFramework):
self.sync_blocks(self.nodes[0:3], timeout=120)
usage = calc_usage(self.prunedir)
- self.log.info("Usage should be below target: %d" % usage)
+ self.log.info(f"Usage should be below target: {usage}")
assert_greater_than(550, usage)
def reorg_back(self):
@@ -217,7 +235,7 @@ class PruneTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash)
with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']):
self.nodes[2].verifychain(checklevel=4, nblocks=0)
- self.log.info("Will need to redownload block %d" % self.forkheight)
+ self.log.info(f"Will need to redownload block {self.forkheight}")
# Verify that we have enough history to reorg back to the fork point
# Although this is more than 288 blocks, because this chain was written more recently
@@ -241,7 +259,7 @@ class PruneTest(BitcoinTestFramework):
# At this point node 2 is within 288 blocks of the fork point so it will preserve its ability to reorg
if self.nodes[2].getblockcount() < self.mainchainheight:
blocks_to_mine = first_reorg_height + 1 - self.mainchainheight
- self.log.info("Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: %d" % blocks_to_mine)
+ self.log.info(f"Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: {blocks_to_mine}")
self.nodes[0].invalidateblock(curchainhash)
assert_equal(self.nodes[0].getblockcount(), self.mainchainheight)
assert_equal(self.nodes[0].getbestblockhash(), self.mainchainhash2)
@@ -278,7 +296,7 @@ class PruneTest(BitcoinTestFramework):
assert_equal(ret, node.getblockchaininfo()['pruneheight'])
def has_block(index):
- return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", "blk{:05}.dat".format(index)))
+ return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", f"blk{index:05}.dat"))
# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
@@ -453,6 +471,9 @@ class PruneTest(BitcoinTestFramework):
self.log.info("Test wallet re-scan")
self.wallet_test()
+ self.log.info("Test invalid pruning command line options")
+ self.test_invalid_command_line_options()
+
self.log.info("Done")
if __name__ == '__main__':
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 65929704eb..694cca15fd 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -23,49 +23,6 @@ from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIP
from test_framework.wallet import MiniWallet
MAX_REPLACEMENT_LIMIT = 100
-
-
-def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
- """Create a txout with a given amount and scriptPubKey
-
- Mines coins as needed.
-
- confirmed - txouts created will be confirmed in the blockchain;
- unconfirmed otherwise.
- """
- fee = 1 * COIN
- while node.getbalance() < satoshi_round((amount + fee) / COIN):
- node.generate(COINBASE_MATURITY)
-
- new_addr = node.getnewaddress()
- txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN))
- tx1 = node.getrawtransaction(txid, 1)
- txid = int(txid, 16)
- i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout'])))
-
- tx2 = CTransaction()
- tx2.vin = [CTxIn(COutPoint(txid, i))]
- tx2.vout = [CTxOut(amount, scriptPubKey)]
- tx2.rehash()
-
- signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex())
-
- txid = node.sendrawtransaction(signed_tx['hex'], 0)
-
- # If requested, ensure txouts are confirmed.
- if confirmed:
- mempool_size = len(node.getrawmempool())
- while mempool_size > 0:
- node.generate(1)
- new_size = len(node.getrawmempool())
- # Error out if we have something stuck in the mempool, as this
- # would likely be a bug.
- assert new_size < mempool_size
- mempool_size = new_size
-
- return COutPoint(int(txid, 16), 0)
-
-
class ReplaceByFeeTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -129,6 +86,46 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Passed")
+ def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
+ """Create a txout with a given amount and scriptPubKey
+
+ Mines coins as needed.
+
+ confirmed - txouts created will be confirmed in the blockchain;
+ unconfirmed otherwise.
+ """
+ fee = 1 * COIN
+ while node.getbalance() < satoshi_round((amount + fee) / COIN):
+ self.generate(node, COINBASE_MATURITY)
+
+ new_addr = node.getnewaddress()
+ txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN))
+ tx1 = node.getrawtransaction(txid, 1)
+ txid = int(txid, 16)
+ i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout'])))
+
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(COutPoint(txid, i))]
+ tx2.vout = [CTxOut(amount, scriptPubKey)]
+ tx2.rehash()
+
+ signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex())
+
+ txid = node.sendrawtransaction(signed_tx['hex'], 0)
+
+ # If requested, ensure txouts are confirmed.
+ if confirmed:
+ mempool_size = len(node.getrawmempool())
+ while mempool_size > 0:
+ self.generate(node, 1)
+ new_size = len(node.getrawmempool())
+ # Error out if we have something stuck in the mempool, as this
+ # would likely be a bug.
+ assert new_size < mempool_size
+ mempool_size = new_size
+
+ return COutPoint(int(txid, 16), 0)
+
def test_simple_doublespend(self):
"""Simple doublespend"""
# we use MiniWallet to create a transaction template with inputs correctly set,
@@ -165,7 +162,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
"""Doublespend of a long chain"""
initial_nValue = 50 * COIN
- tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+ tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
prevout = tx0_outpoint
remaining_value = initial_nValue
@@ -205,7 +202,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
"""Doublespend of a big tree of transactions"""
initial_nValue = 50 * COIN
- tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+ tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001 * COIN, _total_txs=None):
if _total_txs is None:
@@ -268,7 +265,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# double-spent at once" anti-DoS limit.
for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2):
fee = int(0.0001 * COIN)
- tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+ tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
@@ -285,7 +282,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
+ tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -305,8 +302,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_spends_of_conflicting_outputs(self):
"""Replacements that spend conflicting tx outputs are rejected"""
- utxo1 = make_utxo(self.nodes[0], int(1.2 * COIN))
- utxo2 = make_utxo(self.nodes[0], 3 * COIN)
+ utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN))
+ utxo2 = self.make_utxo(self.nodes[0], 3 * COIN)
tx1a = CTransaction()
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
@@ -345,8 +342,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
- confirmed_utxo = make_utxo(self.nodes[0], int(1.1 * COIN))
- unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1 * COIN), False)
+ confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN))
+ unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), False)
tx1 = CTransaction()
tx1.vin = [CTxIn(confirmed_utxo)]
@@ -369,7 +366,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Start by creating a single transaction with many outputs
initial_nValue = 10 * COIN
- utxo = make_utxo(self.nodes[0], initial_nValue)
+ utxo = self.make_utxo(self.nodes[0], initial_nValue)
fee = int(0.0001 * COIN)
split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
@@ -417,7 +414,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_opt_in(self):
"""Replacing should only work if orig tx opted in"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
+ tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
tx1a = CTransaction()
@@ -438,7 +435,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
+ tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
tx2a = CTransaction()
@@ -494,7 +491,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# correctly used by replacement logic
# 1. Check that feeperkb uses modified fees
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
+ tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -520,7 +517,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert tx1b_txid in self.nodes[0].getrawmempool()
# 2. Check that absolute fee checks use modified fee.
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
+ tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx2a = CTransaction()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index cbd8521499..6f1cecce58 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -65,7 +65,7 @@ def find_spendable_utxo(node, min_value):
if utxo['spendable']:
return utxo
- raise AssertionError("Unspent output equal or higher than %s not found" % min_value)
+ raise AssertionError(f"Unspent output equal or higher than {min_value} not found")
txs_mined = {} # txindex from txid to blockhash
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
index 5a0236401d..26048d37f6 100755
--- a/test/functional/feature_settings.py
+++ b/test/functional/feature_settings.py
@@ -83,7 +83,7 @@ class SettingsTest(BitcoinTestFramework):
with altsettings.open("w") as fp:
fp.write('{"key": "value"}')
with node.assert_debug_log(expected_msgs=['Setting file arg: key = "value"']):
- self.start_node(0, extra_args=["-settings={}".format(altsettings)])
+ self.start_node(0, extra_args=[f"-settings={altsettings}"])
self.stop_node(0)
diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py
index 1c9e237d78..a7ec5c48e3 100755
--- a/test/functional/feature_versionbits_warning.py
+++ b/test/functional/feature_versionbits_warning.py
@@ -21,7 +21,7 @@ VB_TOP_BITS = 0x20000000
VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment
VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT)
-WARN_UNKNOWN_RULES_ACTIVE = "Unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT)
+WARN_UNKNOWN_RULES_ACTIVE = f"Unknown new rules activated (versionbit {VB_UNKNOWN_BIT})"
VB_PATTERN = re.compile("Unknown new rules activated.*versionbit")
class VersionBitsWarningTest(BitcoinTestFramework):
@@ -34,7 +34,7 @@ class VersionBitsWarningTest(BitcoinTestFramework):
# Open and close to create zero-length file
with open(self.alert_filename, 'w', encoding='utf8'):
pass
- self.extra_args = [["-alertnotify=echo %s >> \"" + self.alert_filename + "\""]]
+ self.extra_args = [[f"-alertnotify=echo %s >> \"{self.alert_filename}\""]]
self.setup_nodes()
def send_blocks_with_version(self, peer, numblocks, version):
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index dfa448a1a8..4b5363ec49 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -87,12 +87,12 @@ class TestBitcoinCli(BitcoinTestFramework):
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
self.log.info("Test -stdinrpcpass option")
- assert_equal(BLOCKS, self.nodes[0].cli('-rpcuser={}'.format(user), '-stdinrpcpass', input=password).getblockcount())
- assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli('-rpcuser={}'.format(user), '-stdinrpcpass', input='foo').echo)
+ assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input=password).getblockcount())
+ assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input='foo').echo)
self.log.info("Test -stdin and -stdinrpcpass")
- assert_equal(['foo', 'bar'], self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input=password + '\nfoo\nbar').echo())
- assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input='foo').echo)
+ assert_equal(['foo', 'bar'], self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input=f'{password}\nfoo\nbar').echo())
+ assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input='foo').echo)
self.log.info("Test connecting to a non-existing server")
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
@@ -150,8 +150,8 @@ class TestBitcoinCli(BitcoinTestFramework):
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
- rpcwallet2 = '-rpcwallet={}'.format(wallets[1])
- rpcwallet3 = '-rpcwallet={}'.format(wallets[2])
+ rpcwallet2 = f'-rpcwallet={wallets[1]}'
+ rpcwallet3 = f'-rpcwallet={wallets[2]}'
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
@@ -162,7 +162,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
for i in range(len(wallets)):
- cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli()
+ cli_get_info_string = self.nodes[0].cli('-getinfo', f'-rpcwallet={wallets[i]}').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balances' not in cli_get_info_string
assert_equal(cli_get_info["Wallet"], wallets[i])
@@ -296,7 +296,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.log.info("Test -version with node stopped")
self.stop_node(0)
cli_response = self.nodes[0].cli('-version').send_cli()
- assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response
+ assert f"{self.config['environment']['PACKAGE_NAME']} RPC client version" in cli_response
self.log.info("Test -rpcwait option successfully waits for RPC connection")
self.nodes[0].start() # start node without RPC connection
diff --git a/test/functional/interface_http.py b/test/functional/interface_http.py
index d007490f80..075224c011 100755
--- a/test/functional/interface_http.py
+++ b/test/functional/interface_http.py
@@ -24,8 +24,8 @@ class HTTPBasicsTest (BitcoinTestFramework):
# lowlevel check for http persistent connection #
#################################################
url = urllib.parse.urlparse(self.nodes[0].url)
- authpair = url.username + ':' + url.password
- headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+ authpair = f'{url.username}:{url.password}'
+ headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
@@ -42,7 +42,7 @@ class HTTPBasicsTest (BitcoinTestFramework):
conn.close()
#same should be if we add keep-alive because this should be the std. behaviour
- headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection": "keep-alive"}
+ headers = {"Authorization": f"Basic {str_to_b64str(authpair)}", "Connection": "keep-alive"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
@@ -59,7 +59,7 @@ class HTTPBasicsTest (BitcoinTestFramework):
conn.close()
#now do the same with "Connection: close"
- headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection":"close"}
+ headers = {"Authorization": f"Basic {str_to_b64str(authpair)}", "Connection":"close"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
@@ -70,8 +70,8 @@ class HTTPBasicsTest (BitcoinTestFramework):
#node1 (2nd node) is running with disabled keep-alive option
urlNode1 = urllib.parse.urlparse(self.nodes[1].url)
- authpair = urlNode1.username + ':' + urlNode1.password
- headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+ authpair = f'{urlNode1.username}:{urlNode1.password}'
+ headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(urlNode1.hostname, urlNode1.port)
conn.connect()
@@ -81,8 +81,8 @@ class HTTPBasicsTest (BitcoinTestFramework):
#node2 (third node) is running with standard keep-alive parameters which means keep-alive is on
urlNode2 = urllib.parse.urlparse(self.nodes[2].url)
- authpair = urlNode2.username + ':' + urlNode2.password
- headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+ authpair = f'{urlNode2.username}:{urlNode2.password}'
+ headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
@@ -94,13 +94,13 @@ class HTTPBasicsTest (BitcoinTestFramework):
# Check excessive request size
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
- conn.request('GET', '/' + ('x'*1000), '', headers)
+ conn.request('GET', f'/{"x"*1000}', '', headers)
out1 = conn.getresponse()
assert_equal(out1.status, http.client.NOT_FOUND)
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
- conn.request('GET', '/' + ('x'*10000), '', headers)
+ conn.request('GET', f'/{"x"*10000}', '', headers)
out1 = conn.getresponse()
assert_equal(out1.status, http.client.BAD_REQUEST)
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index 0cd6a7b0c6..47c95f5673 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the REST API."""
-import binascii
from decimal import Decimal
from enum import Enum
from io import BytesIO
@@ -58,7 +57,7 @@ class RESTTest (BitcoinTestFramework):
rest_uri += '.hex'
conn = http.client.HTTPConnection(self.url.hostname, self.url.port)
- self.log.debug('%s %s %s', http_method, rest_uri, body)
+ self.log.debug(f'{http_method} {rest_uri} {body}')
if http_method == 'GET':
conn.request('GET', rest_uri)
elif http_method == 'POST':
@@ -93,11 +92,11 @@ class RESTTest (BitcoinTestFramework):
self.log.info("Test the /tx URI")
- json_obj = self.test_rest_request("/tx/{}".format(txid))
+ json_obj = self.test_rest_request(f"/tx/{txid}")
assert_equal(json_obj['txid'], txid)
# Check hex format response
- hex_response = self.test_rest_request("/tx/{}".format(txid), req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ hex_response = self.test_rest_request(f"/tx/{txid}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than_or_equal(int(hex_response.getheader('content-length')),
json_obj['size']*2)
@@ -115,7 +114,7 @@ class RESTTest (BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), Decimal("0.1"))
# Check chainTip response
- json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending))
+ json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(json_obj['chaintipHash'], bb_hash)
# Make sure there is one utxo
@@ -124,7 +123,7 @@ class RESTTest (BitcoinTestFramework):
self.log.info("Query a spent TXO using the /getutxos URI")
- json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spent))
+ json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")
# Check chainTip response
assert_equal(json_obj['chaintipHash'], bb_hash)
@@ -137,7 +136,7 @@ class RESTTest (BitcoinTestFramework):
self.log.info("Query two TXOs using the /getutxos URI")
- json_obj = self.test_rest_request("/getutxos/{}-{}/{}-{}".format(*(spending + spent)))
+ json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 1)
assert_equal(json_obj['bitmap'], "10")
@@ -164,32 +163,32 @@ class RESTTest (BitcoinTestFramework):
# do a tx and don't sync
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
- json_obj = self.test_rest_request("/tx/{}".format(txid))
+ json_obj = self.test_rest_request(f"/tx/{txid}")
# get the spent output to later check for utxo (should be spent by then)
spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout'])
# get n of 0.1 outpoint
n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1'))
spending = (txid, n)
- json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending))
+ json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 0)
- json_obj = self.test_rest_request("/getutxos/checkmempool/{}-{}".format(*spending))
+ json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
- json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spent))
+ json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 1)
- json_obj = self.test_rest_request("/getutxos/checkmempool/{}-{}".format(*spent))
+ json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 0)
self.nodes[0].generate(1)
self.sync_all()
- json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending))
+ json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
- json_obj = self.test_rest_request("/getutxos/checkmempool/{}-{}".format(*spending))
+ json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
# Do some invalid requests
@@ -198,11 +197,11 @@ class RESTTest (BitcoinTestFramework):
self.test_rest_request("/getutxos/checkmempool", http_method='POST', req_type=ReqType.JSON, status=400, ret_type=RetType.OBJ)
# Test limits
- long_uri = '/'.join(["{}-{}".format(txid, n_) for n_ in range(20)])
- self.test_rest_request("/getutxos/checkmempool/{}".format(long_uri), http_method='POST', status=400, ret_type=RetType.OBJ)
+ long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)])
+ self.test_rest_request(f"/getutxos/checkmempool/{long_uri}", http_method='POST', status=400, ret_type=RetType.OBJ)
- long_uri = '/'.join(['{}-{}'.format(txid, n_) for n_ in range(15)])
- self.test_rest_request("/getutxos/checkmempool/{}".format(long_uri), http_method='POST', status=200)
+ long_uri = '/'.join([f'{txid}-{n_}' for n_ in range(15)])
+ self.test_rest_request(f"/getutxos/checkmempool/{long_uri}", http_method='POST', status=200)
self.nodes[0].generate(1) # generate block to not affect upcoming tests
self.sync_all()
@@ -216,42 +215,42 @@ class RESTTest (BitcoinTestFramework):
# Check result if block is not in the active chain
self.nodes[0].invalidateblock(bb_hash)
- assert_equal(self.test_rest_request('/headers/1/{}'.format(bb_hash)), [])
- self.test_rest_request('/block/{}'.format(bb_hash))
+ assert_equal(self.test_rest_request(f'/headers/1/{bb_hash}'), [])
+ self.test_rest_request(f'/block/{bb_hash}')
self.nodes[0].reconsiderblock(bb_hash)
# Check binary format
- response = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ)
+ response = self.test_rest_request(f"/block/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ)
assert_greater_than(int(response.getheader('content-length')), BLOCK_HEADER_SIZE)
response_bytes = response.read()
# Compare with block header
- response_header = self.test_rest_request("/headers/1/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ)
+ response_header = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ)
assert_equal(int(response_header.getheader('content-length')), BLOCK_HEADER_SIZE)
response_header_bytes = response_header.read()
assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes)
# Check block hex format
- response_hex = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ response_hex = self.test_rest_request(f"/block/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than(int(response_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2)
response_hex_bytes = response_hex.read().strip(b'\n')
- assert_equal(binascii.hexlify(response_bytes), response_hex_bytes)
+ assert_equal(response_bytes.hex().encode(), response_hex_bytes)
# Compare with hex block header
- response_header_hex = self.test_rest_request("/headers/1/{}".format(bb_hash), req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ response_header_hex = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than(int(response_header_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2)
response_header_hex_bytes = response_header_hex.read(BLOCK_HEADER_SIZE*2)
- assert_equal(binascii.hexlify(response_bytes[:BLOCK_HEADER_SIZE]), response_header_hex_bytes)
+ assert_equal(response_bytes[:BLOCK_HEADER_SIZE].hex().encode(), response_header_hex_bytes)
# Check json format
- block_json_obj = self.test_rest_request("/block/{}".format(bb_hash))
+ block_json_obj = self.test_rest_request(f"/block/{bb_hash}")
assert_equal(block_json_obj['hash'], bb_hash)
- assert_equal(self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']))['blockhash'], bb_hash)
+ assert_equal(self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}")['blockhash'], bb_hash)
# Check hex/bin format
- resp_hex = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ resp_hex = self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_equal(resp_hex.read().decode('utf-8').rstrip(), bb_hash)
- resp_bytes = self.test_rest_request("/blockhashbyheight/{}".format(block_json_obj['height']), req_type=ReqType.BIN, ret_type=RetType.BYTES)
+ resp_bytes = self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}", req_type=ReqType.BIN, ret_type=RetType.BYTES)
blockhash = resp_bytes[::-1].hex()
assert_equal(blockhash, bb_hash)
@@ -265,7 +264,7 @@ class RESTTest (BitcoinTestFramework):
self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400)
# Compare with json block header
- json_obj = self.test_rest_request("/headers/1/{}".format(bb_hash))
+ json_obj = self.test_rest_request(f"/headers/1/{bb_hash}")
assert_equal(len(json_obj), 1) # ensure that there is one header in the json response
assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same
@@ -277,7 +276,7 @@ class RESTTest (BitcoinTestFramework):
# See if we can get 5 headers in one response
self.nodes[1].generate(5)
self.sync_all()
- json_obj = self.test_rest_request("/headers/5/{}".format(bb_hash))
+ json_obj = self.test_rest_request(f"/headers/5/{bb_hash}")
assert_equal(len(json_obj), 5) # now we should have 5 header objects
self.log.info("Test tx inclusion in the /mempool and /block URIs")
@@ -307,13 +306,13 @@ class RESTTest (BitcoinTestFramework):
self.sync_all()
# Check if the 3 tx show up in the new block
- json_obj = self.test_rest_request("/block/{}".format(newblockhash[0]))
+ json_obj = self.test_rest_request(f"/block/{newblockhash[0]}")
non_coinbase_txs = {tx['txid'] for tx in json_obj['tx']
if 'coinbase' not in tx['vin'][0]}
assert_equal(non_coinbase_txs, set(txs))
# Check the same but without tx details
- json_obj = self.test_rest_request("/block/notxdetails/{}".format(newblockhash[0]))
+ json_obj = self.test_rest_request(f"/block/notxdetails/{newblockhash[0]}")
for tx in txs:
assert tx in json_obj['tx']
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 4d5666f414..89a7d29b24 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -16,7 +16,7 @@ def expect_http_status(expected_http_status, expected_rpc_code,
fcn, *args):
try:
fcn(*args)
- raise AssertionError("Expected RPC error %d, got none" % expected_rpc_code)
+ raise AssertionError(f"Expected RPC error {expected_rpc_code}, got none")
except JSONRPCException as exc:
assert_equal(exc.error["code"], expected_rpc_code)
assert_equal(exc.http_status, expected_http_status)
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 15f352d68c..6686f77a44 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -82,8 +82,8 @@ class ZMQTestSetupBlock:
raw transaction data.
"""
- def __init__(self, node):
- self.block_hash = node.generate(1)[0]
+ def __init__(self, test_framework, node):
+ self.block_hash = test_framework.generate(node, 1)[0]
coinbase = node.getblock(self.block_hash, 2)['tx'][0]
self.tx_hash = coinbase['txid']
self.raw_tx = coinbase['hex']
@@ -132,7 +132,7 @@ class ZMQTest (BitcoinTestFramework):
socket = self.ctx.socket(zmq.SUB)
subscribers.append(ZMQSubscriber(socket, topic.encode()))
- self.restart_node(0, ["-zmqpub%s=%s" % (topic, address) for topic, address in services] +
+ self.restart_node(0, [f"-zmqpub{topic}={address}" for topic, address in services] +
self.extra_args[0])
for i, sub in enumerate(subscribers):
@@ -147,7 +147,7 @@ class ZMQTest (BitcoinTestFramework):
for sub in subscribers:
sub.socket.set(zmq.RCVTIMEO, 1000)
while True:
- test_block = ZMQTestSetupBlock(self.nodes[0])
+ test_block = ZMQTestSetupBlock(self, self.nodes[0])
recv_failed = False
for sub in subscribers:
try:
@@ -184,7 +184,7 @@ class ZMQTest (BitcoinTestFramework):
rawtx = subs[3]
num_blocks = 5
- self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks})
+ self.log.info(f"Generate {num_blocks} blocks (and {num_blocks} coinbase txes)")
genhashes = self.nodes[0].generatetoaddress(num_blocks, ADDRESS_BCRT1_UNSPENDABLE)
self.sync_all()
@@ -504,7 +504,7 @@ class ZMQTest (BitcoinTestFramework):
if mempool_sequence is not None:
zmq_mem_seq = mempool_sequence
if zmq_mem_seq > get_raw_seq:
- raise Exception("We somehow jumped mempool sequence numbers! zmq_mem_seq: {} > get_raw_seq: {}".format(zmq_mem_seq, get_raw_seq))
+ raise Exception(f"We somehow jumped mempool sequence numbers! zmq_mem_seq: {zmq_mem_seq} > get_raw_seq: {get_raw_seq}")
# 4) Moving forward, we apply the delta to our local view
# remaining txs(5) + 1 rbf(A+R) + 1 block connect + 1 final tx
@@ -520,7 +520,7 @@ class ZMQTest (BitcoinTestFramework):
assert mempool_sequence > expected_sequence
r_gap += mempool_sequence - expected_sequence
else:
- raise Exception("WARNING: txhash has unexpected mempool sequence value: {} vs expected {}".format(mempool_sequence, expected_sequence))
+ raise Exception(f"WARNING: txhash has unexpected mempool sequence value: {mempool_sequence} vs expected {expected_sequence}")
if label == "A":
assert hash_str not in mempool_view
mempool_view.add(hash_str)
diff --git a/test/functional/mempool_accept_wtxid.py b/test/functional/mempool_accept_wtxid.py
index ffafe7428f..82752f2d70 100755
--- a/test/functional/mempool_accept_wtxid.py
+++ b/test/functional/mempool_accept_wtxid.py
@@ -90,7 +90,7 @@ class MempoolWtxidTest(BitcoinTestFramework):
self.log.info("Submit child_one to the mempool")
txid_submitted = node.sendrawtransaction(child_one.serialize().hex())
- assert_equal(node.getrawmempool(True)[txid_submitted]['wtxid'], child_one_wtxid)
+ assert_equal(node.getmempoolentry(txid_submitted)['wtxid'], child_one_wtxid)
peer_wtxid_relay.wait_for_broadcast([child_one_wtxid])
assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0)
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index 87f40b7f2b..9a50647a01 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -65,8 +65,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
self.log.info("Add unbroadcasted tx to mempool on new node and shutdown")
unbroadcasted_tx_hash = new_wallet.send_self_transfer(from_node=new_node)['txid']
assert unbroadcasted_tx_hash in new_node.getrawmempool()
- mempool = new_node.getrawmempool(True)
- assert mempool[unbroadcasted_tx_hash]['unbroadcast']
+ assert new_node.getmempoolentry(unbroadcasted_tx_hash)['unbroadcast']
self.stop_node(1)
self.log.info("Move mempool.dat from new to old node")
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index 39035f7cb1..1b1ac23024 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -32,7 +32,7 @@ class MempoolLimitTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
txids = []
- utxos = create_confirmed_utxos(relayfee, self.nodes[0], 91)
+ utxos = create_confirmed_utxos(self, relayfee, self.nodes[0], 91)
self.log.info('Create a mempool tx that will be evicted')
us0 = utxos.pop()
diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py
new file mode 100755
index 0000000000..a54c1d0457
--- /dev/null
+++ b/test/functional/mempool_package_limits.py
@@ -0,0 +1,475 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021 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 logic for limiting mempool and package ancestors/descendants."""
+
+from decimal import Decimal
+
+from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.messages import (
+ COIN,
+ CTransaction,
+ CTxInWitness,
+ tx_from_hex,
+ WITNESS_SCALE_FACTOR,
+)
+from test_framework.script import (
+ CScript,
+ OP_TRUE,
+)
+from test_framework.util import (
+ assert_equal,
+)
+from test_framework.wallet import (
+ bulk_transaction,
+ create_child_with_parents,
+ make_chain,
+)
+
+class MempoolPackageLimitsTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def run_test(self):
+ self.log.info("Generate blocks to create UTXOs")
+ node = self.nodes[0]
+ self.privkeys = [node.get_deterministic_priv_key().key]
+ self.address = node.get_deterministic_priv_key().address
+ self.coins = []
+ # The last 100 coinbase transactions are premature
+ for b in node.generatetoaddress(200, self.address)[:100]:
+ coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
+ self.coins.append({
+ "txid": coinbase["txid"],
+ "amount": coinbase["vout"][0]["value"],
+ "scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
+ })
+
+ self.test_chain_limits()
+ self.test_desc_count_limits()
+ self.test_anc_count_limits()
+ self.test_anc_count_limits_2()
+ self.test_anc_count_limits_bushy()
+
+ # The node will accept our (nonstandard) extra large OP_RETURN outputs
+ self.restart_node(0, extra_args=["-acceptnonstdtxn=1"])
+ self.test_anc_size_limits()
+ self.test_desc_size_limits()
+
+ def test_chain_limits_helper(self, mempool_count, package_count):
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ first_coin = self.coins.pop()
+ spk = None
+ txid = first_coin["txid"]
+ chain_hex = []
+ chain_txns = []
+ value = first_coin["amount"]
+
+ for i in range(mempool_count + package_count):
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
+ txid = tx.rehash()
+ if i < mempool_count:
+ node.sendrawtransaction(txhex)
+ assert_equal(node.getmempoolentry(txid)["ancestorcount"], i + 1)
+ else:
+ chain_hex.append(txhex)
+ chain_txns.append(tx)
+ testres_too_long = node.testmempoolaccept(rawtxs=chain_hex)
+ for txres in testres_too_long:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=chain_hex)])
+
+ def test_chain_limits(self):
+ """Create chains from mempool and package transactions that are longer than 25,
+ but only if both in-mempool and in-package transactions are considered together.
+ This checks that both mempool and in-package transactions are taken into account when
+ calculating ancestors/descendant limits.
+ """
+ self.log.info("Check that in-package ancestors count for mempool ancestor limits")
+
+ # 24 transactions in the mempool and 2 in the package. The parent in the package has
+ # 24 in-mempool ancestors and 1 in-package descendant. The child has 0 direct parents
+ # in the mempool, but 25 in-mempool and in-package ancestors in total.
+ self.test_chain_limits_helper(24, 2)
+ # 2 transactions in the mempool and 24 in the package.
+ self.test_chain_limits_helper(2, 24)
+ # 13 transactions in the mempool and 13 in the package.
+ self.test_chain_limits_helper(13, 13)
+
+ def test_desc_count_limits(self):
+ """Create an 'A' shaped package with 24 transactions in the mempool and 2 in the package:
+ M1
+ ^ ^
+ M2a M2b
+ . .
+ . .
+ . .
+ M12a ^
+ ^ M13b
+ ^ ^
+ Pa Pb
+ The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package
+ descendants are all considered together (24 including in-mempool descendants and 26 including both
+ package transactions).
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
+ # Top parent in mempool, M1
+ first_coin = self.coins.pop()
+ parent_value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs
+ inputs = [{"txid": first_coin["txid"], "vout": 0}]
+ outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}]
+ rawtx = node.createrawtransaction(inputs, outputs)
+
+ parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
+ assert parent_signed["complete"]
+ parent_tx = tx_from_hex(parent_signed["hex"])
+ parent_txid = parent_tx.rehash()
+ node.sendrawtransaction(parent_signed["hex"])
+
+ package_hex = []
+
+ # Chain A
+ spk = parent_tx.vout[0].scriptPubKey.hex()
+ value = parent_value
+ txid = parent_txid
+ for i in range(12):
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
+ txid = tx.rehash()
+ if i < 11: # M2a... M12a
+ node.sendrawtransaction(txhex)
+ else: # Pa
+ package_hex.append(txhex)
+
+ # Chain B
+ value = parent_value - Decimal("0.0001")
+ rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : value})
+ tx_child_b = tx_from_hex(rawtx_b) # M2b
+ tx_child_b.wit.vtxinwit = [CTxInWitness()]
+ tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
+ tx_child_b_hex = tx_child_b.serialize().hex()
+ node.sendrawtransaction(tx_child_b_hex)
+ spk = tx_child_b.vout[0].scriptPubKey.hex()
+ txid = tx_child_b.rehash()
+ for i in range(12):
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
+ txid = tx.rehash()
+ if i < 11: # M3b... M13b
+ node.sendrawtransaction(txhex)
+ else: # Pb
+ package_hex.append(txhex)
+
+ assert_equal(24, node.getmempoolinfo()["size"])
+ assert_equal(2, len(package_hex))
+ testres_too_long = node.testmempoolaccept(rawtxs=package_hex)
+ for txres in testres_too_long:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
+
+ def test_anc_count_limits(self):
+ """Create a 'V' shaped chain with 24 transactions in the mempool and 3 in the package:
+ M1a M1b
+ ^ ^
+ M2a M2b
+ . .
+ . .
+ . .
+ M12a M12b
+ ^ ^
+ Pa Pb
+ ^ ^
+ Pc
+ The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool
+ and in-package ancestors are all considered together.
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ package_hex = []
+ parents_tx = []
+ values = []
+ scripts = []
+
+ self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
+
+ # Two chains of 13 transactions each
+ for _ in range(2):
+ spk = None
+ top_coin = self.coins.pop()
+ txid = top_coin["txid"]
+ value = top_coin["amount"]
+ for i in range(13):
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
+ txid = tx.rehash()
+ if i < 12:
+ node.sendrawtransaction(txhex)
+ else: # Save the 13th transaction for the package
+ package_hex.append(txhex)
+ parents_tx.append(tx)
+ scripts.append(spk)
+ values.append(value)
+
+ # Child Pc
+ child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
+ package_hex.append(child_hex)
+
+ assert_equal(24, node.getmempoolinfo()["size"])
+ assert_equal(3, len(package_hex))
+ testres_too_long = node.testmempoolaccept(rawtxs=package_hex)
+ for txres in testres_too_long:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
+
+ def test_anc_count_limits_2(self):
+ """Create a 'Y' shaped chain with 24 transactions in the mempool and 2 in the package:
+ M1a M1b
+ ^ ^
+ M2a M2b
+ . .
+ . .
+ . .
+ M12a M12b
+ ^ ^
+ Pc
+ ^
+ Pd
+ The lowest descendant, Pd, exceeds ancestor limits, but only if the in-mempool
+ and in-package ancestors are all considered together.
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ parents_tx = []
+ values = []
+ scripts = []
+
+ self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
+ # Two chains of 12 transactions each
+ for _ in range(2):
+ spk = None
+ top_coin = self.coins.pop()
+ txid = top_coin["txid"]
+ value = top_coin["amount"]
+ for i in range(12):
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
+ txid = tx.rehash()
+ value -= Decimal("0.0001")
+ node.sendrawtransaction(txhex)
+ if i == 11:
+ # last 2 transactions will be the parents of Pc
+ parents_tx.append(tx)
+ values.append(value)
+ scripts.append(spk)
+
+ # Child Pc
+ pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts)
+ pc_tx = tx_from_hex(pc_hex)
+ pc_value = sum(values) - Decimal("0.0002")
+ pc_spk = pc_tx.vout[0].scriptPubKey.hex()
+
+ # Child Pd
+ (_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk)
+
+ assert_equal(24, node.getmempoolinfo()["size"])
+ testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
+ for txres in testres_too_long:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
+
+ def test_anc_count_limits_bushy(self):
+ """Create a tree with 20 transactions in the mempool and 6 in the package:
+ M1...M4 M5...M8 M9...M12 M13...M16 M17...M20
+ ^ ^ ^ ^ ^ (each with 4 parents)
+ P0 P1 P2 P3 P4
+ ^ ^ ^ ^ ^ (5 parents)
+ PC
+ Where M(4i+1)...M+(4i+4) are the parents of Pi and P0, P1, P2, P3, and P4 are the parents of PC.
+ P0... P4 individually only have 4 parents each, and PC has no in-mempool parents. But
+ combined, PC has 25 in-mempool and in-package parents.
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ package_hex = []
+ parent_txns = []
+ parent_values = []
+ scripts = []
+ for _ in range(5): # Make package transactions P0 ... P4
+ gp_tx = []
+ gp_values = []
+ gp_scripts = []
+ for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4)
+ parent_coin = self.coins.pop()
+ value = parent_coin["amount"]
+ txid = parent_coin["txid"]
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value)
+ gp_tx.append(tx)
+ gp_values.append(value)
+ gp_scripts.append(spk)
+ node.sendrawtransaction(txhex)
+ # Package transaction Pi
+ pi_hex = create_child_with_parents(node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts)
+ package_hex.append(pi_hex)
+ pi_tx = tx_from_hex(pi_hex)
+ parent_txns.append(pi_tx)
+ parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN)
+ scripts.append(pi_tx.vout[0].scriptPubKey.hex())
+ # Package transaction PC
+ package_hex.append(create_child_with_parents(node, self.address, self.privkeys, parent_txns, parent_values, scripts))
+
+ assert_equal(20, node.getmempoolinfo()["size"])
+ assert_equal(6, len(package_hex))
+ testres = node.testmempoolaccept(rawtxs=package_hex)
+ for txres in testres:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
+
+ def test_anc_size_limits(self):
+ """Test Case with 2 independent transactions in the mempool and a parent + child in the
+ package, where the package parent is the child of both mempool transactions (30KvB each):
+ A B
+ ^ ^
+ C
+ ^
+ D
+ The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool
+ and in-package ancestors are all considered together.
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ parents_tx = []
+ values = []
+ scripts = []
+ target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB
+ high_fee = Decimal("0.003") # 10 sats/vB
+ self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
+ # Mempool transactions A and B
+ for _ in range(2):
+ spk = None
+ top_coin = self.coins.pop()
+ txid = top_coin["txid"]
+ value = top_coin["amount"]
+ (tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
+ bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys)
+ node.sendrawtransaction(bulked_tx.serialize().hex())
+ parents_tx.append(bulked_tx)
+ values.append(Decimal(bulked_tx.vout[0].nValue) / COIN)
+ scripts.append(bulked_tx.vout[0].scriptPubKey.hex())
+
+ # Package transaction C
+ small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee)
+ pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys)
+ pc_value = Decimal(pc_tx.vout[0].nValue) / COIN
+ pc_spk = pc_tx.vout[0].scriptPubKey.hex()
+ pc_hex = pc_tx.serialize().hex()
+
+ # Package transaction D
+ (small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee)
+ prevtxs = [{
+ "txid": pc_tx.rehash(),
+ "vout": 0,
+ "scriptPubKey": spk,
+ "amount": val,
+ }]
+ pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs)
+ pd_hex = pd_tx.serialize().hex()
+
+ assert_equal(2, node.getmempoolinfo()["size"])
+ testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])
+ for txres in testres_too_heavy:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])])
+
+ def test_desc_size_limits(self):
+ """Create 3 mempool transactions and 2 package transactions (25KvB each):
+ Ma
+ ^ ^
+ Mb Mc
+ ^ ^
+ Pd Pe
+ The top ancestor in the package exceeds descendant size limits but only if the in-mempool
+ and in-package descendants are all considered together.
+ """
+ node = self.nodes[0]
+ assert_equal(0, node.getmempoolinfo()["size"])
+ target_weight = 21 * 1000 * WITNESS_SCALE_FACTOR
+ high_fee = Decimal("0.0021") # 10 sats/vB
+ self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
+ # Top parent in mempool, Ma
+ first_coin = self.coins.pop()
+ parent_value = (first_coin["amount"] - high_fee) / 2 # Deduct fee and make 2 outputs
+ inputs = [{"txid": first_coin["txid"], "vout": 0}]
+ outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE: parent_value}]
+ rawtx = node.createrawtransaction(inputs, outputs)
+ parent_tx = bulk_transaction(tx_from_hex(rawtx), node, target_weight, self.privkeys)
+ node.sendrawtransaction(parent_tx.serialize().hex())
+
+ package_hex = []
+ for j in range(2): # Two legs (left and right)
+ # Mempool transaction (Mb and Mc)
+ mempool_tx = CTransaction()
+ spk = parent_tx.vout[j].scriptPubKey.hex()
+ value = Decimal(parent_tx.vout[j].nValue) / COIN
+ txid = parent_tx.rehash()
+ prevtxs = [{
+ "txid": txid,
+ "vout": j,
+ "scriptPubKey": spk,
+ "amount": value,
+ }]
+ if j == 0: # normal key
+ (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, j, spk, high_fee)
+ mempool_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
+ else: # OP_TRUE
+ inputs = [{"txid": txid, "vout": 1}]
+ outputs = {self.address: value - high_fee}
+ small_tx = tx_from_hex(node.createrawtransaction(inputs, outputs))
+ mempool_tx = bulk_transaction(small_tx, node, target_weight, None, prevtxs)
+ node.sendrawtransaction(mempool_tx.serialize().hex())
+
+ # Package transaction (Pd and Pe)
+ spk = mempool_tx.vout[0].scriptPubKey.hex()
+ value = Decimal(mempool_tx.vout[0].nValue) / COIN
+ txid = mempool_tx.rehash()
+ (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
+ prevtxs = [{
+ "txid": txid,
+ "vout": 0,
+ "scriptPubKey": spk,
+ "amount": value,
+ }]
+ package_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
+ package_hex.append(package_tx.serialize().hex())
+
+ assert_equal(3, node.getmempoolinfo()["size"])
+ assert_equal(2, len(package_hex))
+ testres_too_heavy = node.testmempoolaccept(rawtxs=package_hex)
+ for txres in testres_too_heavy:
+ assert_equal(txres["package-error"], "package-mempool-limits")
+
+ # Clear mempool and check that the package passes now
+ node.generate(1)
+ assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
+
+if __name__ == "__main__":
+ MempoolPackageLimitsTest().main()
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index fcd8b061fa..3ee50f5a8a 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -51,7 +51,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
(second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1)
# Check mempool has MAX_ANCESTORS + 1 transactions in it
- assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1)
+ assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
# Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1)
@@ -74,7 +74,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.nodes[0].sendrawtransaction(signed_second_tx['hex'])
# Finally, check that we added two transactions
- assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3)
+ assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)
if __name__ == '__main__':
MempoolPackagesTest().main()
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index 5fc3ec23ae..173d1d7493 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -89,28 +89,28 @@ class MempoolPackagesTest(BitcoinTestFramework):
assert_equal(entry, mempool[x])
# Check that the descendant calculations are correct
- assert_equal(mempool[x]['descendantcount'], descendant_count)
- descendant_fees += mempool[x]['fee']
- assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
- assert_equal(mempool[x]['fees']['base'], mempool[x]['fee'])
- assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee'])
- assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN)
- assert_equal(mempool[x]['fees']['descendant'], descendant_fees)
- descendant_vsize += mempool[x]['vsize']
- assert_equal(mempool[x]['descendantsize'], descendant_vsize)
+ assert_equal(entry['descendantcount'], descendant_count)
+ descendant_fees += entry['fee']
+ assert_equal(entry['modifiedfee'], entry['fee'])
+ assert_equal(entry['fees']['base'], entry['fee'])
+ assert_equal(entry['fees']['modified'], entry['modifiedfee'])
+ assert_equal(entry['descendantfees'], descendant_fees * COIN)
+ assert_equal(entry['fees']['descendant'], descendant_fees)
+ descendant_vsize += entry['vsize']
+ assert_equal(entry['descendantsize'], descendant_vsize)
descendant_count += 1
# Check that ancestor calculations are correct
- assert_equal(mempool[x]['ancestorcount'], ancestor_count)
- assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN)
- assert_equal(mempool[x]['ancestorsize'], ancestor_vsize)
- ancestor_vsize -= mempool[x]['vsize']
- ancestor_fees -= mempool[x]['fee']
+ assert_equal(entry['ancestorcount'], ancestor_count)
+ assert_equal(entry['ancestorfees'], ancestor_fees * COIN)
+ assert_equal(entry['ancestorsize'], ancestor_vsize)
+ ancestor_vsize -= entry['vsize']
+ ancestor_fees -= entry['fee']
ancestor_count -= 1
# Check that parent/child list is correct
- assert_equal(mempool[x]['spentby'], descendants[-1:])
- assert_equal(mempool[x]['depends'], ancestors[-2:-1])
+ assert_equal(entry['spentby'], descendants[-1:])
+ assert_equal(entry['depends'], ancestors[-2:-1])
# Check that getmempooldescendants is correct
assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
@@ -153,12 +153,12 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Check that ancestor modified fees includes fee deltas from
# prioritisetransaction
self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000)
- mempool = self.nodes[0].getrawmempool(True)
ancestor_fees = 0
for x in chain:
- ancestor_fees += mempool[x]['fee']
- assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001'))
- assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000)
+ entry = self.nodes[0].getmempoolentry(x)
+ ancestor_fees += entry['fee']
+ assert_equal(entry['fees']['ancestor'], ancestor_fees + Decimal('0.00001'))
+ assert_equal(entry['ancestorfees'], ancestor_fees * COIN + 1000)
# Undo the prioritisetransaction for later tests
self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000)
@@ -166,13 +166,13 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Check that descendant modified fees includes fee deltas from
# prioritisetransaction
self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000)
- mempool = self.nodes[0].getrawmempool(True)
descendant_fees = 0
for x in reversed(chain):
- descendant_fees += mempool[x]['fee']
- assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001'))
- assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000)
+ entry = self.nodes[0].getmempoolentry(x)
+ descendant_fees += entry['fee']
+ assert_equal(entry['fees']['descendant'], descendant_fees + Decimal('0.00001'))
+ assert_equal(entry['descendantfees'], descendant_fees * COIN + 1000)
# Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1)
@@ -190,16 +190,15 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
# Now check that the transaction is in the mempool, with the right modified fee
- mempool = self.nodes[0].getrawmempool(True)
-
descendant_fees = 0
for x in reversed(chain):
- descendant_fees += mempool[x]['fee']
+ entry = self.nodes[0].getmempoolentry(x)
+ descendant_fees += entry['fee']
if (x == chain[-1]):
- assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002))
- assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee']+satoshi_round(0.00002))
- assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000)
- assert_equal(mempool[x]['fees']['descendant'], descendant_fees+satoshi_round(0.00002))
+ assert_equal(entry['modifiedfee'], entry['fee']+satoshi_round(0.00002))
+ assert_equal(entry['fees']['modified'], entry['fee']+satoshi_round(0.00002))
+ assert_equal(entry['descendantfees'], descendant_fees * COIN + 2000)
+ assert_equal(entry['fees']['descendant'], descendant_fees+satoshi_round(0.00002))
# Check that node1's mempool is as expected (-> custom ancestor limit)
mempool0 = self.nodes[0].getrawmempool(False)
@@ -255,7 +254,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
# - txs from previous ancestor test (-> custom ancestor limit)
# - parent tx for descendant test
# - txs chained off parent tx (-> custom descendant limit)
- self.wait_until(lambda: len(self.nodes[1].getrawmempool(False)) ==
+ self.wait_until(lambda: len(self.nodes[1].getrawmempool()) ==
MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM, timeout=10)
mempool0 = self.nodes[0].getrawmempool(False)
mempool1 = self.nodes[1].getrawmempool(False)
diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py
index 7d9e6c306d..d10b510c6a 100755
--- a/test/functional/mempool_unbroadcast.py
+++ b/test/functional/mempool_unbroadcast.py
@@ -32,7 +32,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
node = self.nodes[0]
min_relay_fee = node.getnetworkinfo()["relayfee"]
- utxos = create_confirmed_utxos(min_relay_fee, node, 10)
+ utxos = create_confirmed_utxos(self, min_relay_fee, node, 10)
self.disconnect_nodes(0, 1)
@@ -94,9 +94,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
self.log.info("Rebroadcast transaction and ensure it is not added to unbroadcast set when already in mempool")
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
- mempool = node.getrawmempool(True)
- assert rpc_tx_hsh in mempool
- assert not mempool[rpc_tx_hsh]['unbroadcast']
+ assert not node.getmempoolentry(rpc_tx_hsh)['unbroadcast']
def test_txn_removal(self):
self.log.info("Test that transactions removed from mempool are removed from unbroadcast set")
diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py
index 8baf974a0a..4cd11e9d11 100755
--- a/test/functional/mempool_updatefromblock.py
+++ b/test/functional/mempool_updatefromblock.py
@@ -86,7 +86,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx)
tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex']))
- tx_size.append(self.nodes[0].getrawmempool(True)[tx_id[-1]]['vsize'])
+ tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize'])
if tx_count in n_tx_to_mine:
# The created transactions are mined into blocks by batches.
@@ -109,10 +109,11 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
self.log.info('Checking descendants/ancestors properties of all of the in-mempool transactions...')
for k, tx in enumerate(tx_id):
self.log.debug('Check transaction #{}.'.format(k))
- assert_equal(self.nodes[0].getrawmempool(True)[tx]['descendantcount'], size - k)
- assert_equal(self.nodes[0].getrawmempool(True)[tx]['descendantsize'], sum(tx_size[k:size]))
- assert_equal(self.nodes[0].getrawmempool(True)[tx]['ancestorcount'], k + 1)
- assert_equal(self.nodes[0].getrawmempool(True)[tx]['ancestorsize'], sum(tx_size[0:(k + 1)]))
+ entry = self.nodes[0].getmempoolentry(tx)
+ assert_equal(entry['descendantcount'], size - k)
+ assert_equal(entry['descendantsize'], sum(tx_size[k:size]))
+ assert_equal(entry['ancestorcount'], k + 1)
+ assert_equal(entry['ancestorsize'], sum(tx_size[0:(k + 1)]))
def run_test(self):
# Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 25 to not fire "too many unconfirmed parents" error.
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index 01fc02f27e..23711646a2 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -65,10 +65,10 @@ class MiningTest(BitcoinTestFramework):
assert_equal(mining_info['currentblockweight'], 4000)
self.log.info('test blockversion')
- self.restart_node(0, extra_args=['-mocktime={}'.format(t), '-blockversion=1337'])
+ self.restart_node(0, extra_args=[f'-mocktime={t}', '-blockversion=1337'])
self.connect_nodes(0, 1)
assert_equal(1337, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
- self.restart_node(0, extra_args=['-mocktime={}'.format(t)])
+ self.restart_node(0, extra_args=[f'-mocktime={t}'])
self.connect_nodes(0, 1)
assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT), self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
self.restart_node(0)
diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py
index 715b68e04c..3003627778 100755
--- a/test/functional/mining_getblocktemplate_longpoll.py
+++ b/test/functional/mining_getblocktemplate_longpoll.py
@@ -48,9 +48,9 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
thr.join(5) # wait 5 seconds or until thread exits
assert thr.is_alive()
- miniwallets = [ MiniWallet(node) for node in self.nodes ]
+ miniwallets = [MiniWallet(node) for node in self.nodes]
self.log.info("Test that longpoll will terminate if another node generates a block")
- miniwallets[1].generate(1) # generate a block on another node
+ self.generate(miniwallets[1], 1) # generate a block on another node
# check that thread will exit now that new transaction entered mempool
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
@@ -58,7 +58,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
self.log.info("Test that longpoll will terminate if we generate a block ourselves")
thr = LongpollThread(self.nodes[0])
thr.start()
- miniwallets[0].generate(1) # generate a block on own node
+ self.generate(miniwallets[0], 1) # generate a block on own node
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index 9fc38ebf53..d357e2ed47 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -48,7 +48,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
utxo_count = 90
- utxos = create_confirmed_utxos(self.relayfee, self.nodes[0], utxo_count)
+ utxos = create_confirmed_utxos(self, self.relayfee, self.nodes[0], utxo_count)
base_fee = self.relayfee*100 # our transactions are smaller than 100kb
txids = []
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 113e1f9492..e93698b08e 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -311,7 +311,7 @@ class AddrTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
- def send_addrs_and_test_rate_limiting(self, peer, no_relay, new_addrs, total_addrs):
+ def send_addrs_and_test_rate_limiting(self, peer, no_relay, *, new_addrs, total_addrs):
"""Send an addr message and check that the number of addresses processed and rate-limited is as expected"""
peer.send_and_ping(self.setup_rand_addr_msg(new_addrs))
@@ -329,27 +329,26 @@ class AddrTest(BitcoinTestFramework):
assert_equal(addrs_rate_limited, max(0, total_addrs - peer.tokens))
def rate_limit_tests(self):
-
self.mocktime = int(time.time())
self.restart_node(0, [])
self.nodes[0].setmocktime(self.mocktime)
- for contype, no_relay in [("outbound-full-relay", False), ("block-relay-only", True), ("inbound", False)]:
- self.log.info(f'Test rate limiting of addr processing for {contype} peers')
- if contype == "inbound":
+ for conn_type, no_relay in [("outbound-full-relay", False), ("block-relay-only", True), ("inbound", False)]:
+ self.log.info(f'Test rate limiting of addr processing for {conn_type} peers')
+ if conn_type == "inbound":
peer = self.nodes[0].add_p2p_connection(AddrReceiver())
else:
- peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type=contype)
+ peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type=conn_type)
# Send 600 addresses. For all but the block-relay-only peer this should result in addresses being processed.
- self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 600)
+ self.send_addrs_and_test_rate_limiting(peer, no_relay, new_addrs=600, total_addrs=600)
# Send 600 more addresses. For the outbound-full-relay peer (which we send a GETADDR, and thus will
# process up to 1001 incoming addresses), this means more addresses will be processed.
- self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 1200)
+ self.send_addrs_and_test_rate_limiting(peer, no_relay, new_addrs=600, total_addrs=1200)
# Send 10 more. As we reached the processing limit for all nodes, no more addresses should be procesesd.
- self.send_addrs_and_test_rate_limiting(peer, no_relay, 10, 1210)
+ self.send_addrs_and_test_rate_limiting(peer, no_relay, new_addrs=10, total_addrs=1210)
# Advance the time by 100 seconds, permitting the processing of 10 more addresses.
# Send 200 and verify that 10 are processed.
@@ -357,7 +356,7 @@ class AddrTest(BitcoinTestFramework):
self.nodes[0].setmocktime(self.mocktime)
peer.increment_tokens(10)
- self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1410)
+ self.send_addrs_and_test_rate_limiting(peer, no_relay, new_addrs=200, total_addrs=1410)
# Advance the time by 1000 seconds, permitting the processing of 100 more addresses.
# Send 200 and verify that 100 are processed.
@@ -365,9 +364,10 @@ class AddrTest(BitcoinTestFramework):
self.nodes[0].setmocktime(self.mocktime)
peer.increment_tokens(100)
- self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1610)
+ self.send_addrs_and_test_rate_limiting(peer, no_relay, new_addrs=200, total_addrs=1610)
self.nodes[0].disconnect_p2ps()
+
if __name__ == '__main__':
AddrTest().main()
diff --git a/test/functional/p2p_addrfetch.py b/test/functional/p2p_addrfetch.py
index 66ee1544a9..2a0f6432a9 100755
--- a/test/functional/p2p_addrfetch.py
+++ b/test/functional/p2p_addrfetch.py
@@ -21,18 +21,24 @@ ADDR.port = 18444
class P2PAddrFetch(BitcoinTestFramework):
-
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ def assert_getpeerinfo(self, *, peer_ids):
+ num_peers = len(peer_ids)
+ info = self.nodes[0].getpeerinfo()
+ assert_equal(len(info), num_peers)
+ for n in range(0, num_peers):
+ assert_equal(info[n]['id'], peer_ids[n])
+ assert_equal(info[n]['connection_type'], 'addr-fetch')
+
def run_test(self):
node = self.nodes[0]
self.log.info("Connect to an addr-fetch peer")
- peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="addr-fetch")
- info = node.getpeerinfo()
- assert_equal(len(info), 1)
- assert_equal(info[0]['connection_type'], 'addr-fetch')
+ peer_id = 0
+ peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=peer_id, connection_type="addr-fetch")
+ self.assert_getpeerinfo(peer_ids=[peer_id])
self.log.info("Check that we send getaddr but don't try to sync headers with the addr-fetch peer")
peer.sync_send_with_ping()
@@ -45,7 +51,7 @@ class P2PAddrFetch(BitcoinTestFramework):
msg = msg_addr()
msg.addrs = [ADDR]
peer.send_and_ping(msg)
- assert_equal(len(node.getpeerinfo()), 1)
+ self.assert_getpeerinfo(peer_ids=[peer_id])
self.log.info("Check that answering with larger addr messages leads to disconnect")
msg.addrs = [ADDR] * 2
@@ -53,9 +59,20 @@ class P2PAddrFetch(BitcoinTestFramework):
peer.wait_for_disconnect(timeout=5)
self.log.info("Check timeout for addr-fetch peer that does not send addrs")
- peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="addr-fetch")
- node.setmocktime(int(time.time()) + 301) # Timeout: 5 minutes
+ peer_id = 1
+ peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=peer_id, connection_type="addr-fetch")
+
+ time_now = int(time.time())
+ self.assert_getpeerinfo(peer_ids=[peer_id])
+
+ # Expect addr-fetch peer connection to be maintained up to 5 minutes.
+ node.setmocktime(time_now + 295)
+ self.assert_getpeerinfo(peer_ids=[peer_id])
+
+ # Expect addr-fetch peer connection to be disconnected after 5 minutes.
+ node.setmocktime(time_now + 301)
peer.wait_for_disconnect(timeout=5)
+ self.assert_getpeerinfo(peer_ids=[])
if __name__ == '__main__':
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 8783c244c3..8fef2d173f 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -141,9 +141,9 @@ class InvalidTxRequestTest(BitcoinTestFramework):
tx_orphan_2_valid, # The valid transaction (with sufficient fee)
]
}
- # Transactions that do not end up in the mempool
- # tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx)
- # tx_orphan_invalid, because it has negative fee (p2ps[1] is disconnected for relaying that tx)
+ # Transactions that do not end up in the mempool:
+ # tx_orphan_2_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx)
+ # tx_orphan_2_invalid, because it has negative fee (p2ps[1] is disconnected for relaying that tx)
self.wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) # p2ps[1] is no longer connected
assert_equal(expected_mempool, set(node.getrawmempool()))
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index e7a05d8547..a9d5ed970a 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -77,7 +77,7 @@ class AcceptBlockTest(BitcoinTestFramework):
min_work_node = self.nodes[1].add_p2p_connection(P2PInterface())
# 1. Have nodes mine a block (leave IBD)
- [n.generatetoaddress(1, n.get_deterministic_priv_key().address) for n in self.nodes]
+ [self.generatetoaddress(n, 1, n.get_deterministic_priv_key().address) for n in self.nodes]
tips = [int("0x" + n.getbestblockhash(), 0) for n in self.nodes]
# 2. Send one block that builds on each tip.
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 721e3f93a3..2ee5c7ee0a 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -27,6 +27,8 @@ import subprocess
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
from test_framework.blocktools import (
+ CLTV_HEIGHT,
+ DERSIG_HEIGHT,
create_block,
create_coinbase,
TIME_GENESIS_BLOCK,
@@ -141,8 +143,8 @@ class BlockchainTest(BitcoinTestFramework):
assert_equal(res['softforks'], {
'bip34': {'type': 'buried', 'active': True, 'height': 2},
- 'bip66': {'type': 'buried', 'active': False, 'height': 1251},
- 'bip65': {'type': 'buried', 'active': False, 'height': 1351},
+ 'bip66': {'type': 'buried', 'active': True, 'height': DERSIG_HEIGHT},
+ 'bip65': {'type': 'buried', 'active': True, 'height': CLTV_HEIGHT},
'csv': {'type': 'buried', 'active': False, 'height': 432},
'segwit': {'type': 'buried', 'active': True, 'height': 0},
'testdummy': {
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 816ec67492..bb2e51c2f7 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -3,7 +3,6 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test multisig RPCs"""
-import binascii
import decimal
import itertools
import json
@@ -66,9 +65,9 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
# decompress pk2
pk_obj = ECPubKey()
- pk_obj.set(binascii.unhexlify(pk2))
+ pk_obj.set(bytes.fromhex(pk2))
pk_obj.compressed = False
- pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
+ pk2 = pk_obj.get_bytes().hex()
node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
wmulti0 = node0.get_wallet_rpc('wmulti0')
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index fa98c44152..0ce58efbcf 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -99,6 +99,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_subtract_fee_with_presets()
self.test_transaction_too_large()
self.test_include_unsafe()
+ self.test_22670()
def test_change_position(self):
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -379,7 +380,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1)
- signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+ signedFee = self.nodes[0].getmempoolentry(txId)['fee']
# Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
@@ -402,7 +403,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Create same transaction over sendtoaddress.
txId = self.nodes[0].sendmany("", outputs)
- signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+ signedFee = self.nodes[0].getmempoolentry(txId)['fee']
# Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
@@ -426,7 +427,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
- signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+ signedFee = self.nodes[0].getmempoolentry(txId)['fee']
# Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
@@ -467,7 +468,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Create same transaction over sendtoaddress.
txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
- signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+ signedFee = self.nodes[0].getmempoolentry(txId)['fee']
# Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
@@ -542,7 +543,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
inputs = []
- outputs = {self.nodes[0].getnewaddress():1.09999500}
+ outputs = {self.nodes[0].getnewaddress():1.19999500}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
# fund a transaction that does not require a new key for the change output
self.nodes[1].fundrawtransaction(rawtx)
@@ -599,7 +600,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# Create same transaction over sendtoaddress.
txId = self.nodes[1].sendmany("", outputs)
- signedFee = self.nodes[1].getrawmempool(True)[txId]['fee']
+ signedFee = self.nodes[1].getmempoolentry(txId)['fee']
# Compare fee.
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
@@ -969,6 +970,62 @@ class RawTransactionsTest(BitcoinTestFramework):
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
wallet.sendrawtransaction(signedtx['hex'])
+ def test_22670(self):
+ # In issue #22670, it was observed that ApproximateBestSubset may
+ # choose enough value to cover the target amount but not enough to cover the transaction fees.
+ # This leads to a transaction whose actual transaction feerate is lower than expected.
+ # However at normal feerates, the difference between the effective value and the real value
+ # that this bug is not detected because the transaction fee must be at least 0.01 BTC (the minimum change value).
+ # Otherwise the targeted minimum change value will be enough to cover the transaction fees that were not
+ # being accounted for. So the minimum relay fee is set to 0.1 BTC/kvB in this test.
+ self.log.info("Test issue 22670 ApproximateBestSubset bug")
+ # Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee
+ self.nodes[0].unloadwallet(self.default_wallet_name, False)
+ feerate = Decimal("0.1")
+ self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation
+
+ self.nodes[0].loadwallet(self.default_wallet_name, True)
+ funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.nodes[0].createwallet(wallet_name="tester")
+ tester = self.nodes[0].get_wallet_rpc("tester")
+
+ # Because this test is specifically for ApproximateBestSubset, the target value must be greater
+ # than any single input available, and require more than 1 input. So we make 3 outputs
+ for i in range(0, 3):
+ funds.sendtoaddress(tester.getnewaddress(address_type="bech32"), 1)
+ self.nodes[0].generate(1)
+
+ # Create transactions in order to calculate fees for the target bounds that can trigger this bug
+ change_tx = tester.fundrawtransaction(tester.createrawtransaction([], [{funds.getnewaddress(): 1.5}]))
+ tx = tester.createrawtransaction([], [{funds.getnewaddress(): 2}])
+ no_change_tx = tester.fundrawtransaction(tx, {"subtractFeeFromOutputs": [0]})
+
+ overhead_fees = feerate * len(tx) / 2 / 1000
+ cost_of_change = change_tx["fee"] - no_change_tx["fee"]
+ fees = no_change_tx["fee"]
+ assert_greater_than(fees, 0.01)
+
+ def do_fund_send(target):
+ create_tx = tester.createrawtransaction([], [{funds.getnewaddress(): target}])
+ funded_tx = tester.fundrawtransaction(create_tx)
+ signed_tx = tester.signrawtransactionwithwallet(funded_tx["hex"])
+ assert signed_tx["complete"]
+ decoded_tx = tester.decoderawtransaction(signed_tx["hex"])
+ assert_equal(len(decoded_tx["vin"]), 3)
+ assert tester.testmempoolaccept([signed_tx["hex"]])[0]["allowed"]
+
+ # We want to choose more value than is available in 2 inputs when considering the fee,
+ # but not enough to need 3 inputs when not considering the fee.
+ # So the target value must be at least 2.00000001 - fee.
+ lower_bound = Decimal("2.00000001") - fees
+ # The target value must be at most 2 - cost_of_change - not_input_fees - min_change (these are all
+ # included in the target before ApproximateBestSubset).
+ upper_bound = Decimal("2.0") - cost_of_change - overhead_fees - Decimal("0.01")
+ assert_greater_than_or_equal(upper_bound, lower_bound)
+ do_fund_send(lower_bound)
+ do_fund_send(upper_bound)
+
+ self.restart_node(0)
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 4b2ed20958..3cb4154601 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -22,6 +22,11 @@ from test_framework.script import (
from test_framework.util import (
assert_equal,
)
+from test_framework.wallet import (
+ create_child_with_parents,
+ create_raw_chain,
+ make_chain,
+)
class RPCPackagesTest(BitcoinTestFramework):
def set_test_params(self):
@@ -78,26 +83,6 @@ class RPCPackagesTest(BitcoinTestFramework):
self.test_conflicting()
self.test_rbf()
- def chain_transaction(self, parent_txid, parent_value, n=0, parent_locking_script=None):
- """Build a transaction that spends parent_txid.vout[n] and produces one output with
- amount = parent_value with a fee deducted.
- Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created).
- """
- node = self.nodes[0]
- inputs = [{"txid": parent_txid, "vout": n}]
- my_value = parent_value - Decimal("0.0001")
- outputs = {self.address : my_value}
- rawtx = node.createrawtransaction(inputs, outputs)
- prevtxs = [{
- "txid": parent_txid,
- "vout": n,
- "scriptPubKey": parent_locking_script,
- "amount": parent_value,
- }] if parent_locking_script else None
- signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys, prevtxs=prevtxs)
- assert signedtx["complete"]
- tx = tx_from_hex(signedtx["hex"])
- return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex())
def test_independent(self):
self.log.info("Test multiple independent transactions in a package")
@@ -148,20 +133,7 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_chain(self):
node = self.nodes[0]
first_coin = self.coins.pop()
-
- # Chain of 25 transactions
- parent_locking_script = None
- txid = first_coin["txid"]
- chain_hex = []
- chain_txns = []
- value = first_coin["amount"]
-
- for _ in range(25):
- (tx, txhex, value, parent_locking_script) = self.chain_transaction(txid, value, 0, parent_locking_script)
- txid = tx.rehash()
- chain_hex.append(txhex)
- chain_txns.append(tx)
-
+ (chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
[{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
@@ -201,7 +173,7 @@ class RPCPackagesTest(BitcoinTestFramework):
child_value = value - Decimal("0.0001")
# Child A
- (_, tx_child_a_hex, _, _) = self.chain_transaction(parent_txid, child_value, 0, parent_locking_script_a)
+ (_, tx_child_a_hex, _, _) = make_chain(node, self.address, self.privkeys, parent_txid, child_value, 0, parent_locking_script_a)
assert not node.testmempoolaccept([tx_child_a_hex])[0]["allowed"]
# Child B
@@ -226,19 +198,6 @@ class RPCPackagesTest(BitcoinTestFramework):
node.sendrawtransaction(rawtx)
assert_equal(testres_single, testres_multiple_ab)
- def create_child_with_parents(self, parents_tx, values, locking_scripts):
- """Creates a transaction that spends the first output of each parent in parents_tx."""
- num_parents = len(parents_tx)
- total_value = sum(values)
- inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx]
- outputs = {self.address : total_value - num_parents * Decimal("0.0001")}
- rawtx_child = self.nodes[0].createrawtransaction(inputs, outputs)
- prevtxs = []
- for i in range(num_parents):
- prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]})
- signedtx_child = self.nodes[0].signrawtransactionwithkey(hexstring=rawtx_child, privkeys=self.privkeys, prevtxs=prevtxs)
- assert signedtx_child["complete"]
- return signedtx_child["hex"]
def test_multiple_parents(self):
node = self.nodes[0]
@@ -253,12 +212,12 @@ class RPCPackagesTest(BitcoinTestFramework):
for _ in range(num_parents):
parent_coin = self.coins.pop()
value = parent_coin["amount"]
- (tx, txhex, value, parent_locking_script) = self.chain_transaction(parent_coin["txid"], value)
+ (tx, txhex, value, parent_locking_script) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
package_hex.append(txhex)
parents_tx.append(tx)
values.append(value)
parent_locking_scripts.append(parent_locking_script)
- child_hex = self.create_child_with_parents(parents_tx, values, parent_locking_scripts)
+ child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, parent_locking_scripts)
# Package accept should work with the parents in any order (as long as parents come before child)
for _ in range(10):
random.shuffle(package_hex)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 9d4a5525d1..fbf8c6ef15 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -5,11 +5,11 @@
"""Test the rawtransaction RPCs.
Test the following RPCs:
+ - getrawtransaction
- createrawtransaction
- signrawtransactionwithwallet
- sendrawtransaction
- decoderawtransaction
- - getrawtransaction
"""
from collections import OrderedDict
@@ -28,6 +28,9 @@ from test_framework.util import (
)
+TXID = "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000"
+
+
class multidict(dict):
"""Dictionary that allows duplicate keys.
@@ -46,15 +49,15 @@ class multidict(dict):
return self.x
-# Create one-input, one-output, no-fee transaction:
class RawTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 3
+ self.num_nodes = 4
self.extra_args = [
["-txindex"],
["-txindex"],
["-txindex"],
+ [],
]
# whitelist all peers to speed up tx relay / mempool sync
for args in self.extra_args:
@@ -70,23 +73,112 @@ class RawTransactionsTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
def run_test(self):
- self.log.info('prepare some coins for multiple *rawtransaction commands')
+ self.log.info("Prepare some coins for multiple *rawtransaction commands")
self.nodes[2].generate(1)
self.sync_all()
self.nodes[0].generate(COINBASE_MATURITY + 1)
self.sync_all()
- self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5)
- self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0)
- self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0)
+ for amount in [1.5, 1.0, 5.0]:
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), amount)
self.sync_all()
self.nodes[0].generate(5)
self.sync_all()
- self.log.info('Test getrawtransaction on genesis block coinbase returns an error')
+ self.getrawtransaction_tests()
+ self.createrawtransaction_tests()
+ self.signrawtransactionwithwallet_tests()
+ self.sendrawtransaction_tests()
+ self.sendrawtransaction_testmempoolaccept_tests()
+ self.decoderawtransaction_tests()
+ self.transaction_version_number_tests()
+ if not self.options.descriptors:
+ self.raw_multisig_transaction_legacy_tests()
+
+ def getrawtransaction_tests(self):
+ addr = self.nodes[1].getnewaddress()
+ txid = self.nodes[0].sendtoaddress(addr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+ vout = find_vout_for_address(self.nodes[1], txid, addr)
+ rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999})
+ rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx)
+ txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex'])
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ for n in [0, 3]:
+ self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex")
+ # 1. valid parameters - only supply txid
+ assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex'])
+
+ # 2. valid parameters - supply txid and 0 for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex'])
+
+ # 3. valid parameters - supply txid and False for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex'])
+
+ # 4. valid parameters - supply txid and 1 for verbose.
+ # We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
+ assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex'])
+
+ # 5. valid parameters - supply txid and True for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex'])
+
+ # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose
+ for value in ["True", "False"]:
+ assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txid=txId, verbose=value)
+
+ # 7. invalid parameters - supply txid and empty array
+ assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, [])
+
+ # 8. invalid parameters - supply txid and empty dict
+ assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, {})
+
+ # Make a tx by sending, then generate 2 blocks; block1 has the tx in it
+ tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ block1, block2 = self.nodes[2].generate(2)
+ self.sync_all()
+ for n in [0, 3]:
+ self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex, with blockhash")
+ # We should be able to get the raw transaction by providing the correct block
+ gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True, blockhash=block1)
+ assert_equal(gottx['txid'], tx)
+ assert_equal(gottx['in_active_chain'], True)
+ if n == 0:
+ self.log.info("Test getrawtransaction with -txindex, without blockhash: 'in_active_chain' should be absent")
+ gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True)
+ assert_equal(gottx['txid'], tx)
+ assert 'in_active_chain' not in gottx
+ else:
+ self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise")
+ err_msg = (
+ "No such mempool transaction. Use -txindex or provide a block hash to enable"
+ " blockchain transaction queries. Use gettransaction for wallet transactions."
+ )
+ assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True)
+ # We should not get the tx if we provide an unrelated block
+ assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2)
+ # An invalid block hash should raise the correct errors
+ assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[n].getrawtransaction, txid=tx, blockhash=True)
+ assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[n].getrawtransaction, txid=tx, blockhash="foobar")
+ assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[n].getrawtransaction, txid=tx, blockhash="abcd1234")
+ foo = "ZZZ0000000000000000000000000000000000000000000000000000000000000"
+ assert_raises_rpc_error(-8, f"parameter 3 must be hexadecimal string (not '{foo}')", self.nodes[n].getrawtransaction, txid=tx, blockhash=foo)
+ bar = "0000000000000000000000000000000000000000000000000000000000000000"
+ assert_raises_rpc_error(-5, "Block hash not found", self.nodes[n].getrawtransaction, txid=tx, blockhash=bar)
+ # Undo the blocks and verify that "in_active_chain" is false.
+ self.nodes[n].invalidateblock(block1)
+ gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True, blockhash=block1)
+ assert_equal(gottx['in_active_chain'], False)
+ self.nodes[n].reconsiderblock(block1)
+ assert_equal(self.nodes[n].getbestblockhash(), block2)
+
+ self.log.info("Test getrawtransaction on genesis block coinbase returns an error")
block = self.nodes[0].getblock(self.nodes[0].getblockhash(0))
assert_raises_rpc_error(-5, "The genesis block coinbase is not considered an ordinary transaction", self.nodes[0].getrawtransaction, block['merkleroot'])
- self.log.info('Check parameter types and required parameters of createrawtransaction')
+ def createrawtransaction_tests(self):
+ self.log.info("Test createrawtransaction")
# Test `createrawtransaction` required parameters
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction)
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [])
@@ -95,16 +187,28 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')
# Test `createrawtransaction` invalid `inputs`
- txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000'
assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {})
assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, ['foo'], {})
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].createrawtransaction, [{}], {})
assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {})
- assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", self.nodes[0].createrawtransaction, [{'txid': 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844'}], {})
- assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
- assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 'foo'}], {})
- assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': -1}], {})
- assert_raises_rpc_error(-8, "Invalid parameter, sequence number is out of range", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 0, 'sequence': -1}], {})
+ txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844"
+ assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
+ assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': TXID}], {})
+ assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 'foo'}], {})
+ assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': -1}], {})
+ # sequence number out of range
+ for invalid_seq in [-1, 4294967296]:
+ inputs = [{'txid': TXID, 'vout': 1, 'sequence': invalid_seq}]
+ outputs = {self.nodes[0].getnewaddress(): 1}
+ assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range',
+ self.nodes[0].createrawtransaction, inputs, outputs)
+ # with valid sequence number
+ for valid_seq in [1000, 4294967294]:
+ inputs = [{'txid': TXID, 'vout': 1, 'sequence': valid_seq}]
+ outputs = {self.nodes[0].getnewaddress(): 1}
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+ decrawtx = self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['vin'][0]['sequence'], valid_seq)
# Test `createrawtransaction` invalid `outputs`
address = self.nodes[0].getnewaddress()
@@ -131,53 +235,51 @@ class RawTransactionsTest(BitcoinTestFramework):
# Test `createrawtransaction` invalid `replaceable`
assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')
- self.log.info('Check that createrawtransaction accepts an array and object as outputs')
+ # Test that createrawtransaction accepts an array and object as outputs
# One output
- tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs={address: 99}))
+ tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs={address: 99}))
assert_equal(len(tx.vout), 1)
assert_equal(
tx.serialize().hex(),
- self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}]),
+ self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}]),
)
# Two outputs
- tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)])))
+ tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)])))
assert_equal(len(tx.vout), 2)
assert_equal(
tx.serialize().hex(),
- self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]),
+ self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]),
)
# Multiple mixed outputs
- tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')])))
+ tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')])))
assert_equal(len(tx.vout), 3)
assert_equal(
tx.serialize().hex(),
- self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]),
+ self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]),
)
+ def signrawtransactionwithwallet_tests(self):
for type in ["bech32", "p2sh-segwit", "legacy"]:
+ self.log.info(f"Test signrawtransactionwithwallet with missing prevtx info ({type})")
addr = self.nodes[0].getnewaddress("", type)
addrinfo = self.nodes[0].getaddressinfo(addr)
pubkey = addrinfo["scriptPubKey"]
+ inputs = [{'txid': TXID, 'vout': 3, 'sequence': 1000}]
+ outputs = {self.nodes[0].getnewaddress(): 1}
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- self.log.info('sendrawtransaction with missing prevtx info (%s)' %(type))
-
- # Test `signrawtransactionwithwallet` invalid `prevtxs`
- inputs = [ {'txid' : txid, 'vout' : 3, 'sequence' : 1000}]
- outputs = { self.nodes[0].getnewaddress() : 1 }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
-
- prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1)
+ prevtx = dict(txid=TXID, scriptPubKey=pubkey, vout=3, amount=1)
succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
assert succ["complete"]
+
if type == "legacy":
del prevtx["amount"]
succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
assert succ["complete"]
-
- if type != "legacy":
+ else:
assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [
{
- "txid": txid,
+ "txid": TXID,
"scriptPubKey": pubkey,
"vout": 3,
}
@@ -185,7 +287,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [
{
- "txid": txid,
+ "txid": TXID,
"scriptPubKey": pubkey,
"amount": 1,
}
@@ -199,273 +301,23 @@ class RawTransactionsTest(BitcoinTestFramework):
])
assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [
{
- "txid": txid,
+ "txid": TXID,
"vout": 3,
"amount": 1
}
])
- #########################################
- # sendrawtransaction with missing input #
- #########################################
-
- self.log.info('sendrawtransaction with missing input')
- inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1}] #won't exists
- outputs = { self.nodes[0].getnewaddress() : 4.998 }
- rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
-
- # This will raise an exception since there are missing inputs
+ def sendrawtransaction_tests(self):
+ self.log.info("Test sendrawtransaction with missing input")
+ inputs = [{'txid': TXID, 'vout': 1}] # won't exist
+ outputs = {self.nodes[0].getnewaddress(): 4.998}
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex'])
- #####################################
- # getrawtransaction with block hash #
- #####################################
-
- # make a tx by sending then generate 2 blocks; block1 has the tx in it
- tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1)
- block1, block2 = self.nodes[2].generate(2)
- self.sync_all()
- # We should be able to get the raw transaction by providing the correct block
- gottx = self.nodes[0].getrawtransaction(tx, True, block1)
- assert_equal(gottx['txid'], tx)
- assert_equal(gottx['in_active_chain'], True)
- # We should not have the 'in_active_chain' flag when we don't provide a block
- gottx = self.nodes[0].getrawtransaction(tx, True)
- assert_equal(gottx['txid'], tx)
- assert 'in_active_chain' not in gottx
- # We should not get the tx if we provide an unrelated block
- assert_raises_rpc_error(-5, "No such transaction found", self.nodes[0].getrawtransaction, tx, True, block2)
- # An invalid block hash should raise the correct errors
- assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].getrawtransaction, tx, True, True)
- assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[0].getrawtransaction, tx, True, "foobar")
- assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[0].getrawtransaction, tx, True, "abcd1234")
- assert_raises_rpc_error(-8, "parameter 3 must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getrawtransaction, tx, True, "ZZZ0000000000000000000000000000000000000000000000000000000000000")
- assert_raises_rpc_error(-5, "Block hash not found", self.nodes[0].getrawtransaction, tx, True, "0000000000000000000000000000000000000000000000000000000000000000")
- # Undo the blocks and check in_active_chain
- self.nodes[0].invalidateblock(block1)
- gottx = self.nodes[0].getrawtransaction(txid=tx, verbose=True, blockhash=block1)
- assert_equal(gottx['in_active_chain'], False)
- self.nodes[0].reconsiderblock(block1)
- assert_equal(self.nodes[0].getbestblockhash(), block2)
-
- if not self.options.descriptors:
- # The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
- # The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here.
- #########################
- # RAW TX MULTISIG TESTS #
- #########################
- # 2of2 test
- addr1 = self.nodes[2].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[2].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
-
- # Tests for createmultisig and addmultisigaddress
- assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
- self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys
- assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here.
-
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
-
- #use balance deltas instead of absolute values
- bal = self.nodes[2].getbalance()
-
- # send 1.2 BTC to msig adr
- txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance
-
-
- # 2of3 test from different nodes
- bal = self.nodes[2].getbalance()
- addr1 = self.nodes[1].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
- addr3 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[1].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
- addr3Obj = self.nodes[2].getaddressinfo(addr3)
-
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
-
- txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
- decTx = self.nodes[0].gettransaction(txId)
- rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
-
- #THIS IS AN INCOMPLETE FEATURE
- #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
- assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable
-
- txDetails = self.nodes[0].gettransaction(txId, True)
- rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
- vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
-
- bal = self.nodes[0].getbalance()
- inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "amount" : vout['value']}]
- outputs = { self.nodes[0].getnewaddress() : 2.19 }
- rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs)
- assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
-
- rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs)
- assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys
- self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
- rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
-
- # 2of2 test for combining transactions
- bal = self.nodes[2].getbalance()
- addr1 = self.nodes[1].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[1].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
-
- self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
- mSigObjValid = self.nodes[2].getaddressinfo(mSigObj)
-
- txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
- decTx = self.nodes[0].gettransaction(txId)
- rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
-
- assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
-
- txDetails = self.nodes[0].gettransaction(txId, True)
- rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
- vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
-
- bal = self.nodes[0].getbalance()
- inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}]
- outputs = { self.nodes[0].getnewaddress() : 2.19 }
- rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs)
- self.log.debug(rawTxPartialSigned1)
- assert_equal(rawTxPartialSigned1['complete'], False) #node1 only has one key, can't comp. sign the tx
-
- rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs)
- self.log.debug(rawTxPartialSigned2)
- assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx
- rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
- self.log.debug(rawTxComb)
- self.nodes[2].sendrawtransaction(rawTxComb)
- rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
-
- # decoderawtransaction tests
- # witness transaction
- encrawtx = "010000000001010000000000000072c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000ffffffff0100e1f50500000000000102616100000000"
- decrawtx = self.nodes[0].decoderawtransaction(encrawtx, True) # decode as witness transaction
- assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
- assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, encrawtx, False) # force decode as non-witness transaction
- # non-witness transaction
- encrawtx = "01000000010000000000000072c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000ffffffff0100e1f505000000000000000000"
- decrawtx = self.nodes[0].decoderawtransaction(encrawtx, False) # decode as non-witness transaction
- assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
- # known ambiguous transaction in the chain (see https://github.com/bitcoin/bitcoin/issues/20579)
- encrawtx = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4b03c68708046ff8415c622f4254432e434f4d2ffabe6d6de1965d02c68f928e5b244ab1965115a36f56eb997633c7f690124bbf43644e23080000000ca3d3af6d005a65ff0200fd00000000ffffffff03f4c1fb4b0000000016001497cfc76442fe717f2a3f0cc9c175f7561b6619970000000000000000266a24aa21a9ed957d1036a80343e0d1b659497e1b48a38ebe876a056d45965fac4a85cda84e1900000000000000002952534b424c4f434b3a8e092581ab01986cbadc84f4b43f4fa4bb9e7a2e2a0caf9b7cf64d939028e22c0120000000000000000000000000000000000000000000000000000000000000000000000000"
- decrawtx = self.nodes[0].decoderawtransaction(encrawtx)
- decrawtx_wit = self.nodes[0].decoderawtransaction(encrawtx, True)
- assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, encrawtx, False) # fails to decode as non-witness transaction
- assert_equal(decrawtx, decrawtx_wit) # the witness interpretation should be chosen
- assert_equal(decrawtx['vin'][0]['coinbase'], "03c68708046ff8415c622f4254432e434f4d2ffabe6d6de1965d02c68f928e5b244ab1965115a36f56eb997633c7f690124bbf43644e23080000000ca3d3af6d005a65ff0200fd00000000")
-
- # Basic signrawtransaction test
- addr = self.nodes[1].getnewaddress()
- txid = self.nodes[0].sendtoaddress(addr, 10)
- self.nodes[0].generate(1)
- self.sync_all()
- vout = find_vout_for_address(self.nodes[1], txid, addr)
- rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999})
- rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx)
- txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex'])
- self.nodes[0].generate(1)
- self.sync_all()
-
- # getrawtransaction tests
- # 1. valid parameters - only supply txid
- assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex'])
-
- # 2. valid parameters - supply txid and 0 for non-verbose
- assert_equal(self.nodes[0].getrawtransaction(txId, 0), rawTxSigned['hex'])
-
- # 3. valid parameters - supply txid and False for non-verbose
- assert_equal(self.nodes[0].getrawtransaction(txId, False), rawTxSigned['hex'])
-
- # 4. valid parameters - supply txid and 1 for verbose.
- # We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
- assert_equal(self.nodes[0].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex'])
-
- # 5. valid parameters - supply txid and True for non-verbose
- assert_equal(self.nodes[0].getrawtransaction(txId, True)["hex"], rawTxSigned['hex'])
-
- # 6. invalid parameters - supply txid and string "Flase"
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase")
-
- # 7. invalid parameters - supply txid and empty array
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, [])
-
- # 8. invalid parameters - supply txid and empty dict
- assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, {})
-
- inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 1000}]
- outputs = { self.nodes[0].getnewaddress() : 1 }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- decrawtx= self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['vin'][0]['sequence'], 1000)
-
- # 9. invalid parameters - sequence number out of range
- inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : -1}]
- outputs = { self.nodes[0].getnewaddress() : 1 }
- assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs)
-
- # 10. invalid parameters - sequence number out of range
- inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 4294967296}]
- outputs = { self.nodes[0].getnewaddress() : 1 }
- assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs)
-
- inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 4294967294}]
- outputs = { self.nodes[0].getnewaddress() : 1 }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- decrawtx= self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['vin'][0]['sequence'], 4294967294)
-
- ####################################
- # TRANSACTION VERSION NUMBER TESTS #
- ####################################
-
- # Test the minimum transaction version number that fits in a signed 32-bit integer.
- # As transaction version is unsigned, this should convert to its unsigned equivalent.
- tx = CTransaction()
- tx.nVersion = -0x80000000
- rawtx = tx.serialize().hex()
- decrawtx = self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['version'], 0x80000000)
-
- # Test the maximum transaction version number that fits in a signed 32-bit integer.
- tx = CTransaction()
- tx.nVersion = 0x7fffffff
- rawtx = tx.serialize().hex()
- decrawtx = self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['version'], 0x7fffffff)
-
- self.log.info('sendrawtransaction/testmempoolaccept with maxfeerate')
+ def sendrawtransaction_testmempoolaccept_tests(self):
+ self.log.info("Test sendrawtransaction/testmempoolaccept with maxfeerate")
+ fee_exceeds_max = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
# Test a transaction with a small fee.
txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
@@ -473,9 +325,9 @@ class RawTransactionsTest(BitcoinTestFramework):
vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000'))
self.sync_all()
- inputs = [{ "txid" : txId, "vout" : vout['n'] }]
+ inputs = [{"txid": txId, "vout": vout['n']}]
# Fee 10,000 satoshis, (1 - (10000 sat * 0.00000001 BTC/sat)) = 0.9999
- outputs = { self.nodes[0].getnewaddress() : Decimal("0.99990000") }
+ outputs = {self.nodes[0].getnewaddress(): Decimal("0.99990000")}
rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx)
assert_equal(rawTxSigned['complete'], True)
@@ -485,7 +337,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
+ assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
# and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']])[0]
assert_equal(testres['allowed'], True)
@@ -497,9 +349,9 @@ class RawTransactionsTest(BitcoinTestFramework):
vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000'))
self.sync_all()
- inputs = [{ "txid" : txId, "vout" : vout['n'] }]
+ inputs = [{"txid": txId, "vout": vout['n']}]
# Fee 2,000,000 satoshis, (1 - (2000000 sat * 0.00000001 BTC/sat)) = 0.98
- outputs = { self.nodes[0].getnewaddress() : Decimal("0.98000000") }
+ outputs = {self.nodes[0].getnewaddress() : Decimal("0.98000000")}
rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx)
assert_equal(rawTxSigned['complete'], True)
@@ -509,13 +361,13 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex'])
+ assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex'])
# and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0]
assert_equal(testres['allowed'], True)
self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.20000000')
- self.log.info('sendrawtransaction/testmempoolaccept with tx that is already in the chain')
+ self.log.info("Test sendrawtransaction/testmempoolaccept with tx already in the chain")
self.nodes[2].generate(1)
self.sync_blocks()
for node in self.nodes:
@@ -524,6 +376,166 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(testres['reject-reason'], 'txn-already-known')
assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, rawTxSigned['hex'])
+ def decoderawtransaction_tests(self):
+ self.log.info("Test decoderawtransaction")
+ # witness transaction
+ encrawtx = "010000000001010000000000000072c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000ffffffff0100e1f50500000000000102616100000000"
+ decrawtx = self.nodes[0].decoderawtransaction(encrawtx, True) # decode as witness transaction
+ assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
+ assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, encrawtx, False) # force decode as non-witness transaction
+ # non-witness transaction
+ encrawtx = "01000000010000000000000072c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000ffffffff0100e1f505000000000000000000"
+ decrawtx = self.nodes[0].decoderawtransaction(encrawtx, False) # decode as non-witness transaction
+ assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
+ # known ambiguous transaction in the chain (see https://github.com/bitcoin/bitcoin/issues/20579)
+ coinbase = "03c68708046ff8415c622f4254432e434f4d2ffabe6d6de1965d02c68f928e5b244ab1965115a36f56eb997633c7f690124bbf43644e23080000000ca3d3af6d005a65ff0200fd00000000"
+ encrawtx = f"020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4b{coinbase}" \
+ "ffffffff03f4c1fb4b0000000016001497cfc76442fe717f2a3f0cc9c175f7561b6619970000000000000000266a24aa21a9ed957d1036a80343e0d1b659497e1b48a38ebe876a056d45965fac4a85cda84e1900000000000000002952534b424c4f434b3a8e092581ab01986cbadc84f4b43f4fa4bb9e7a2e2a0caf9b7cf64d939028e22c0120000000000000000000000000000000000000000000000000000000000000000000000000"
+ decrawtx = self.nodes[0].decoderawtransaction(encrawtx)
+ decrawtx_wit = self.nodes[0].decoderawtransaction(encrawtx, True)
+ assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, encrawtx, False) # fails to decode as non-witness transaction
+ assert_equal(decrawtx, decrawtx_wit) # the witness interpretation should be chosen
+ assert_equal(decrawtx['vin'][0]['coinbase'], coinbase)
+
+ def transaction_version_number_tests(self):
+ self.log.info("Test transaction version numbers")
+
+ # Test the minimum transaction version number that fits in a signed 32-bit integer.
+ # As transaction version is unsigned, this should convert to its unsigned equivalent.
+ tx = CTransaction()
+ tx.nVersion = -0x80000000
+ rawtx = tx.serialize().hex()
+ decrawtx = self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['version'], 0x80000000)
+
+ # Test the maximum transaction version number that fits in a signed 32-bit integer.
+ tx = CTransaction()
+ tx.nVersion = 0x7fffffff
+ rawtx = tx.serialize().hex()
+ decrawtx = self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['version'], 0x7fffffff)
+
+ def raw_multisig_transaction_legacy_tests(self):
+ self.log.info("Test raw multisig transactions (legacy)")
+ # The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
+ # The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here.
+
+ # 2of2 test
+ addr1 = self.nodes[2].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[2].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+
+ # Tests for createmultisig and addmultisigaddress
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
+ # createmultisig can only take public keys
+ self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1])
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
+
+ # use balance deltas instead of absolute values
+ bal = self.nodes[2].getbalance()
+
+ # send 1.2 BTC to msig adr
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ # node2 has both keys of the 2of2 ms addr, tx should affect the balance
+ assert_equal(self.nodes[2].getbalance(), bal + Decimal('1.20000000'))
+
+
+ # 2of3 test from different nodes
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+ addr3 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+ addr3Obj = self.nodes[2].getaddressinfo(addr3)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # THIS IS AN INCOMPLETE FEATURE
+ # NODE2 HAS TWO OF THREE KEYS AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
+ assert_equal(self.nodes[2].getbalance(), bal) # for now, assume the funds of a 2of3 multisig tx are not marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "amount": vout['value']}]
+ outputs = {self.nodes[0].getnewaddress(): 2.19}
+ rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs)
+ assert_equal(rawTxPartialSigned['complete'], False) # node1 only has one key, can't comp. sign the tx
+
+ rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs)
+ assert_equal(rawTxSigned['complete'], True) # node2 can sign the tx compl., own two of three keys
+ self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
+ rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx
+
+ # 2of2 test for combining transactions
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+
+ self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObjValid = self.nodes[2].getaddressinfo(mSigObj)
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "redeemScript": mSigObjValid['hex'], "amount": vout['value']}]
+ outputs = {self.nodes[0].getnewaddress(): 2.19}
+ rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs)
+ self.log.debug(rawTxPartialSigned1)
+ assert_equal(rawTxPartialSigned1['complete'], False) # node1 only has one key, can't comp. sign the tx
+
+ rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs)
+ self.log.debug(rawTxPartialSigned2)
+ assert_equal(rawTxPartialSigned2['complete'], False) # node2 only has one key, can't comp. sign the tx
+ rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
+ self.log.debug(rawTxComb)
+ self.nodes[2].sendrawtransaction(rawTxComb)
+ rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx
+
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/test/functional/rpc_signmessage.py b/test/functional/rpc_signmessagewithprivkey.py
index 1c71732a61..95beba8730 100755
--- a/test/functional/rpc_signmessage.py
+++ b/test/functional/rpc_signmessagewithprivkey.py
@@ -2,7 +2,7 @@
# Copyright (c) 2016-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Test RPC commands for signing and verifying messages."""
+"""Test RPC commands for signing messages with private key."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -10,14 +10,10 @@ from test_framework.util import (
assert_raises_rpc_error,
)
-class SignMessagesTest(BitcoinTestFramework):
+class SignMessagesWithPrivTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
- self.extra_args = [["-addresstype=legacy"]]
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
def run_test(self):
message = 'This is just a test message'
@@ -30,33 +26,20 @@ class SignMessagesTest(BitcoinTestFramework):
assert_equal(expected_signature, signature)
assert self.nodes[0].verifymessage(address, signature, message)
- self.log.info('test signing with an address with wallet')
- address = self.nodes[0].getnewaddress()
- signature = self.nodes[0].signmessage(address, message)
- assert self.nodes[0].verifymessage(address, signature, message)
-
- self.log.info('test verifying with another address should not work')
- other_address = self.nodes[0].getnewaddress()
- other_signature = self.nodes[0].signmessage(other_address, message)
- assert not self.nodes[0].verifymessage(other_address, signature, message)
- assert not self.nodes[0].verifymessage(address, other_signature, message)
-
self.log.info('test parameter validity and error codes')
- # signmessage(withprivkey) have two required parameters
+ # signmessagewithprivkey has two required parameters
for num_params in [0, 1, 3, 4, 5]:
param_list = ["dummy"]*num_params
assert_raises_rpc_error(-1, "signmessagewithprivkey", self.nodes[0].signmessagewithprivkey, *param_list)
- assert_raises_rpc_error(-1, "signmessage", self.nodes[0].signmessage, *param_list)
# verifymessage has three required parameters
for num_params in [0, 1, 2, 4, 5]:
param_list = ["dummy"]*num_params
assert_raises_rpc_error(-1, "verifymessage", self.nodes[0].verifymessage, *param_list)
# invalid key or address provided
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[0].signmessagewithprivkey, "invalid_key", message)
- assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].signmessage, "invalid_addr", message)
assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].verifymessage, "invalid_addr", signature, message)
# malformed signature provided
- assert_raises_rpc_error(-3, "Malformed base64 encoding", self.nodes[0].verifymessage, self.nodes[0].getnewaddress(), "invalid_sig", message)
+ assert_raises_rpc_error(-3, "Malformed base64 encoding", self.nodes[0].verifymessage, 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB', "invalid_sig", message)
if __name__ == '__main__':
- SignMessagesTest().main()
+ SignMessagesWithPrivTest().main()
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py
index 312a4abbc3..e6fef7e838 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/rpc_signrawtransaction.py
@@ -5,7 +5,6 @@
"""Test transaction signing using the signrawtransaction* RPCs."""
from test_framework.blocktools import (
- CLTV_HEIGHT,
COINBASE_MATURITY,
CSV_ACTIVATION_HEIGHT,
)
@@ -274,7 +273,7 @@ class SignRawTransactionsTest(BitcoinTestFramework):
getcontext().prec = 8
# Make sure CSV is active
- generate_to_height(self.nodes[0], CSV_ACTIVATION_HEIGHT)
+ generate_to_height(self, self.nodes[0], CSV_ACTIVATION_HEIGHT)
assert self.nodes[0].getblockchaininfo()['softforks']['csv']['active']
# Create a P2WSH script with CSV
@@ -310,11 +309,10 @@ class SignRawTransactionsTest(BitcoinTestFramework):
getcontext().prec = 8
# Make sure CLTV is active
- generate_to_height(self.nodes[0], CLTV_HEIGHT)
assert self.nodes[0].getblockchaininfo()['softforks']['bip65']['active']
# Create a P2WSH script with CLTV
- script = CScript([1000, OP_CHECKLOCKTIMEVERIFY, OP_DROP])
+ script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP])
address = script_to_p2wsh(script)
# Fund that address and make the spend
diff --git a/test/functional/test_framework/bdb.py b/test/functional/test_framework/bdb.py
index 97b9c1d6d0..d623bcdf6e 100644
--- a/test/functional/test_framework/bdb.py
+++ b/test/functional/test_framework/bdb.py
@@ -24,7 +24,6 @@ transactions.
`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
"""
-import binascii
import struct
# Important constants
@@ -96,7 +95,7 @@ def dump_meta_page(page):
metadata['key_count'] = key_count
metadata['record_count'] = record_count
metadata['flags'] = flags
- metadata['uid'] = binascii.hexlify(uid)
+ metadata['uid'] = uid.hex().encode()
assert magic == BTREE_MAGIC, 'bdb magic does not match bdb btree magic'
assert pg_type == BTREE_META, 'Metadata page is not a btree metadata page'
@@ -110,8 +109,9 @@ def dump_meta_page(page):
metadata['re_pad'] = re_pad
metadata['root'] = root
metadata['crypto_magic'] = crypto_magic
- metadata['iv'] = binascii.hexlify(iv)
- metadata['chksum'] = binascii.hexlify(chksum)
+ metadata['iv'] = iv.hex().encode()
+ metadata['chksum'] = chksum.hex().encode()
+
return metadata
# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index a88a119e6f..25b36b6a91 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions."""
-from binascii import a2b_hex
import struct
import time
import unittest
@@ -55,7 +54,8 @@ TIME_GENESIS_BLOCK = 1296688602
COINBASE_MATURITY = 100
# Soft-fork activation heights
-CLTV_HEIGHT = 1351
+DERSIG_HEIGHT = 102 # BIP 66
+CLTV_HEIGHT = 111 # BIP 65
CSV_ACTIVATION_HEIGHT = 432
# From BIP141
@@ -74,7 +74,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl
block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600)
block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10)
if tmpl and not tmpl.get('bits') is None:
- block.nBits = struct.unpack('>I', a2b_hex(tmpl['bits']))[0]
+ block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0]
else:
block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
if coinbase is None:
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 6e57107f86..65d90f8448 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -19,7 +19,6 @@ Classes use __slots__ to ensure extraneous attributes aren't accidentally added
by tests, compromising their intended effect.
"""
from base64 import b32decode, b32encode
-from codecs import encode
import copy
import hashlib
from io import BytesIO
@@ -681,7 +680,7 @@ class CBlockHeader:
r += struct.pack("<I", self.nBits)
r += struct.pack("<I", self.nNonce)
self.sha256 = uint256_from_str(hash256(r))
- self.hash = encode(hash256(r)[::-1], 'hex_codec').decode('ascii')
+ self.hash = hash256(r)[::-1].hex()
def rehash(self):
self.sha256 = None
diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py
index 5dc723c1d5..b5f78e0cf3 100644
--- a/test/functional/test_framework/netutil.py
+++ b/test/functional/test_framework/netutil.py
@@ -12,7 +12,6 @@ import socket
import struct
import array
import os
-from binascii import unhexlify
# STATE_ESTABLISHED = '01'
# STATE_SYN_SENT = '02'
@@ -44,7 +43,7 @@ def _remove_empty(array):
def _convert_ip_port(array):
host,port = array.split(':')
# convert host from mangled-per-four-bytes form as used by kernel
- host = unhexlify(host)
+ host = bytes.fromhex(host)
host_out = ''
for x in range(0, len(host) // 4):
(val,) = struct.unpack('=I', host[x*4:(x+1)*4])
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 6d8e6ef45c..f382e0fdb3 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -410,7 +410,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# To ensure that all nodes are out of IBD, the most recent block
# must have a timestamp not too old (see IsInitialBlockDownload()).
self.log.debug('Generate a block with current time')
- block_hash = self.nodes[0].generate(1)[0]
+ block_hash = self.generate(self.nodes[0], 1)[0]
block = self.nodes[0].getblock(blockhash=block_hash, verbosity=0)
for n in self.nodes:
n.submitblock(block)
@@ -619,6 +619,22 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.connect_nodes(1, 2)
self.sync_all()
+ def generate(self, generator, *args, **kwargs):
+ blocks = generator.generate(*args, **kwargs)
+ return blocks
+
+ def generateblock(self, generator, *args, **kwargs):
+ blocks = generator.generateblock(*args, **kwargs)
+ return blocks
+
+ def generatetoaddress(self, generator, *args, **kwargs):
+ blocks = generator.generatetoaddress(*args, **kwargs)
+ return blocks
+
+ def generatetodescriptor(self, generator, *args, **kwargs):
+ blocks = generator.generatetodescriptor(*args, **kwargs)
+ return blocks
+
def sync_blocks(self, nodes=None, wait=1, timeout=60):
"""
Wait until everybody has the same tip.
@@ -749,7 +765,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [ADDRESS_BCRT1_P2WSH_OP_TRUE]
assert_equal(len(gen_addresses), 4)
for i in range(8):
- cache_node.generatetoaddress(
+ self.generatetoaddress(
+ cache_node,
nblocks=25 if i != 7 else 24,
address=gen_addresses[i % len(gen_addresses)],
)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 54f2fdee21..ec27fd7f85 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -445,10 +445,10 @@ def find_output(node, txid, amount, *, blockhash=None):
# Helper to create at least "count" utxos
# Pass in a fee that is sufficient for relay and mining new transactions.
-def create_confirmed_utxos(fee, node, count):
+def create_confirmed_utxos(test_framework, fee, node, count):
to_generate = int(0.5 * count) + 101
while to_generate > 0:
- node.generate(min(25, to_generate))
+ test_framework.generate(node, min(25, to_generate))
to_generate -= 25
utxos = node.listunspent()
iterations = count - len(utxos)
@@ -469,7 +469,7 @@ def create_confirmed_utxos(fee, node, count):
node.sendrawtransaction(signed_tx)
while (node.getmempoolinfo()['size'] > 0):
- node.generate(1)
+ test_framework.generate(node, 1)
utxos = node.listunspent()
assert len(utxos) >= count
@@ -541,7 +541,7 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
return txids
-def mine_large_block(node, utxos=None):
+def mine_large_block(test_framework, node, utxos=None):
# generate a 66k transaction,
# and 14 of them is close to the 1MB block limit
num = 14
@@ -552,17 +552,17 @@ def mine_large_block(node, utxos=None):
utxos.extend(node.listunspent())
fee = 100 * node.getnetworkinfo()["relayfee"]
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
- node.generate(1)
+ test_framework.generate(node, 1)
-def generate_to_height(node, target_height):
+def generate_to_height(test_framework, node, target_height):
"""Generates blocks until a given target block height has been reached.
To prevent timeouts, only up to 200 blocks are generated per RPC call.
Can be used to activate certain soft-forks (e.g. CSV, CLTV)."""
current_height = node.getblockcount()
while current_height < target_height:
nblocks = min(200, target_height - current_height)
- current_height += len(node.generate(nblocks))
+ current_height += len(test_framework.generate(node, nblocks))
assert_equal(node.getblockcount(), target_height)
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 609553c6d0..ba5b95f930 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -4,8 +4,10 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""A limited-functionality wallet, which may replace a real wallet in tests"""
+from copy import deepcopy
from decimal import Decimal
from enum import Enum
+from random import choice
from typing import Optional
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE
from test_framework.key import ECKey
@@ -16,6 +18,7 @@ from test_framework.messages import (
CTxIn,
CTxInWitness,
CTxOut,
+ tx_from_hex,
)
from test_framework.script import (
CScript,
@@ -27,9 +30,11 @@ from test_framework.script import (
)
from test_framework.util import (
assert_equal,
+ assert_greater_than_or_equal,
satoshi_round,
)
+DEFAULT_FEE = Decimal("0.0001")
class MiniWalletMode(Enum):
"""Determines the transaction type the MiniWallet is creating and spending.
@@ -176,3 +181,75 @@ class MiniWallet:
def sendrawtransaction(self, *, from_node, tx_hex):
from_node.sendrawtransaction(tx_hex)
self.scan_tx(from_node.decoderawtransaction(tx_hex))
+
+def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
+ """Build a transaction that spends parent_txid.vout[n] and produces one output with
+ amount = parent_value with a fee deducted.
+ Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created).
+ """
+ inputs = [{"txid": parent_txid, "vout": n}]
+ my_value = parent_value - fee
+ outputs = {address : my_value}
+ rawtx = node.createrawtransaction(inputs, outputs)
+ prevtxs = [{
+ "txid": parent_txid,
+ "vout": n,
+ "scriptPubKey": parent_locking_script,
+ "amount": parent_value,
+ }] if parent_locking_script else None
+ signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=privkeys, prevtxs=prevtxs)
+ assert signedtx["complete"]
+ tx = tx_from_hex(signedtx["hex"])
+ return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex())
+
+def create_child_with_parents(node, address, privkeys, parents_tx, values, locking_scripts, fee=DEFAULT_FEE):
+ """Creates a transaction that spends the first output of each parent in parents_tx."""
+ num_parents = len(parents_tx)
+ total_value = sum(values)
+ inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx]
+ outputs = {address : total_value - fee}
+ rawtx_child = node.createrawtransaction(inputs, outputs)
+ prevtxs = []
+ for i in range(num_parents):
+ prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]})
+ signedtx_child = node.signrawtransactionwithkey(hexstring=rawtx_child, privkeys=privkeys, prevtxs=prevtxs)
+ assert signedtx_child["complete"]
+ return signedtx_child["hex"]
+
+def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
+ """Helper function: create a "chain" of chain_length transactions. The nth transaction in the
+ chain is a child of the n-1th transaction and parent of the n+1th transaction.
+ """
+ parent_locking_script = None
+ txid = first_coin["txid"]
+ chain_hex = []
+ chain_txns = []
+ value = first_coin["amount"]
+
+ for _ in range(chain_length):
+ (tx, txhex, value, parent_locking_script) = make_chain(node, address, privkeys, txid, value, 0, parent_locking_script)
+ txid = tx.rehash()
+ chain_hex.append(txhex)
+ chain_txns.append(tx)
+
+ return (chain_hex, chain_txns)
+
+def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None):
+ """Pad a transaction with extra outputs until it reaches a target weight (or higher).
+ returns CTransaction object
+ """
+ tx_heavy = deepcopy(tx)
+ assert_greater_than_or_equal(target_weight, tx_heavy.get_weight())
+ while tx_heavy.get_weight() < target_weight:
+ random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
+ for _ in range(512*2):
+ random_spk += choice("0123456789ABCDEF")
+ tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk)))
+ # Re-sign the transaction
+ if privkeys:
+ signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs)
+ return tx_from_hex(signed["hex"])
+ # OP_TRUE
+ tx_heavy.wit.vtxinwit = [CTxInWitness()]
+ tx_heavy.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
+ return tx_heavy
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index fecf52d53a..d6d676da1f 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -218,13 +218,15 @@ BASE_SCRIPTS = [
'rpc_createmultisig.py --legacy-wallet',
'rpc_createmultisig.py --descriptors',
'rpc_packages.py',
+ 'mempool_package_limits.py',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
'wallet_importprunedfunds.py --legacy-wallet',
'wallet_importprunedfunds.py --descriptors',
'p2p_leak_tx.py',
'p2p_eviction.py',
- 'rpc_signmessage.py',
+ 'wallet_signmessagewithaddress.py',
+ 'rpc_signmessagewithprivkey.py',
'rpc_generateblock.py',
'rpc_generate.py',
'wallet_balance.py --legacy-wallet',
diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py
index 05a0ef0ea1..c7a983556d 100755
--- a/test/functional/wallet_backup.py
+++ b/test/functional/wallet_backup.py
@@ -111,6 +111,18 @@ class WalletBackupTest(BitcoinTestFramework):
os.remove(os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
os.remove(os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ def restore_nonexistent_wallet(self):
+ node = self.nodes[3]
+ nonexistent_wallet_file = os.path.join(self.nodes[0].datadir, 'nonexistent_wallet.bak')
+ wallet_name = "res0"
+ assert_raises_rpc_error(-8, "Backup file does not exist", node.restorewallet, wallet_name, nonexistent_wallet_file)
+
+ def restore_wallet_existent_name(self):
+ node = self.nodes[3]
+ wallet_file = os.path.join(self.nodes[0].datadir, 'wallet.bak')
+ wallet_name = "res0"
+ assert_raises_rpc_error(-8, "Wallet name already exists.", node.restorewallet, wallet_name, wallet_file)
+
def init_three(self):
self.init_wallet(0)
self.init_wallet(1)
@@ -169,26 +181,27 @@ class WalletBackupTest(BitcoinTestFramework):
##
# Test restoring spender wallets from backups
##
- self.log.info("Restoring using wallet.dat")
- self.stop_three()
- self.erase_three()
+ self.log.info("Restoring wallets on node 3 using backup files")
- # Start node2 with no chain
- shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'blocks'))
- shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'chainstate'))
+ self.restore_nonexistent_wallet()
- # Restore wallets from backup
- shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
- shutil.copyfile(os.path.join(self.nodes[1].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
- shutil.copyfile(os.path.join(self.nodes[2].datadir, 'wallet.bak'), os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ backup_file_0 = os.path.join(self.nodes[0].datadir, 'wallet.bak')
+ backup_file_1 = os.path.join(self.nodes[1].datadir, 'wallet.bak')
+ backup_file_2 = os.path.join(self.nodes[2].datadir, 'wallet.bak')
- self.log.info("Re-starting nodes")
- self.start_three()
- self.sync_blocks()
+ self.nodes[3].restorewallet("res0", backup_file_0)
+ self.nodes[3].restorewallet("res1", backup_file_1)
+ self.nodes[3].restorewallet("res2", backup_file_2)
+
+ res0_rpc = self.nodes[3].get_wallet_rpc("res0")
+ res1_rpc = self.nodes[3].get_wallet_rpc("res1")
+ res2_rpc = self.nodes[3].get_wallet_rpc("res2")
+
+ assert_equal(res0_rpc.getbalance(), balance0)
+ assert_equal(res1_rpc.getbalance(), balance1)
+ assert_equal(res2_rpc.getbalance(), balance2)
- assert_equal(self.nodes[0].getbalance(), balance0)
- assert_equal(self.nodes[1].getbalance(), balance1)
- assert_equal(self.nodes[2].getbalance(), balance2)
+ self.restore_wallet_existent_name()
if not self.options.descriptors:
self.log.info("Restoring using dumped wallet")
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index c6f5d334f8..17a4c79da3 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -84,7 +84,7 @@ class WalletDescriptorTest(BitcoinTestFramework):
send_wrpc = self.nodes[0].get_wallet_rpc("desc1")
# Generate some coins
- send_wrpc.generatetoaddress(COINBASE_MATURITY + 1, send_wrpc.getnewaddress())
+ self.generatetoaddress(send_wrpc, COINBASE_MATURITY + 1, send_wrpc.getnewaddress())
# Make transactions
self.log.info("Test sending and receiving")
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 262175c789..4cb311983c 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -74,7 +74,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
self.log.info('Mining coins')
- w0.generatetoaddress(COINBASE_MATURITY + 1, w0.getnewaddress())
+ self.generatetoaddress(w0, COINBASE_MATURITY + 1, w0.getnewaddress())
# RPC importdescriptors -----------------------------------------------
@@ -405,7 +405,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
solvable=True,
ismine=True)
txid = w0.sendtoaddress(address, 49.99995540)
- w0.generatetoaddress(6, w0.getnewaddress())
+ self.generatetoaddress(w0, 6, w0.getnewaddress())
self.sync_blocks()
tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], {w0.getnewaddress(): 49.999})
signed_tx = wpriv.signrawtransactionwithwallet(tx)
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
index c2565d84f6..221f5262d9 100755
--- a/test/functional/wallet_listdescriptors.py
+++ b/test/functional/wallet_listdescriptors.py
@@ -72,11 +72,39 @@ class ListDescriptorsTest(BitcoinTestFramework):
],
}
assert_equal(expected, wallet.listdescriptors())
+ assert_equal(expected, wallet.listdescriptors(False))
+
+ self.log.info('Test list private descriptors')
+ expected_private = {
+ 'wallet_name': 'w2',
+ 'descriptors': [
+ {'desc': descsum_create('wpkh(' + xprv + hardened_path + '/0/*)'),
+ 'timestamp': 1296688602,
+ 'active': False,
+ 'range': [0, 0],
+ 'next': 0},
+ ],
+ }
+ assert_equal(expected_private, wallet.listdescriptors(True))
self.log.info("Test listdescriptors with encrypted wallet")
wallet.encryptwallet("pass")
assert_equal(expected, wallet.listdescriptors())
+ self.log.info('Test list private descriptors with encrypted wallet')
+ assert_raises_rpc_error(-13, 'Please enter the wallet passphrase with walletpassphrase first.', wallet.listdescriptors, True)
+ wallet.walletpassphrase(passphrase="pass", timeout=1000000)
+ assert_equal(expected_private, wallet.listdescriptors(True))
+
+ self.log.info('Test list private descriptors with watch-only wallet')
+ node.createwallet(wallet_name='watch-only', descriptors=True, disable_private_keys=True)
+ watch_only_wallet = node.get_wallet_rpc('watch-only')
+ watch_only_wallet.importdescriptors([{
+ 'desc': descsum_create('wpkh(' + xpub_acc + ')'),
+ 'timestamp': 1296688602,
+ }])
+ assert_raises_rpc_error(-4, 'Can\'t get descriptor string', watch_only_wallet.listdescriptors, True)
+
self.log.info('Test non-active non-range combo descriptor')
node.createwallet(wallet_name='w4', blank=True, descriptors=True)
wallet = node.get_wallet_rpc('w4')
diff --git a/test/functional/wallet_signmessagewithaddress.py b/test/functional/wallet_signmessagewithaddress.py
new file mode 100755
index 0000000000..bf6f95e3f1
--- /dev/null
+++ b/test/functional/wallet_signmessagewithaddress.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016-2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test Wallet commands for signing and verifying messages."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_raises_rpc_error,
+)
+
+class SignMessagesWithAddressTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.extra_args = [["-addresstype=legacy"]]
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ message = 'This is just a test message'
+
+ self.log.info('test signing with an address with wallet')
+ address = self.nodes[0].getnewaddress()
+ signature = self.nodes[0].signmessage(address, message)
+ assert self.nodes[0].verifymessage(address, signature, message)
+
+ self.log.info('test verifying with another address should not work')
+ other_address = self.nodes[0].getnewaddress()
+ other_signature = self.nodes[0].signmessage(other_address, message)
+ assert not self.nodes[0].verifymessage(other_address, signature, message)
+ assert not self.nodes[0].verifymessage(address, other_signature, message)
+
+ self.log.info('test parameter validity and error codes')
+ # signmessage has two required parameters
+ for num_params in [0, 1, 3, 4, 5]:
+ param_list = ["dummy"]*num_params
+ assert_raises_rpc_error(-1, "signmessage", self.nodes[0].signmessage, *param_list)
+ # invalid key or address provided
+ assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].signmessage, "invalid_addr", message)
+
+
+if __name__ == '__main__':
+ SignMessagesWithAddressTest().main()
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index df5051720b..233381f2d9 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -15,6 +15,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"index/base -> validation -> index/blockfilterindex -> index/base"
"index/coinstatsindex -> node/coinstats -> index/coinstatsindex"
"policy/fees -> txmempool -> policy/fees"
+ "policy/rbf -> txmempool -> validation -> policy/rbf"
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel"
"qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog"
diff --git a/test/util/bitcoin-util-test.py b/test/util/bitcoin-util-test.py
index 7b1cc2b031..aa8fd6eee5 100755
--- a/test/util/bitcoin-util-test.py
+++ b/test/util/bitcoin-util-test.py
@@ -10,7 +10,6 @@ Runs automatically during `make check`.
Can also be run manually."""
import argparse
-import binascii
import configparser
import difflib
import json
@@ -167,7 +166,7 @@ def parse_output(a, fmt):
if fmt == 'json': # json: compare parsed data
return json.loads(a)
elif fmt == 'hex': # hex: parse and compare binary data
- return binascii.a2b_hex(a.strip())
+ return bytes.fromhex(a.strip())
else:
raise NotImplementedError("Don't know how to compare %s" % fmt)