aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml15
-rw-r--r--CONTRIBUTING.md114
-rwxr-xr-xci/test/00_setup_env.sh3
-rw-r--r--ci/test/00_setup_env_mac.sh2
-rw-r--r--ci/test/00_setup_env_mac_host.sh11
-rw-r--r--ci/test/00_setup_env_native_tsan.sh2
-rwxr-xr-xci/test/04_install.sh1
-rwxr-xr-xci/test/05_before_script.sh2
-rw-r--r--configure.ac5
-rwxr-xr-xcontrib/devtools/previous_release.py217
-rwxr-xr-xcontrib/devtools/previous_release.sh152
-rwxr-xr-xcontrib/devtools/test_deterministic_coverage.sh1
-rw-r--r--depends/Makefile19
-rw-r--r--depends/README.md51
-rw-r--r--depends/builders/darwin.mk1
-rw-r--r--depends/funcs.mk4
-rw-r--r--depends/hosts/darwin.mk34
-rw-r--r--depends/packages/native_cctools.mk41
-rw-r--r--depends/packages/native_cdrkit.mk4
-rw-r--r--depends/packages/qt.mk1
-rw-r--r--doc/files.md3
-rw-r--r--doc/reduce-traffic.md4
-rw-r--r--doc/release-notes-11413.md11
-rw-r--r--doc/release-notes-16377.md9
-rw-r--r--doc/release-notes-16525.md9
-rw-r--r--doc/release-notes-16528.md0
-rw-r--r--doc/release-notes-18594.md5
-rw-r--r--doc/release-notes-18918.md0
-rw-r--r--doc/release-notes-19133.md7
-rw-r--r--doc/release-notes-19200.md7
-rw-r--r--doc/release-notes-19219.md23
-rw-r--r--doc/release-notes-19469.md6
-rw-r--r--doc/release-notes.md60
-rw-r--r--share/examples/bitcoin.conf4
-rw-r--r--src/Makefile.qt.include6
-rw-r--r--src/Makefile.test.include77
-rw-r--r--src/bench/bench_bitcoin.cpp2
-rw-r--r--src/bitcoin-cli.cpp10
-rw-r--r--src/bitcoind.cpp5
-rw-r--r--src/consensus/validation.h36
-rw-r--r--src/core_write.cpp4
-rw-r--r--src/init.cpp16
-rw-r--r--src/interfaces/chain.h3
-rw-r--r--src/interfaces/node.cpp1
-rw-r--r--src/interfaces/node.h5
-rw-r--r--src/interfaces/wallet.cpp11
-rw-r--r--src/net.cpp10
-rw-r--r--src/net.h9
-rw-r--r--src/net_processing.cpp511
-rw-r--r--src/net_processing.h5
-rw-r--r--src/netaddress.cpp10
-rw-r--r--src/netaddress.h6
-rw-r--r--src/node/context.h2
-rw-r--r--src/node/transaction.cpp5
-rw-r--r--src/policy/fees.h2
-rw-r--r--src/protocol.cpp4
-rw-r--r--src/protocol.h19
-rw-r--r--src/qt/bitcoin.cpp7
-rw-r--r--src/qt/bitcoin.qrc74
-rw-r--r--src/qt/bitcoingui.cpp6
-rw-r--r--src/qt/forms/debugwindow.ui67
-rw-r--r--src/qt/guiutil.cpp22
-rw-r--r--src/qt/guiutil.h15
-rw-r--r--src/qt/modaloverlay.cpp2
-rw-r--r--src/qt/modaloverlay.h10
-rwxr-xr-xsrc/qt/res/animation/makespinner.sh (renamed from src/qt/res/movies/makespinner.sh)0
-rw-r--r--src/qt/res/animation/spinner-000.png (renamed from src/qt/res/movies/spinner-000.png)bin1794 -> 1794 bytes
-rw-r--r--src/qt/res/animation/spinner-001.png (renamed from src/qt/res/movies/spinner-001.png)bin2376 -> 2376 bytes
-rw-r--r--src/qt/res/animation/spinner-002.png (renamed from src/qt/res/movies/spinner-002.png)bin2376 -> 2376 bytes
-rw-r--r--src/qt/res/animation/spinner-003.png (renamed from src/qt/res/movies/spinner-003.png)bin2355 -> 2355 bytes
-rw-r--r--src/qt/res/animation/spinner-004.png (renamed from src/qt/res/movies/spinner-004.png)bin2349 -> 2349 bytes
-rw-r--r--src/qt/res/animation/spinner-005.png (renamed from src/qt/res/movies/spinner-005.png)bin2305 -> 2305 bytes
-rw-r--r--src/qt/res/animation/spinner-006.png (renamed from src/qt/res/movies/spinner-006.png)bin2304 -> 2304 bytes
-rw-r--r--src/qt/res/animation/spinner-007.png (renamed from src/qt/res/movies/spinner-007.png)bin2283 -> 2283 bytes
-rw-r--r--src/qt/res/animation/spinner-008.png (renamed from src/qt/res/movies/spinner-008.png)bin2312 -> 2312 bytes
-rw-r--r--src/qt/res/animation/spinner-009.png (renamed from src/qt/res/movies/spinner-009.png)bin1810 -> 1810 bytes
-rw-r--r--src/qt/res/animation/spinner-010.png (renamed from src/qt/res/movies/spinner-010.png)bin2305 -> 2305 bytes
-rw-r--r--src/qt/res/animation/spinner-011.png (renamed from src/qt/res/movies/spinner-011.png)bin2338 -> 2338 bytes
-rw-r--r--src/qt/res/animation/spinner-012.png (renamed from src/qt/res/movies/spinner-012.png)bin2352 -> 2352 bytes
-rw-r--r--src/qt/res/animation/spinner-013.png (renamed from src/qt/res/movies/spinner-013.png)bin2377 -> 2377 bytes
-rw-r--r--src/qt/res/animation/spinner-014.png (renamed from src/qt/res/movies/spinner-014.png)bin2358 -> 2358 bytes
-rw-r--r--src/qt/res/animation/spinner-015.png (renamed from src/qt/res/movies/spinner-015.png)bin2405 -> 2405 bytes
-rw-r--r--src/qt/res/animation/spinner-016.png (renamed from src/qt/res/movies/spinner-016.png)bin2429 -> 2429 bytes
-rw-r--r--src/qt/res/animation/spinner-017.png (renamed from src/qt/res/movies/spinner-017.png)bin2408 -> 2408 bytes
-rw-r--r--src/qt/res/animation/spinner-018.png (renamed from src/qt/res/movies/spinner-018.png)bin1831 -> 1831 bytes
-rw-r--r--src/qt/res/animation/spinner-019.png (renamed from src/qt/res/movies/spinner-019.png)bin2380 -> 2380 bytes
-rw-r--r--src/qt/res/animation/spinner-020.png (renamed from src/qt/res/movies/spinner-020.png)bin2366 -> 2366 bytes
-rw-r--r--src/qt/res/animation/spinner-021.png (renamed from src/qt/res/movies/spinner-021.png)bin2368 -> 2368 bytes
-rw-r--r--src/qt/res/animation/spinner-022.png (renamed from src/qt/res/movies/spinner-022.png)bin2356 -> 2356 bytes
-rw-r--r--src/qt/res/animation/spinner-023.png (renamed from src/qt/res/movies/spinner-023.png)bin2311 -> 2311 bytes
-rw-r--r--src/qt/res/animation/spinner-024.png (renamed from src/qt/res/movies/spinner-024.png)bin2315 -> 2315 bytes
-rw-r--r--src/qt/res/animation/spinner-025.png (renamed from src/qt/res/movies/spinner-025.png)bin2298 -> 2298 bytes
-rw-r--r--src/qt/res/animation/spinner-026.png (renamed from src/qt/res/movies/spinner-026.png)bin2291 -> 2291 bytes
-rw-r--r--src/qt/res/animation/spinner-027.png (renamed from src/qt/res/movies/spinner-027.png)bin1816 -> 1816 bytes
-rw-r--r--src/qt/res/animation/spinner-028.png (renamed from src/qt/res/movies/spinner-028.png)bin2308 -> 2308 bytes
-rw-r--r--src/qt/res/animation/spinner-029.png (renamed from src/qt/res/movies/spinner-029.png)bin2356 -> 2356 bytes
-rw-r--r--src/qt/res/animation/spinner-030.png (renamed from src/qt/res/movies/spinner-030.png)bin2346 -> 2346 bytes
-rw-r--r--src/qt/res/animation/spinner-031.png (renamed from src/qt/res/movies/spinner-031.png)bin2380 -> 2380 bytes
-rw-r--r--src/qt/res/animation/spinner-032.png (renamed from src/qt/res/movies/spinner-032.png)bin2345 -> 2345 bytes
-rw-r--r--src/qt/res/animation/spinner-033.png (renamed from src/qt/res/movies/spinner-033.png)bin2401 -> 2401 bytes
-rw-r--r--src/qt/res/animation/spinner-034.png (renamed from src/qt/res/movies/spinner-034.png)bin2422 -> 2422 bytes
-rw-r--r--src/qt/res/animation/spinner-035.png (renamed from src/qt/res/movies/spinner-035.png)bin2406 -> 2406 bytes
-rw-r--r--src/qt/rpcconsole.cpp14
-rw-r--r--src/qt/test/apptests.cpp8
-rw-r--r--src/qt/test/test_main.cpp4
-rw-r--r--src/rpc/blockchain.cpp24
-rw-r--r--src/rpc/rawtransaction.cpp61
-rw-r--r--src/rpc/server.cpp50
-rw-r--r--src/rpc/server.h17
-rw-r--r--src/rpc/util.cpp44
-rw-r--r--src/rpc/util.h19
-rw-r--r--src/sync.cpp14
-rw-r--r--src/test/data/script_tests.json2
-rw-r--r--src/test/denialofservice_tests.cpp102
-rw-r--r--src/test/descriptor_tests.cpp2
-rw-r--r--src/test/fuzz/addrdb.cpp2
-rw-r--r--src/test/fuzz/autofile.cpp72
-rw-r--r--src/test/fuzz/banman.cpp88
-rw-r--r--src/test/fuzz/buffered_file.cpp78
-rw-r--r--src/test/fuzz/crypto_aes256.cpp30
-rw-r--r--src/test/fuzz/crypto_aes256cbc.cpp34
-rw-r--r--src/test/fuzz/crypto_chacha20.cpp50
-rw-r--r--src/test/fuzz/crypto_chacha20_poly1305_aead.cpp72
-rw-r--r--src/test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp25
-rw-r--r--src/test/fuzz/crypto_poly1305.cpp22
-rw-r--r--src/test/fuzz/http_request.cpp10
-rw-r--r--src/test/fuzz/load_external_block_file.cpp31
-rw-r--r--src/test/fuzz/netaddress.cpp30
-rw-r--r--src/test/fuzz/policy_estimator.cpp11
-rw-r--r--src/test/fuzz/policy_estimator_io.cpp28
-rw-r--r--src/test/fuzz/process_message.cpp4
-rw-r--r--src/test/fuzz/script.cpp2
-rw-r--r--src/test/fuzz/scriptnum_ops.cpp2
-rw-r--r--src/test/fuzz/signature_checker.cpp6
-rw-r--r--src/test/fuzz/util.h242
-rw-r--r--src/test/netbase_tests.cpp2
-rw-r--r--src/test/settings_tests.cpp80
-rw-r--r--src/test/sync_tests.cpp2
-rw-r--r--src/test/util_tests.cpp33
-rw-r--r--src/txmempool.cpp14
-rw-r--r--src/txmempool.h55
-rw-r--r--src/util/check.h2
-rw-r--r--src/util/settings.cpp64
-rw-r--r--src/util/settings.h20
-rw-r--r--src/util/system.cpp82
-rw-r--r--src/util/system.h34
-rw-r--r--src/validation.cpp31
-rw-r--r--src/validation.h3
-rw-r--r--src/version.h14
-rw-r--r--src/wallet/bdb.cpp103
-rw-r--r--src/wallet/bdb.h136
-rw-r--r--src/wallet/context.h2
-rw-r--r--src/wallet/db.h140
-rw-r--r--src/wallet/init.cpp12
-rw-r--r--src/wallet/load.cpp11
-rw-r--r--src/wallet/load.h3
-rw-r--r--src/wallet/rpcdump.cpp2
-rw-r--r--src/wallet/rpcwallet.cpp136
-rw-r--r--src/wallet/salvage.cpp6
-rw-r--r--src/wallet/scriptpubkeyman.cpp28
-rw-r--r--src/wallet/scriptpubkeyman.h6
-rw-r--r--src/wallet/test/init_test_fixture.cpp5
-rw-r--r--src/wallet/test/wallet_test_fixture.h5
-rw-r--r--src/wallet/test/wallet_tests.cpp38
-rw-r--r--src/wallet/wallet.cpp53
-rw-r--r--src/wallet/wallet.h20
-rw-r--r--src/wallet/walletdb.cpp58
-rw-r--r--src/wallet/walletdb.h21
-rw-r--r--src/wallet/wallettool.cpp6
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py2
-rwxr-xr-xtest/functional/feature_config_args.py27
-rwxr-xr-xtest/functional/feature_settings.py84
-rwxr-xr-xtest/functional/mempool_compatibility.py2
-rwxr-xr-xtest/functional/mempool_packages.py7
-rwxr-xr-xtest/functional/p2p_blocksonly.py15
-rwxr-xr-xtest/functional/p2p_feefilter.py4
-rwxr-xr-xtest/functional/p2p_ibd_txrelay.py42
-rwxr-xr-xtest/functional/p2p_leak.py23
-rwxr-xr-xtest/functional/p2p_permissions.py8
-rwxr-xr-xtest/functional/p2p_segwit.py103
-rwxr-xr-xtest/functional/p2p_tx_download.py12
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py12
-rwxr-xr-xtest/functional/rpc_net.py6
-rwxr-xr-xtest/functional/rpc_rawtransaction.py3
-rwxr-xr-xtest/functional/test_framework/messages.py39
-rwxr-xr-xtest/functional/test_framework/mininode.py8
-rwxr-xr-xtest/functional/test_framework/test_framework.py4
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/wallet_basic.py7
-rwxr-xr-xtest/functional/wallet_dump.py5
-rwxr-xr-xtest/functional/wallet_encryption.py16
-rwxr-xr-xtest/functional/wallet_fallbackfee.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py12
-rwxr-xr-xtest/functional/wallet_upgradewallet.py2
-rw-r--r--test/sanitizer_suppressions/tsan1
195 files changed, 3381 insertions, 1284 deletions
diff --git a/.travis.yml b/.travis.yml
index 783737c8c1..2d69ad7d23 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,14 +18,11 @@ os: linux
language: minimal
arch: amd64
cache:
- ccache: true
directories:
- $TRAVIS_BUILD_DIR/depends/built
- $TRAVIS_BUILD_DIR/depends/sdk-sources
- $TRAVIS_BUILD_DIR/ci/scratch/.ccache
- $TRAVIS_BUILD_DIR/releases/$HOST
-before_cache:
- - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew cleanup; fi
stages:
- lint
- test
@@ -47,7 +44,7 @@ script:
- if [ $SECONDS -gt 1200 ]; then export CONTINUE=0; fi # Likely the depends build took very long
- if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # continue on repos with extended build time (90 minutes)
- if [ $CONTINUE = "1" ]; then set -o errexit; source ./ci/test/06_script_a.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi
- - if [ $SECONDS -gt 2000 ]; then export CONTINUE=0; fi # Likely the build took very long; The tests take about 1000s, so we should abort if we have less than 50*60-1000=2000s left
+ - if [[ $SECONDS -gt 50*60-$EXPECTED_TESTS_DURATION_IN_SECONDS ]]; then export CONTINUE=0; fi
- if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # continue on repos with extended build time (90 minutes)
- if [ $CONTINUE = "1" ]; then set -o errexit; source ./ci/test/06_script_b.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi
after_script:
@@ -136,22 +133,12 @@ jobs:
# Xcode 11.3.1, macOS 10.14, SDK 10.15
# https://docs.travis-ci.com/user/reference/osx/#macos-version
osx_image: xcode11.3
- cache:
- directories:
- - $TRAVIS_BUILD_DIR/ci/scratch/.ccache
- - $TRAVIS_BUILD_DIR/releases/$HOST
- - $HOME/Library/Caches/Homebrew
- - /usr/local/Homebrew
addons:
homebrew:
packages:
- - libtool
- berkeley-db4
- - boost
- miniupnpc
- - qt
- qrencode
- - python3
- ccache
- zeromq
env: >-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 65bc45a00b..11a0f4bac7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,27 +6,28 @@ welcome to contribute towards development in the form of peer review, testing
and patches. This document explains the practical process and guidelines for
contributing.
-Firstly in terms of structure, there is no particular concept of "Core
+First, in terms of structure, there is no particular concept of "Bitcoin Core
developers" in the sense of privileged people. Open source often naturally
-revolves around meritocracy where longer term contributors gain more trust from
-the developer community. However, some hierarchy is necessary for practical
-purposes. As such there are repository "maintainers" who are responsible for
-merging pull requests as well as a "lead maintainer" who is responsible for the
-release cycle, overall merging, moderation and appointment of maintainers.
+revolves around a meritocracy where contributors earn trust from the developer
+community over time. Nevertheless, some hierarchy is necessary for practical
+purposes. As such, there are repository "maintainers" who are responsible for
+merging pull requests, as well as a "lead maintainer" who is responsible for the
+release cycle as well as overall merging, moderation and appointment of
+maintainers.
Getting Started
---------------
New contributors are very welcome and needed.
-Reviewing and testing is the most effective way you can contribute as a new
-contributor, and it also will teach you much more about the code and process
-than opening PRs. Please refer to the section [peer review](#peer-review) later
-in this document.
+Reviewing and testing is highly valued and the most effective way you can contribute
+as a new contributor. It also will teach you much more about the code and
+process than opening pull requests. Please refer to the [peer review](#peer-review)
+section below.
Before you start contributing, familiarize yourself with the Bitcoin Core build
system and tests. Refer to the documentation in the repository on how to build
-Bitcoin Core and how to run the unit and functional tests.
+Bitcoin Core and how to run the unit tests, functional tests, and fuzz tests.
There are many open issues of varying difficulty waiting to be fixed.
If you're looking for somewhere to start contributing, check out the
@@ -62,7 +63,7 @@ history logs can be found
on [http://www.erisian.com.au/bitcoin-core-dev/](http://www.erisian.com.au/bitcoin-core-dev/)
and [http://gnusha.org/bitcoin-core-dev/](http://gnusha.org/bitcoin-core-dev/).
-Discussion about code base improvements happens in GitHub issues and on pull
+Discussion about codebase improvements happens in GitHub issues and pull
requests.
The developer
@@ -75,7 +76,7 @@ Contributor Workflow
--------------------
The codebase is maintained using the "contributor workflow" where everyone
-without exception contributes patch proposals using "pull requests". This
+without exception contributes patch proposals using "pull requests" (PRs). This
facilitates social contribution, easy testing and peer review.
To contribute a patch, the workflow is as follows:
@@ -113,6 +114,9 @@ In general, [commits should be atomic](https://en.wikipedia.org/wiki/Atomic_comm
and diffs should be easy to read. For this reason, do not mix any formatting
fixes or code moves with actual code changes.
+Make sure each individual commit is hygienic: that it builds successfully on its
+own without warnings, errors, regressions, or test failures.
+
Commit messages should be verbose by default consisting of a short subject line
(50 chars max), a blank line and detailed explanatory text as separate
paragraph(s), unless the title alone is self-explanatory (like "Corrected typo
@@ -124,7 +128,7 @@ If a particular commit references another issue, please add the reference. For
example: `refs #1234` or `fixes #4321`. Using the `fixes` or `closes` keywords
will cause the corresponding issue to be closed when the pull request is merged.
-Commit messages should never contain any `@` mentions.
+Commit messages should never contain any `@` mentions (usernames prefixed with "@").
Please refer to the [Git manual](https://git-scm.com/doc) for more information
about Git.
@@ -158,10 +162,16 @@ Examples:
qt: Add feed bump button
log: Fix typo in log message
-The body of the pull request should contain enough description about what the
-patch does together with any justification/reasoning. You should include
-references to any discussions (for example other tickets or mailing list
-discussions).
+The body of the pull request should contain sufficient description of *what* the
+patch does, and even more importantly, *why*, with justification and reasoning.
+You should include references to any discussions (for example, other issues or
+mailing list discussions).
+
+The description for a new pull request should not contain any `@` mentions. The
+PR description will be included in the commit message when the PR is merged and
+any users mentioned in the description will be annoyingly notified each time a
+fork of Bitcoin Core copies the merge. Instead, make any username mentions in a
+subsequent comment to the PR.
### Translation changes
@@ -197,13 +207,13 @@ before it will be merged. The basic squashing workflow is shown below.
# Save and quit.
git push -f # (force push to GitHub)
-Please update the resulting commit message if needed. It should read as a
-coherent message. In most cases, this means that you should not just list the
-interim commits.
+Please update the resulting commit message, if needed. It should read as a
+coherent message. In most cases, this means not just listing the interim
+commits.
-If you have problems with squashing (or other workflows with `git`), you can
-alternatively enable "Allow edits from maintainers" in the right GitHub
-sidebar and ask for help in the pull request.
+If you have problems with squashing or other git workflows, you can enable
+"Allow edits from maintainers" in the right-hand sidebar of the GitHub web
+interface and ask for help in the pull request.
Please refrain from creating several pull requests for the same change.
Use the pull request that is already open (or was created earlier) to amend
@@ -287,8 +297,8 @@ In general, all pull requests must:
- Have a clear use case, fix a demonstrable bug or serve the greater good of
the project (for example refactoring for modularisation);
- - Be well peer reviewed;
- - Have unit tests and functional tests where appropriate;
+ - Be well peer-reviewed;
+ - Have unit tests, functional tests, and fuzz tests, where appropriate;
- Follow code style guidelines ([C++](doc/developer-notes.md), [functional tests](test/functional/README.md));
- Not break the existing test suite;
- Where bugs are fixed, where possible, there should be unit tests
@@ -315,7 +325,7 @@ spread out over GitHub, mailing list and IRC discussions).
#### Conceptual Review
A review can be a conceptual review, where the reviewer leaves a comment
- * `Concept (N)ACK`, meaning "I do (not) agree in the general goal of this pull
+ * `Concept (N)ACK`, meaning "I do (not) agree with the general goal of this pull
request",
* `Approach (N)ACK`, meaning `Concept ACK`, but "I do (not) agree with the
approach of this change".
@@ -325,30 +335,28 @@ NACKs without accompanying reasoning may be disregarded.
#### Code Review
-After conceptual agreement on the change, code review can be provided. It is
-starting with `ACK BRANCH_COMMIT`, where `BRANCH_COMMIT` is the top of the
-topic branch. The review is followed by a description of how the reviewer did
-the review. The following
-language is used within pull-request comments:
+After conceptual agreement on the change, code review can be provided. A review
+begins with `ACK BRANCH_COMMIT`, where `BRANCH_COMMIT` is the top of the PR
+branch, followed by a description of how the reviewer did the review. The
+following language is used within pull request comments:
- - "I have tested the code", involving
- change-specific manual testing in addition to running the unit and functional
- tests, and in case it is not obvious how the manual testing was done, it should
- be described;
+ - "I have tested the code", involving change-specific manual testing in
+ addition to running the unit, functional, or fuzz tests, and in case it is
+ not obvious how the manual testing was done, it should be described;
- "I have not tested the code, but I have reviewed it and it looks
OK, I agree it can be merged";
- - Nit refers to trivial, often non-blocking issues.
+ - A "nit" refers to a trivial, often non-blocking issue.
Project maintainers reserve the right to weigh the opinions of peer reviewers
-using common sense judgement and also may weight based on meritocracy: Those
-that have demonstrated a deeper commitment and understanding towards the project
-(over time) or have clear domain expertise may naturally have more weight, as
-one would expect in all walks of life.
+using common sense judgement and may also weigh based on merit. Reviewers that
+have demonstrated a deeper commitment and understanding of the project over time
+or who have clear domain expertise may naturally have more weight, as one would
+expect in all walks of life.
-Where a patch set affects consensus critical code, the bar will be set much
+Where a patch set affects consensus-critical code, the bar will be much
higher in terms of discussion and peer review requirements, keeping in mind that
mistakes could be very costly to the wider community. This includes refactoring
-of consensus critical code.
+of consensus-critical code.
Where a patch set proposes to change the Bitcoin consensus, it must have been
discussed extensively on the mailing list and IRC, be accompanied by a widely
@@ -365,7 +373,7 @@ about:
- It may be because of a feature freeze due to an upcoming release. During this time,
only bug fixes are taken into consideration. If your pull request is a new feature,
- it will not be prioritized until the release is over. Wait for release.
+ it will not be prioritized until after the release. Wait for the release.
- It may be because the changes you are suggesting do not appeal to people. Rather than
nits and critique, which require effort and means they care enough to spend time on your
contribution, thundering silence is a good sign of widespread (mild) dislike of a given change
@@ -375,16 +383,18 @@ about:
[developer notes](doc/developer-notes.md), is dangerous or insecure, is messily written, etc.
Identify and address any of the issues you find. Then ask e.g. on IRC if someone could give
their opinion on the concept itself.
- - It may be because your code is too complex for all but a few people. And those people
+ - 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
- find the person touching the code you are touching before you and see if you can find
- them and give them a nudge. Don't be incessant about the nudging though.
+ 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
- a look. If you think you've been waiting an unreasonably long amount of time (month+) for
- no particular reason (few lines changed, etc), this is totally fine. Try to return the favor
- when someone else is asking for feedback on their code, and universe balances out.
+ a look. If you think you've been waiting for an unreasonably long time (say,
+ more than a month) for no particular reason (a few lines changed, etc.),
+ this is totally fine. Try to return the favor when someone else is asking
+ for feedback on their code, and the universe balances out.
+ - Remember that the best thing you can do while waiting is give review to others!
Backporting
@@ -393,11 +403,11 @@ Backporting
Security and bug fixes can be backported from `master` to release
branches.
If the backport is non-trivial, it may be appropriate to open an
-additional PR, to backport the change, only after the original PR
+additional PR to backport the change, but only after the original PR
has been merged.
Otherwise, backports will be done in batches and
the maintainers will use the proper `Needs backport (...)` labels
-when needed (the original author does not need to worry).
+when needed (the original author does not need to worry about it).
A backport should contain the following metadata in the commit body:
diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh
index 711f436630..2413cfca9f 100755
--- a/ci/test/00_setup_env.sh
+++ b/ci/test/00_setup_env.sh
@@ -31,11 +31,14 @@ export BASE_SCRATCH_DIR=${BASE_SCRATCH_DIR:-$BASE_ROOT_DIR/ci/scratch}
export HOST=${HOST:-$("$BASE_ROOT_DIR/depends/config.guess")}
# Whether to prefer BusyBox over GNU utilities
export USE_BUSY_BOX=${USE_BUSY_BOX:-false}
+
export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-true}
export RUN_FUNCTIONAL_TESTS=${RUN_FUNCTIONAL_TESTS:-true}
export RUN_SECURITY_TESTS=${RUN_SECURITY_TESTS:-false}
export TEST_RUNNER_ENV=${TEST_RUNNER_ENV:-}
export RUN_FUZZ_TESTS=${RUN_FUZZ_TESTS:-false}
+export EXPECTED_TESTS_DURATION_IN_SECONDS=${EXPECTED_TESTS_DURATION_IN_SECONDS:-1000}
+
export CONTAINER_NAME=${CONTAINER_NAME:-ci_unnamed}
export DOCKER_NAME_TAG=${DOCKER_NAME_TAG:-ubuntu:18.04}
# Randomize test order.
diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh
index 45a29928cb..7ec66eeb4f 100644
--- a/ci/test/00_setup_env_mac.sh
+++ b/ci/test/00_setup_env_mac.sh
@@ -14,4 +14,4 @@ export XCODE_BUILD_ID=11C505
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
-export BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror"
+export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-werror"
diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_host.sh
index eb0ac33184..b8a9ccaae5 100644
--- a/ci/test/00_setup_env_mac_host.sh
+++ b/ci/test/00_setup_env_mac_host.sh
@@ -10,8 +10,13 @@ export HOST=x86_64-apple-darwin16
export DOCKER_NAME_TAG=ubuntu:18.04 # Check that bionic can cross-compile to macos (bionic is used in the gitian build as well)
export PIP_PACKAGES="zmq"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror"
-export RUN_SECURITY_TESTS="true"
-# Run without depends
+export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-werror"
export NO_DEPENDS=1
export OSX_SDK=""
+export CCACHE_SIZE=300M
+
+export RUN_SECURITY_TESTS="true"
+if [ "$TRAVIS_REPO_SLUG" != "bitcoin/bitcoin" ]; then
+ export RUN_FUNCTIONAL_TESTS="false"
+ export EXPECTED_TESTS_DURATION_IN_SECONDS=200
+fi
diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh
index c4fe0eff2f..5695c43ec3 100644
--- a/ci/test/00_setup_env_native_tsan.sh
+++ b/ci/test/00_setup_env_native_tsan.sh
@@ -12,4 +12,4 @@ export PACKAGES="clang llvm libc++abi-dev libc++-dev python3-zmq"
export DEP_OPTS="CC=clang CXX='clang++ -stdlib=libc++'"
export TEST_RUNNER_EXTRA="--exclude feature_block --timeout-factor=4" # Increase timeout because sanitizers slow down. Low memory on Travis machines, exclude feature_block.
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++'"
+export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++'"
diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh
index 7cbf6f9d10..d3566914ac 100755
--- a/ci/test/04_install.sh
+++ b/ci/test/04_install.sh
@@ -14,7 +14,6 @@ if [[ $QEMU_USER_CMD == qemu-s390* ]]; then
fi
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
- export PATH="/usr/local/opt/ccache/libexec:$PATH"
${CI_RETRY_EXE} pip3 install $PIP_PACKAGES
fi
diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh
index 057395eba6..131ea21677 100755
--- a/ci/test/05_before_script.sh
+++ b/ci/test/05_before_script.sh
@@ -48,6 +48,6 @@ if [ -z "$NO_DEPENDS" ]; then
fi
if [ -n "$PREVIOUS_RELEASES_TO_DOWNLOAD" ]; then
BEGIN_FOLD previous-versions
- DOCKER_EXEC contrib/devtools/previous_release.sh -b -t "$PREVIOUS_RELEASES_DIR" "${PREVIOUS_RELEASES_TO_DOWNLOAD}"
+ DOCKER_EXEC contrib/devtools/previous_release.py -b -t "$PREVIOUS_RELEASES_DIR" "${PREVIOUS_RELEASES_TO_DOWNLOAD}"
END_FOLD
fi
diff --git a/configure.ac b/configure.ac
index e1b09dae33..c6b8aafef9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -392,11 +392,11 @@ if test "x$CXXFLAGS_overridden" = "xno"; then
AX_CHECK_COMPILE_FLAG([-Wall],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wall"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wextra],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wextra"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wgnu],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wgnu"],,[[$CXXFLAG_WERROR]])
- AX_CHECK_COMPILE_FLAG([-Wformat],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wformat"],,[[$CXXFLAG_WERROR]])
+ dnl some compilers will ignore -Wformat-security without -Wformat, so just combine the two here.
+ AX_CHECK_COMPILE_FLAG([-Wformat -Wformat-security],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wformat -Wformat-security"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wvla],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wvla"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wshadow-field],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wshadow-field"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wswitch],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wswitch"],,[[$CXXFLAG_WERROR]])
- AX_CHECK_COMPILE_FLAG([-Wformat-security],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wformat-security"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wthread-safety],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wthread-safety"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wrange-loop-analysis],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wrange-loop-analysis"],,[[$CXXFLAG_WERROR]])
AX_CHECK_COMPILE_FLAG([-Wredundant-decls],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wredundant-decls"],,[[$CXXFLAG_WERROR]])
@@ -642,6 +642,7 @@ case $host in
BUILD_OS=darwin
;;
*)
+ AC_PATH_TOOL([DSYMUTIL], [dsymutil], dsymutil)
AC_PATH_TOOL([INSTALLNAMETOOL], [install_name_tool], install_name_tool)
AC_PATH_TOOL([OTOOL], [otool], otool)
AC_PATH_PROGS([GENISOIMAGE], [genisoimage mkisofs],genisoimage)
diff --git a/contrib/devtools/previous_release.py b/contrib/devtools/previous_release.py
new file mode 100755
index 0000000000..5599051cf3
--- /dev/null
+++ b/contrib/devtools/previous_release.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+#
+# Download or build previous releases.
+# Needs curl and tar to download a release, or the build dependencies when
+# building a release.
+
+import argparse
+import contextlib
+from fnmatch import fnmatch
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import sys
+import hashlib
+
+
+@contextlib.contextmanager
+def pushd(new_dir) -> None:
+ previous_dir = os.getcwd()
+ os.chdir(new_dir)
+ try:
+ yield
+ finally:
+ os.chdir(previous_dir)
+
+
+def download_binary(tag, args) -> int:
+ if Path(tag).is_dir():
+ if not args.remove_dir:
+ print('Using cached {}'.format(tag))
+ return 0
+ shutil.rmtree(tag)
+ Path(tag).mkdir()
+ bin_path = 'bin/bitcoin-core-{}'.format(tag[1:])
+ match = re.compile('v(.*)(rc[0-9]+)$').search(tag)
+ if match:
+ bin_path = 'bin/bitcoin-core-{}/test.{}'.format(
+ match.group(1), match.group(2))
+ tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format(
+ tag=tag[1:], platform=args.platform)
+ sha256Sums = "SHA256SUMS-{tag}.asc".format(tag=tag[1:])
+ tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format(
+ bin_path=bin_path, tarball=tarball)
+ sha256SumsUrl = 'https://bitcoincore.org/{bin_path}/SHA256SUMS.asc'.format(
+ bin_path=bin_path)
+
+ print('Fetching: {tarballUrl}'.format(tarballUrl=tarballUrl))
+ print('Fetching: {sha256SumsUrl}'.format(sha256SumsUrl=sha256SumsUrl))
+
+ header, status = subprocess.Popen(
+ ['curl', '--head', tarballUrl], stdout=subprocess.PIPE).communicate()
+ if re.search("404 Not Found", header.decode("utf-8")):
+ print("Binary tag was not found")
+ return 1
+
+ curlCmds = [
+ ['curl', '--remote-name', tarballUrl],
+ ['curl', '--output', sha256Sums, sha256SumsUrl],
+ ]
+
+ for cmd in curlCmds:
+ ret = subprocess.run(cmd).returncode
+ if ret:
+ return ret
+
+ hasher = hashlib.sha256()
+ with open(tarball, "rb") as afile:
+ hasher.update(afile.read())
+ tarballHash = hasher.hexdigest()
+ tarballHash = '{} {}\n'.format(tarballHash, tarball)
+ with open(sha256Sums, 'r', encoding="utf-8") as afile:
+ shasums = afile.readlines()
+
+ if tarballHash not in shasums:
+ print("Checksum did not match")
+ Path(tarball).unlink()
+ return 1
+ print("Checksum matched")
+
+ # Bitcoin Core Release Signing Keys v0.11.0+
+ signingKey = "01EA5486DE18A882D4C2684590C8019E36C2E964"
+
+ isKeyPresent = subprocess.run(
+ ["gpg", "--list-keys", signingKey]).returncode
+ if isKeyPresent:
+ return isKeyPresent
+
+ isVerified = subprocess.run(
+ ["gpg", "--verify", sha256Sums]).returncode
+ if isVerified:
+ return isVerified
+
+ # Extract tarball
+ ret = subprocess.run(['tar', '-zxf', tarball, '-C', tag,
+ '--strip-components=1',
+ 'bitcoin-{tag}'.format(tag=tag[1:])]).returncode
+ if ret:
+ return ret
+
+ Path(tarball).unlink()
+ Path(sha256Sums).unlink()
+ return 0
+
+
+def build_release(tag, args) -> int:
+ githubUrl = "https://github.com/bitcoin/bitcoin"
+ if args.remove_dir:
+ if Path(tag).is_dir():
+ shutil.rmtree(tag)
+ if not Path(tag).is_dir():
+ # fetch new tags
+ subprocess.run(
+ ["git", "fetch", githubUrl, "--tags"])
+ output = subprocess.check_output(['git', 'tag', '-l', tag])
+ if not output:
+ print('Tag {} not found'.format(tag))
+ return 1
+ ret = subprocess.run([
+ 'git', 'clone', githubUrl, tag
+ ]).returncode
+ if ret:
+ return ret
+ with pushd(tag):
+ ret = subprocess.run(['git', 'checkout', tag]).returncode
+ if ret:
+ return ret
+ host = args.host
+ if args.depends:
+ with pushd('depends'):
+ ret = subprocess.run(['make', 'NO_QT=1']).returncode
+ if ret:
+ return ret
+ host = os.environ.get(
+ 'HOST', subprocess.check_output(['./config.guess']))
+ config_flags = '--prefix={pwd}/depends/{host} '.format(
+ pwd=os.getcwd(),
+ host=host) + args.config_flags
+ cmds = [
+ './autogen.sh',
+ './configure {}'.format(config_flags),
+ 'make',
+ ]
+ for cmd in cmds:
+ ret = subprocess.run(cmd.split()).returncode
+ if ret:
+ return ret
+ # Move binaries, so they're in the same place as in the
+ # release download
+ Path('bin').mkdir(exist_ok=True)
+ files = ['bitcoind', 'bitcoin-cli', 'bitcoin-tx']
+ for f in files:
+ Path('src/'+f).rename('bin/'+f)
+ return 0
+
+
+def check_host(args) -> int:
+ args.host = os.environ.get('HOST', subprocess.check_output(
+ './depends/config.guess').decode())
+ if args.download_binary:
+ platforms = {
+ 'x86_64-*-linux*': 'x86_64-linux-gnu',
+ 'x86_64-apple-darwin*': 'osx64',
+ }
+ args.platform = ''
+ for pattern, target in platforms.items():
+ if fnmatch(args.host, pattern):
+ args.platform = target
+ if not args.platform:
+ print('Not sure which binary to download for {}'.format(args.host))
+ return 1
+ return 0
+
+
+def main(args) -> int:
+ Path(args.target_dir).mkdir(exist_ok=True, parents=True)
+ print("Releases directory: {}".format(args.target_dir))
+ ret = check_host(args)
+ if ret:
+ return ret
+ if args.download_binary:
+ with pushd(args.target_dir):
+ for tag in args.tags:
+ ret = download_binary(tag, args)
+ if ret:
+ return ret
+ return 0
+ args.config_flags = os.environ.get('CONFIG_FLAGS', '')
+ args.config_flags += ' --without-gui --disable-tests --disable-bench'
+ with pushd(args.target_dir):
+ for tag in args.tags:
+ ret = build_release(tag, args)
+ if ret:
+ return ret
+ return 0
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('-r', '--remove-dir', action='store_true',
+ help='remove existing directory.')
+ parser.add_argument('-d', '--depends', action='store_true',
+ help='use depends.')
+ parser.add_argument('-b', '--download-binary', action='store_true',
+ help='download release binary.')
+ parser.add_argument('-t', '--target-dir', action='store',
+ help='target directory.', default='releases')
+ parser.add_argument('tags', nargs='+',
+ help="release tags. e.g.: v0.18.1 v0.20.0rc2")
+ args = parser.parse_args()
+ sys.exit(main(args))
diff --git a/contrib/devtools/previous_release.sh b/contrib/devtools/previous_release.sh
deleted file mode 100755
index d375291f47..0000000000
--- a/contrib/devtools/previous_release.sh
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-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.
-#
-# Build previous releases.
-
-export LC_ALL=C
-
-CONFIG_FLAGS=""
-FUNCTIONAL_TESTS=0
-DELETE_EXISTING=0
-USE_DEPENDS=0
-DOWNLOAD_BINARY=0
-CONFIG_FLAGS=""
-TARGET="releases"
-
-while getopts ":hfrdbt:" opt; do
- case $opt in
- h)
- echo "Usage: .previous_release.sh [options] tag1 tag2"
- echo " options:"
- echo " -h Print this message"
- echo " -f Configure for functional tests"
- echo " -r Remove existing directory"
- echo " -d Use depends"
- echo " -b Download release binary"
- echo " -t Target directory (default: releases)"
- exit 0
- ;;
- f)
- FUNCTIONAL_TESTS=1
- CONFIG_FLAGS="$CONFIG_FLAGS --without-gui --disable-tests --disable-bench"
- ;;
- r)
- DELETE_EXISTING=1
- ;;
- d)
- USE_DEPENDS=1
- ;;
- b)
- DOWNLOAD_BINARY=1
- ;;
- t)
- TARGET=$OPTARG
- ;;
- \?)
- echo "Invalid option: -$OPTARG" >&2
- exit 1
- ;;
- esac
-done
-
-shift $((OPTIND-1))
-
-if [ -z "$1" ]; then
- echo "Specify release tag(s), e.g.: .previous_release v0.15.1"
- exit 1
-fi
-
-if [ ! -d "$TARGET" ]; then
- mkdir -p $TARGET
-fi
-
-if [ "$DOWNLOAD_BINARY" -eq "1" ]; then
- HOST="${HOST:-$(./depends/config.guess)}"
- case "$HOST" in
- x86_64-*-linux*)
- PLATFORM=x86_64-linux-gnu
- ;;
- x86_64-apple-darwin*)
- PLATFORM=osx64
- ;;
- *)
- echo "Not sure which binary to download for $HOST."
- exit 1
- ;;
- esac
-fi
-
-echo "Releases directory: $TARGET"
-pushd "$TARGET" || exit 1
-{
- for tag in "$@"
- do
- if [ "$DELETE_EXISTING" -eq "1" ]; then
- if [ -d "$tag" ]; then
- rm -r "$tag"
- fi
- fi
-
- if [ "$DOWNLOAD_BINARY" -eq "0" ]; then
-
- if [ ! -d "$tag" ]; then
- if [ -z $(git tag -l "$tag") ]; then
- echo "Tag $tag not found"
- exit 1
- fi
-
- git clone https://github.com/bitcoin/bitcoin "$tag"
- pushd "$tag" || exit 1
- {
- git checkout "$tag"
- if [ "$USE_DEPENDS" -eq "1" ]; then
- pushd depends || exit 1
- {
- if [ "$FUNCTIONAL_TESTS" -eq "1" ]; then
- make NO_QT=1
- else
- make
- fi
- HOST="${HOST:-$(./config.guess)}"
- }
- popd || exit 1
- CONFIG_FLAGS="--prefix=$PWD/depends/$HOST $CONFIG_FLAGS"
- fi
- ./autogen.sh
- ./configure $CONFIG_FLAGS
- make
- # Move binaries, so they're in the same place as in the release download:
- mkdir bin
- mv src/bitcoind src/bitcoin-cli src/bitcoin-tx bin
- if [ "$FUNCTIONAL_TESTS" -eq "0" ]; then
- mv src/qt/bitcoin-qt bin
- fi
- }
- popd || exit 1
- fi
- else
- if [ -d "$tag" ]; then
- echo "Using cached $tag"
- else
- mkdir "$tag"
- if [[ "$tag" =~ v(.*)(rc[0-9]+)$ ]]; then
- BIN_PATH="bin/bitcoin-core-${BASH_REMATCH[1]}/test.${BASH_REMATCH[2]}"
- else
- BIN_PATH="bin/bitcoin-core-${tag:1}"
- fi
- URL="https://bitcoincore.org/$BIN_PATH/bitcoin-${tag:1}-$PLATFORM.tar.gz"
- echo "Fetching: $URL"
- if ! curl -O -f $URL; then
- echo "Download failed."
- exit 1
- fi
- tar -zxf "bitcoin-${tag:1}-$PLATFORM.tar.gz" -C "$tag" --strip-components=1 "bitcoin-${tag:1}"
- rm "bitcoin-${tag:1}-$PLATFORM.tar.gz"
- fi
- fi
- done
-}
-popd || exit 1
diff --git a/contrib/devtools/test_deterministic_coverage.sh b/contrib/devtools/test_deterministic_coverage.sh
index 95b1553215..8501c72f04 100755
--- a/contrib/devtools/test_deterministic_coverage.sh
+++ b/contrib/devtools/test_deterministic_coverage.sh
@@ -16,7 +16,6 @@ GCOV_EXECUTABLE="gcov"
NON_DETERMINISTIC_TESTS=(
"blockfilter_index_tests/blockfilter_index_initial_sync" # src/checkqueue.h: In CCheckQueue::Loop(): while (queue.empty()) { ... }
"coinselector_tests/knapsack_solver_test" # coinselector_tests.cpp: if (equal_sets(setCoinsRet, setCoinsRet2))
- "denialofservice_tests/DoS_mapOrphans" # denialofservice_tests.cpp: it = mapOrphanTransactions.lower_bound(InsecureRand256());
"fs_tests/fsbridge_fstream" # deterministic test failure?
"miner_tests/CreateNewBlock_validity" # validation.cpp: if (GetMainSignals().CallbacksPending() > 10)
"scheduler_tests/manythreads" # scheduler.cpp: CScheduler::serviceQueue()
diff --git a/depends/Makefile b/depends/Makefile
index 3d0784cb6b..2bc5df974a 100644
--- a/depends/Makefile
+++ b/depends/Makefile
@@ -125,6 +125,11 @@ $(host_arch)_$(host_os)_id_string+=$(shell $(host_CXX) --version 2>/dev/null)
$(host_arch)_$(host_os)_id_string+=$(shell $(host_RANLIB) --version 2>/dev/null)
$(host_arch)_$(host_os)_id_string+=$(shell $(host_STRIP) --version 2>/dev/null)
+ifneq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
+build_id_string+=system_clang
+$(host_arch)_$(host_os)_id_string+=system_clang
+endif
+
qrencode_packages_$(NO_QR) = $(qrencode_packages)
qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_)
@@ -150,11 +155,17 @@ all_packages = $(packages) $(native_packages)
meta_depends = Makefile funcs.mk builders/default.mk hosts/default.mk hosts/$(host_os).mk builders/$(build_os).mk
+$(host_arch)_$(host_os)_native_binutils?=$($(host_os)_native_binutils)
$(host_arch)_$(host_os)_native_toolchain?=$($(host_os)_native_toolchain)
include funcs.mk
+binutils_path=$($($(host_arch)_$(host_os)_native_binutils)_prefixbin)
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
toolchain_path=$($($(host_arch)_$(host_os)_native_toolchain)_prefixbin)
+else
+toolchain_path=
+endif
final_build_id_long+=$(shell $(build_SHA256SUM) config.site.in)
final_build_id+=$(shell echo -n "$(final_build_id_long)" | $(build_SHA256SUM) | cut -c-$(HASH_LENGTH))
$(host_prefix)/.stamp_$(final_build_id): $(native_packages) $(packages)
@@ -170,10 +181,10 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_
$(AT)sed -e 's|@HOST@|$(host)|' \
-e 's|@CC@|$(toolchain_path)$(host_CC)|' \
-e 's|@CXX@|$(toolchain_path)$(host_CXX)|' \
- -e 's|@AR@|$(toolchain_path)$(host_AR)|' \
- -e 's|@RANLIB@|$(toolchain_path)$(host_RANLIB)|' \
- -e 's|@NM@|$(toolchain_path)$(host_NM)|' \
- -e 's|@STRIP@|$(toolchain_path)$(host_STRIP)|' \
+ -e 's|@AR@|$(binutils_path)$(host_AR)|' \
+ -e 's|@RANLIB@|$(binutils_path)$(host_RANLIB)|' \
+ -e 's|@NM@|$(binutils_path)$(host_NM)|' \
+ -e 's|@STRIP@|$(binutils_path)$(host_STRIP)|' \
-e 's|@build_os@|$(build_os)|' \
-e 's|@host_os@|$(host_os)|' \
-e 's|@CFLAGS@|$(strip $(host_CFLAGS) $(host_$(release_type)_CFLAGS))|' \
diff --git a/depends/README.md b/depends/README.md
index c12ea8bcb3..11733024b1 100644
--- a/depends/README.md
+++ b/depends/README.md
@@ -44,7 +44,7 @@ The paths are automatically configured and no other options are needed unless ta
#### For macOS cross compilation
- sudo apt-get install curl librsvg2-bin libtiff-tools bsdmainutils cmake imagemagick libcap-dev libz-dev libbz2-dev python3-setuptools
+ sudo apt-get install curl librsvg2-bin libtiff-tools bsdmainutils cmake imagemagick libcap-dev libz-dev libbz2-dev python3-setuptools libtinfo5
#### For Win64 cross compilation
@@ -80,21 +80,40 @@ For linux S390X cross compilation:
sudo apt-get install g++-s390x-linux-gnu binutils-s390x-linux-gnu
### Dependency Options
-The following can be set when running make: make FOO=bar
-
- SOURCES_PATH: downloaded sources will be placed here
- BASE_CACHE: built packages will be placed here
- SDK_PATH: Path where sdk's can be found (used by macOS)
- FALLBACK_DOWNLOAD_PATH: If a source file can't be fetched, try here before giving up
- NO_QT: Don't download/build/cache qt and its dependencies
- NO_QR: Don't download/build/cache packages needed for enabling qrencode
- NO_ZMQ: Don't download/build/cache packages needed for enabling zeromq
- NO_WALLET: Don't download/build/cache libs needed to enable the wallet
- NO_UPNP: Don't download/build/cache packages needed for enabling upnp
- MULTIPROCESS: build libmultiprocess (experimental, requires cmake)
- DEBUG: disable some optimizations and enable more runtime checking
- HOST_ID_SALT: Optional salt to use when generating host package ids
- BUILD_ID_SALT: Optional salt to use when generating build package ids
+The following can be set when running make: `make FOO=bar`
+
+<dl>
+<dt>SOURCES_PATH</dt>
+<dd>downloaded sources will be placed here</dd>
+<dt>BASE_CACHE</dt>
+<dd>built packages will be placed here</dd>
+<dt>SDK_PATH</dt>
+<dd>Path where sdk's can be found (used by macOS)</dd>
+<dt>FALLBACK_DOWNLOAD_PATH</dt>
+<dd>If a source file can't be fetched, try here before giving up</dd>
+<dt>NO_QT</dt>
+<dd>Don't download/build/cache qt and its dependencies</dd>
+<dt>NO_QR</dt>
+<dd>Don't download/build/cache packages needed for enabling qrencode</dd>
+<dt>NO_ZMQ</dt>
+<dd>Don't download/build/cache packages needed for enabling zeromq</dd>
+<dt>NO_WALLET</dt>
+<dd>Don't download/build/cache libs needed to enable the wallet</dd>
+<dt>NO_UPNP</dt>
+<dd>Don't download/build/cache packages needed for enabling upnp</dd>
+<dt>MULTIPROCESS</dt>
+<dd>build libmultiprocess (experimental, requires cmake)</dd>
+<dt>DEBUG</dt>
+<dd>disable some optimizations and enable more runtime checking</dd>
+<dt>HOST_ID_SALT</dt>
+<dd>Optional salt to use when generating host package ids</dd>
+<dt>BUILD_ID_SALT</dt>
+<dd>Optional salt to use when generating build package ids</dd>
+<dt>FORCE_USE_SYSTEM_CLANG</dt>
+<dd>(EXPERTS ONLY) When cross-compiling for macOS, use clang found in the
+system's <code>$PATH</code> rather than the default prebuilt release of clang
+from llvm.org</dd>
+</dl>
If some packages are not built, for example `make NO_WALLET=1`, the appropriate
options will be passed to bitcoin's configure. In this case, `--disable-wallet`.
diff --git a/depends/builders/darwin.mk b/depends/builders/darwin.mk
index 69c394ec1d..f4103fc1f2 100644
--- a/depends/builders/darwin.mk
+++ b/depends/builders/darwin.mk
@@ -19,4 +19,5 @@ darwin_LIBTOOL:=$(shell xcrun -f libtool)
darwin_OTOOL:=$(shell xcrun -f otool)
darwin_NM:=$(shell xcrun -f nm)
darwin_INSTALL_NAME_TOOL:=$(shell xcrun -f install_name_tool)
+darwin_native_binutils=
darwin_native_toolchain=
diff --git a/depends/funcs.mk b/depends/funcs.mk
index 135ebba9f8..6fc20543bb 100644
--- a/depends/funcs.mk
+++ b/depends/funcs.mk
@@ -41,7 +41,7 @@ endef
define int_get_build_id
$(eval $(1)_dependencies += $($(1)_$(host_arch)_$(host_os)_dependencies) $($(1)_$(host_os)_dependencies))
-$(eval $(1)_all_dependencies:=$(call int_get_all_dependencies,$(1),$($($(1)_type)_native_toolchain) $($(1)_dependencies)))
+$(eval $(1)_all_dependencies:=$(call int_get_all_dependencies,$(1),$($($(1)_type)_native_toolchain) $($($(1)_type)_native_binutils) $($(1)_dependencies)))
$(foreach dep,$($(1)_all_dependencies),$(eval $(1)_build_id_deps+=$(dep)-$($(dep)_version)-$($(dep)_recipe_hash)))
$(eval $(1)_build_id_long:=$(1)-$($(1)_version)-$($(1)_recipe_hash)-$(release_type) $($(1)_build_id_deps) $($($(1)_type)_id_string))
$(eval $(1)_build_id:=$(shell echo -n "$($(1)_build_id_long)" | $(build_SHA256SUM) | cut -c-$(HASH_LENGTH)))
@@ -260,4 +260,4 @@ $(foreach package,$(all_packages),$(eval $(call int_config_attach_build_config,$
$(foreach package,$(all_packages),$(eval $(call int_add_cmds,$(package))))
#special exception: if a toolchain package exists, all non-native packages depend on it
-$(foreach package,$(packages),$(eval $($(package)_unpacked): |$($($(host_arch)_$(host_os)_native_toolchain)_cached) ))
+$(foreach package,$(packages),$(eval $($(package)_unpacked): |$($($(host_arch)_$(host_os)_native_toolchain)_cached) $($($(host_arch)_$(host_os)_native_binutils)_cached) ))
diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk
index 0d4fab937d..6099fd4c71 100644
--- a/depends/hosts/darwin.mk
+++ b/depends/hosts/darwin.mk
@@ -6,10 +6,30 @@ LD64_VERSION=530
OSX_SDK=$(SDK_PATH)/Xcode-$(XCODE_VERSION)-$(XCODE_BUILD_ID)-extracted-SDK-with-libcxx-headers
-# When cross-compiling for Darwin using Clang, -mlinker-version must be passed to
-# ensure that modern linker features are enabled.
-darwin_CC=clang -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
-darwin_CXX=clang++ -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -stdlib=libc++ -mlinker-version=$(LD64_VERSION)
+# Flag explanations:
+#
+# -mlinker-version
+#
+# Ensures that modern linker features are enabled. See here for more
+# details: https://github.com/bitcoin/bitcoin/pull/19407.
+#
+# -B$(build_prefix)/bin
+#
+# Explicitly point to our binaries (e.g. cctools) so that they are
+# ensured to be found and preferred over other possibilities.
+#
+# -nostdinc++ -isystem $(OSX_SDK)/usr/include/c++/v1
+#
+# Forces clang to use the libc++ headers from our SDK and completely
+# forget about the libc++ headers from the standard directories
+#
+# TODO: Once we start requiring a clang version that has the
+# -stdlib++-isystem<directory> flag first introduced here:
+# https://reviews.llvm.org/D64089, we should use that instead. Read the
+# differential summary there for more details.
+#
+darwin_CC=clang -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION) -B$(build_prefix)/bin
+darwin_CXX=clang++ -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -stdlib=libc++ -mlinker-version=$(LD64_VERSION) -B$(build_prefix)/bin -nostdinc++ -isystem $(OSX_SDK)/usr/include/c++/v1
darwin_CFLAGS=-pipe
darwin_CXXFLAGS=$(darwin_CFLAGS)
@@ -20,5 +40,11 @@ darwin_release_CXXFLAGS=$(darwin_release_CFLAGS)
darwin_debug_CFLAGS=-O1
darwin_debug_CXXFLAGS=$(darwin_debug_CFLAGS)
+darwin_native_binutils=native_cctools
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
darwin_native_toolchain=native_cctools
+else
+darwin_native_toolchain=
+endif
+
darwin_cmake_system=Darwin
diff --git a/depends/packages/native_cctools.mk b/depends/packages/native_cctools.mk
index bdebd11862..072d3828a6 100644
--- a/depends/packages/native_cctools.mk
+++ b/depends/packages/native_cctools.mk
@@ -4,11 +4,13 @@ $(package)_download_path=https://github.com/tpoechtrager/cctools-port/archive
$(package)_file_name=$($(package)_version).tar.gz
$(package)_sha256_hash=a2d491c0981cef72fee2b833598f20f42a6c44a7614a61c439bda93d56446fec
$(package)_build_subdir=cctools
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
$(package)_clang_version=8.0.0
$(package)_clang_download_path=https://releases.llvm.org/$($(package)_clang_version)
$(package)_clang_download_file=clang+llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz
$(package)_clang_file_name=clang-llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz
$(package)_clang_sha256_hash=9ef854b71949f825362a119bf2597f744836cb571131ae6b721cd102ffea8cd0
+endif
$(package)_libtapi_version=3efb201881e7a76a21e0554906cf306432539cef
$(package)_libtapi_download_path=https://github.com/tpoechtrager/apple-libtapi/archive
@@ -16,15 +18,25 @@ $(package)_libtapi_download_file=$($(package)_libtapi_version).tar.gz
$(package)_libtapi_file_name=$($(package)_libtapi_version).tar.gz
$(package)_libtapi_sha256_hash=380c1ca37cfa04a8699d0887a8d3ee1ad27f3d08baba78887c73b09485c0fbd3
-$(package)_extra_sources=$($(package)_clang_file_name)
-$(package)_extra_sources += $($(package)_libtapi_file_name)
+$(package)_extra_sources=$($(package)_libtapi_file_name)
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
+$(package)_extra_sources += $($(package)_clang_file_name)
+endif
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
define $(package)_fetch_cmds
$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_clang_download_path),$($(package)_clang_download_file),$($(package)_clang_file_name),$($(package)_clang_sha256_hash)) && \
$(call fetch_file,$(package),$($(package)_libtapi_download_path),$($(package)_libtapi_download_file),$($(package)_libtapi_file_name),$($(package)_libtapi_sha256_hash))
endef
+else
+define $(package)_fetch_cmds
+$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \
+$(call fetch_file,$(package),$($(package)_libtapi_download_path),$($(package)_libtapi_download_file),$($(package)_libtapi_file_name),$($(package)_libtapi_sha256_hash))
+endef
+endif
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
define $(package)_extract_cmds
mkdir -p $($(package)_extract_dir) && \
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
@@ -38,12 +50,28 @@ define $(package)_extract_cmds
rm -f toolchain/lib/libc++abi.so* && \
tar --no-same-owner --strip-components=1 -xf $($(package)_source)
endef
+else
+define $(package)_extract_cmds
+ mkdir -p $($(package)_extract_dir) && \
+ echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
+ echo "$($(package)_libtapi_sha256_hash) $($(package)_source_dir)/$($(package)_libtapi_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \
+ $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \
+ mkdir -p libtapi && \
+ tar --no-same-owner --strip-components=1 -C libtapi -xf $($(package)_source_dir)/$($(package)_libtapi_file_name) && \
+ tar --no-same-owner --strip-components=1 -xf $($(package)_source)
+endef
+endif
define $(package)_set_vars
$(package)_config_opts=--target=$(host) --disable-lto-support --with-libtapi=$($(package)_extract_dir)
$(package)_ldflags+=-Wl,-rpath=\\$$$$$$$$\$$$$$$$$ORIGIN/../lib
+ ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
$(package)_cc=$($(package)_extract_dir)/toolchain/bin/clang
$(package)_cxx=$($(package)_extract_dir)/toolchain/bin/clang++
+ else
+ $(package)_cc=clang
+ $(package)_cxx=clang++
+ endif
endef
define $(package)_preprocess_cmds
@@ -60,6 +88,7 @@ define $(package)_build_cmds
$(MAKE)
endef
+ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),)
define $(package)_stage_cmds
$(MAKE) DESTDIR=$($(package)_staging_dir) install && \
mkdir -p $($(package)_staging_prefix_dir)/lib/ && \
@@ -74,3 +103,11 @@ define $(package)_stage_cmds
cp -rf lib/clang/$($(package)_clang_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_clang_version)/include/ && \
cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil
endef
+else
+define $(package)_stage_cmds
+ $(MAKE) DESTDIR=$($(package)_staging_dir) install && \
+ mkdir -p $($(package)_staging_prefix_dir)/lib/ && \
+ cd $($(package)_extract_dir) && \
+ cp lib/libtapi.so.6 $($(package)_staging_prefix_dir)/lib/
+endef
+endif
diff --git a/depends/packages/native_cdrkit.mk b/depends/packages/native_cdrkit.mk
index 8243458ec8..14c37a0fc7 100644
--- a/depends/packages/native_cdrkit.mk
+++ b/depends/packages/native_cdrkit.mk
@@ -9,8 +9,10 @@ define $(package)_preprocess_cmds
patch -p1 < $($(package)_patch_dir)/cdrkit-deterministic.patch
endef
+# Starting with 10.1, GCC defaults to -fno-common, resulting in linking errors.
+# Pass -fcommon to retain the legacy behaviour.
define $(package)_config_cmds
- cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix)
+ cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix) -DCMAKE_C_FLAGS="-fcommon"
endef
define $(package)_build_cmds
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index 631851855a..500881e442 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -80,7 +80,6 @@ $(package)_config_opts += -no-feature-colordialog
$(package)_config_opts += -no-feature-commandlineparser
$(package)_config_opts += -no-feature-concurrent
$(package)_config_opts += -no-feature-dial
-$(package)_config_opts += -no-feature-filesystemwatcher
$(package)_config_opts += -no-feature-fontcombobox
$(package)_config_opts += -no-feature-ftp
$(package)_config_opts += -no-feature-http
diff --git a/doc/files.md b/doc/files.md
index cd23d547bb..5475826329 100644
--- a/doc/files.md
+++ b/doc/files.md
@@ -50,7 +50,7 @@ Subdirectory | File(s) | Description
`indexes/blockfilter/basic/` | `fltrNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Blockfilter index filters for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
`wallets/` | | [Contains wallets](#multi-wallet-environment); can be specified by `-walletdir` option; if `wallets/` subdirectory does not exist, a wallet resides in the data directory
`./` | `banlist.dat` | Stores the IPs/subnets of banned nodes
-`./` | `bitcoin.conf` | Contains [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`; can be specified by `-conf` option
+`./` | `bitcoin.conf` | User-defined [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`. File is not written to by the software and must be created manually. Path can be specified by `-conf` option
`./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option
`./` | `debug.log` | Contains debug information and general logging generated by `bitcoind` or `bitcoin-qt`; can be specified by `-debuglogfile` option
`./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees and priorities required for confirmation
@@ -58,6 +58,7 @@ Subdirectory | File(s) | Description
`./` | `mempool.dat` | Dump of the mempool's transactions
`./` | `onion_private_key` | Cached Tor hidden service private key for `-listenonion` option
`./` | `peers.dat` | Peer IP address database (custom format)
+`./` | `settings.json` | Read-write settings set through GUI or RPC interfaces, augmenting manual settings from [bitcoin.conf](bitcoin-conf.md). File is created automatically if read-write settings storage is not disabled with `-nosettings` option. Path can be specified with `-settings` option
`./` | `.cookie` | Session RPC authentication cookie; if used, created at start and deleted on shutdown; can be specified by `-rpccookiefile` option
`./` | `.lock` | Data directory lock file
diff --git a/doc/reduce-traffic.md b/doc/reduce-traffic.md
index ea13dd5c27..86943b1f72 100644
--- a/doc/reduce-traffic.md
+++ b/doc/reduce-traffic.md
@@ -50,7 +50,7 @@ Be reminded of the effects of this setting.
Doing so disables the automatic broadcasting of transactions from wallet. Not
relaying other's transactions could hurt your privacy if used while a wallet
is loaded or if you use the node to broadcast transactions.
-- If a peer is whitelisted and "-whitelistforcerelay" is set to "1" (which will
- also set "whitelistrelay" to "1"), we will still receive and relay their transactions.
+- If a peer has the forcerelay permission, we will still receive and relay
+ their transactions.
- It makes block propagation slower because compact block relay can only be
used when transaction relay is enabled.
diff --git a/doc/release-notes-11413.md b/doc/release-notes-11413.md
deleted file mode 100644
index 32735e37f6..0000000000
--- a/doc/release-notes-11413.md
+++ /dev/null
@@ -1,11 +0,0 @@
-Updated or changed RPC
-----------------------
-
-The `bumpfee`, `fundrawtransaction`, `sendmany`, `sendtoaddress`, and `walletcreatefundedpsbt`
-RPC commands have been updated to include two new fee estimation methods "BTC/kB" and "sat/B".
-The target is the fee expressed explicitly in the given form. Note that use of this feature
-will trigger BIP 125 (replace-by-fee) opt-in.
-
-In addition, the `estimate_mode` parameter is now case insensitive for all of the above RPC commands.
-
-The `bumpfee` command now uses `conf_target` rather than `confTarget` in the options.
diff --git a/doc/release-notes-16377.md b/doc/release-notes-16377.md
deleted file mode 100644
index 3442fa451b..0000000000
--- a/doc/release-notes-16377.md
+++ /dev/null
@@ -1,9 +0,0 @@
-RPC changes
------------
-- The `walletcreatefundedpsbt` RPC call will now fail with
- `Insufficient funds` when inputs are manually selected but are not enough to cover
- the outputs and fee. Additional inputs can automatically be added through the
- new `add_inputs` option.
-
-- The `fundrawtransaction` RPC now supports `add_inputs` option that when `false`
- prevents adding more inputs if necessary and consequently the RPC fails.
diff --git a/doc/release-notes-16525.md b/doc/release-notes-16525.md
new file mode 100644
index 0000000000..220cb78de4
--- /dev/null
+++ b/doc/release-notes-16525.md
@@ -0,0 +1,9 @@
+RPC changes
+-----------
+
+Exposed transaction version numbers are now treated as unsigned 32-bit integers
+instead of signed 32-bit integers. This matches their treatment in consensus
+logic. Versions greater than 2 continue to be non-standard (matching previous
+behavior of smaller than 1 or greater than 2 being non-standard). Note that
+this includes the joinpsbt command, which combines partially-signed
+transactions by selecting the highest version number.
diff --git a/doc/release-notes-16528.md b/doc/release-notes-16528.md
deleted file mode 100644
index e69de29bb2..0000000000
--- a/doc/release-notes-16528.md
+++ /dev/null
diff --git a/doc/release-notes-18594.md b/doc/release-notes-18594.md
deleted file mode 100644
index 6a2ef0a67c..0000000000
--- a/doc/release-notes-18594.md
+++ /dev/null
@@ -1,5 +0,0 @@
-## CLI
-
-The `bitcoin-cli -getinfo` command now displays the wallet name and balance for
-each of the loaded wallets when more than one is loaded (e.g. in multiwallet
-mode) and a wallet is not specified with `-rpcwallet`. (#18594)
diff --git a/doc/release-notes-18918.md b/doc/release-notes-18918.md
deleted file mode 100644
index e69de29bb2..0000000000
--- a/doc/release-notes-18918.md
+++ /dev/null
diff --git a/doc/release-notes-19133.md b/doc/release-notes-19133.md
deleted file mode 100644
index 5150fbe1c7..0000000000
--- a/doc/release-notes-19133.md
+++ /dev/null
@@ -1,7 +0,0 @@
-## CLI
-
-A new `bitcoin-cli -generate` command, equivalent to RPC `generatenewaddress`
-followed by `generatetoaddress`, can generate blocks for command line testing
-purposes. This is a client-side version of the
-[former](https://github.com/bitcoin/bitcoin/issues/14299) `generate` RPC. See
-the help for details. (#19133)
diff --git a/doc/release-notes-19200.md b/doc/release-notes-19200.md
deleted file mode 100644
index 4670cb2e75..0000000000
--- a/doc/release-notes-19200.md
+++ /dev/null
@@ -1,7 +0,0 @@
-## Wallet
-
-- Backwards compatibility has been dropped for two `getaddressinfo` RPC
- deprecations, as notified in the 0.20 release notes. The deprecated `label`
- field has been removed as well as the deprecated `labels` behavior of
- returning a JSON object containing `name` and `purpose` key-value pairs. Since
- 0.20, the `labels` field returns a JSON array of label names. (#19200)
diff --git a/doc/release-notes-19219.md b/doc/release-notes-19219.md
deleted file mode 100644
index b5ee885ddc..0000000000
--- a/doc/release-notes-19219.md
+++ /dev/null
@@ -1,23 +0,0 @@
-#### Changes regarding misbehaving peers
-
-Peers that misbehave (e.g. send us invalid blocks) are now referred to as
-discouraged nodes in log output, as they're not (and weren't) strictly banned:
-incoming connections are still allowed from them, but they're preferred for
-eviction.
-
-Furthermore, a few additional changes are introduced to how discouraged
-addresses are treated:
-
-- Discouraging an address does not time out automatically after 24 hours
- (or the `-bantime` setting). Depending on traffic from other peers,
- discouragement may time out at an indeterminate time.
-
-- Discouragement is not persisted over restarts.
-
-- There is no method to list discouraged addresses. They are not returned by
- the `listbanned` RPC. That RPC also no longer reports the `ban_reason`
- field, as `"manually added"` is the only remaining option.
-
-- Discouragement cannot be removed with the `setban remove` RPC command.
- If you need to remove a discouragement, you can remove all discouragements by
- stop-starting your node.
diff --git a/doc/release-notes-19469.md b/doc/release-notes-19469.md
deleted file mode 100644
index 974b228cc1..0000000000
--- a/doc/release-notes-19469.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Updated RPCs
-------------
-
-- `getpeerinfo` no longer returns the `banscore` field unless the configuration
- option `-deprecatedrpc=banscore` is used. The `banscore` field will be fully
- removed in the next major release. (#19469)
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 4d2225884e..23983dcd7b 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -87,6 +87,28 @@ Updated RPCs
whether initial broadcast of the transaction has been acknowledged by a
peer. `getmempoolancestors` and `getmempooldescendants` are also updated.
+- The `bumpfee`, `fundrawtransaction`, `sendmany`, `sendtoaddress`, and `walletcreatefundedpsbt`
+RPC commands have been updated to include two new fee estimation methods "BTC/kB" and "sat/B".
+The target is the fee expressed explicitly in the given form. Note that use of this feature
+will trigger BIP 125 (replace-by-fee) opt-in. (#11413)
+
+- In addition, the `estimate_mode` parameter is now case insensitive for all of
+ the above RPC commands. (#11413)
+
+- The `bumpfee` command now uses `conf_target` rather than `confTarget` in the
+ options. (#11413)
+
+- The `getpeerinfo` RPC no longer returns the `banscore` field unless the configuration
+ option `-deprecatedrpc=banscore` is used. The `banscore` field will be fully
+ removed in the next major release. (#19469)
+
+- The `walletcreatefundedpsbt` RPC call will now fail with
+ `Insufficient funds` when inputs are manually selected but are not enough to cover
+ the outputs and fee. Additional inputs can automatically be added through the
+ new `add_inputs` option. (#16377)
+
+- The `fundrawtransaction` RPC now supports `add_inputs` option that when `false`
+ prevents adding more inputs if necessary and consequently the RPC fails.
Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below.
@@ -99,6 +121,12 @@ Build System
Updated settings
----------------
+- The `-banscore` configuration option, which modified the default threshold for
+ disconnecting and discouraging misbehaving peers, has been removed as part of
+ changes in 0.20.1 and in this release to the handling of misbehaving peers.
+ Refer to "Changes regarding misbehaving peers" in the 0.20.1 release notes for
+ details. (#19464)
+
- The `-debug=db` logging category, which was deprecated in 0.20 and replaced by
`-debug=walletdb` to distinguish it from `coindb`, has been removed. (#19202)
@@ -109,12 +137,30 @@ Updated settings
Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below.
+Tools and Utilities
+-------------------
+
+- A new `bitcoin-cli -generate` command, equivalent to RPC `generatenewaddress`
+ followed by `generatetoaddress`, can generate blocks for command line testing
+ purposes. This is a client-side version of the
+ former `generate` RPC. See the help for details. (#19133)
+
+- The `bitcoin-cli -getinfo` command now displays the wallet name and balance for
+ each of the loaded wallets when more than one is loaded (e.g. in multiwallet
+ mode) and a wallet is not specified with `-rpcwallet`. (#18594)
+
New settings
------------
Wallet
------
+- Backwards compatibility has been dropped for two `getaddressinfo` RPC
+ deprecations, as notified in the 0.20 release notes. The deprecated `label`
+ field has been removed as well as the deprecated `labels` behavior of
+ returning a JSON object containing `name` and `purpose` key-value pairs. Since
+ 0.20, the `labels` field returns a JSON array of label names. (#19200)
+
- To improve wallet privacy, the frequency of wallet rebroadcast attempts is
reduced from approximately once every 15 minutes to once every 12-36 hours.
To maintain a similar level of guarantee for initial broadcast of wallet
@@ -258,9 +304,23 @@ issue.
GUI changes
-----------
+- The GUI Peers window no longer displays a "Ban Score" field. This is part of
+ changes in 0.20.1 and in this release to the handling of misbehaving
+ peers. Refer to "Changes regarding misbehaving peers" in the 0.20.1 release
+ notes for details. (#19512)
+
Low-level changes
=================
+RPC
+---
+
+- To make RPC `sendtoaddress` more consistent with `sendmany` the following error
+ `sendtoaddress` codes were changed from `-4` to `-6`:
+ - Insufficient funds
+ - Fee estimation failed
+ - Transaction has too long of a mempool chain
+
Tests
-----
diff --git a/share/examples/bitcoin.conf b/share/examples/bitcoin.conf
index 96fb6658a0..90a592cc63 100644
--- a/share/examples/bitcoin.conf
+++ b/share/examples/bitcoin.conf
@@ -20,8 +20,8 @@
# Bind to given address and always listen on it. Use [host]:port notation for IPv6
#bind=<addr>
-# Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6
-#whitebind=<addr>
+# Bind to given address and add permission flags to peers connecting to it. Use [host]:port notation for IPv6
+#whitebind=perm@<addr>
##############################################################
## Quick Primer on addnode vs connect ##
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index e5c19e5afc..848053e841 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -276,7 +276,7 @@ if ENABLE_WALLET
BITCOIN_QT_CPP += $(BITCOIN_QT_WALLET_CPP)
endif # ENABLE_WALLET
-RES_MOVIES = $(wildcard $(srcdir)/qt/res/movies/spinner-*.png)
+RES_ANIMATION = $(wildcard $(srcdir)/qt/res/animation/spinner-*.png)
BITCOIN_RC = qt/res/bitcoin-qt-res.rc
@@ -288,7 +288,7 @@ qt_libbitcoinqt_a_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS)
qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS)
qt_libbitcoinqt_a_SOURCES = $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(QT_FORMS_UI) \
- $(QT_QRC) $(QT_QRC_LOCALE) $(QT_TS) $(RES_ICONS) $(RES_MOVIES)
+ $(QT_QRC) $(QT_QRC_LOCALE) $(QT_TS) $(RES_ICONS) $(RES_ANIMATION)
if TARGET_DARWIN
qt_libbitcoinqt_a_SOURCES += $(BITCOIN_MM)
endif
@@ -359,7 +359,7 @@ $(QT_QRC_LOCALE_CPP): $(QT_QRC_LOCALE) $(QT_QM)
$(SED) -e '/^\*\*.*Created:/d' -e '/^\*\*.*by:/d' > $@
@rm $(@D)/temp_$(<F)
-$(QT_QRC_CPP): $(QT_QRC) $(QT_FORMS_H) $(RES_ICONS) $(RES_MOVIES)
+$(QT_QRC_CPP): $(QT_QRC) $(QT_FORMS_H) $(RES_ICONS) $(RES_ANIMATION)
@test -f $(RCC)
$(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(RCC) -name bitcoin $< | \
$(SED) -e '/^\*\*.*Created:/d' -e '/^\*\*.*by:/d' > $@
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 9dc3078487..637d1d2f6e 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -10,7 +10,9 @@ FUZZ_TARGETS = \
test/fuzz/addrman_deserialize \
test/fuzz/asmap \
test/fuzz/asmap_direct \
+ test/fuzz/autofile \
test/fuzz/banentry_deserialize \
+ test/fuzz/banman \
test/fuzz/base_encode_decode \
test/fuzz/bech32 \
test/fuzz/block \
@@ -28,12 +30,19 @@ FUZZ_TARGETS = \
test/fuzz/blockundo_deserialize \
test/fuzz/bloom_filter \
test/fuzz/bloomfilter_deserialize \
+ test/fuzz/buffered_file \
test/fuzz/chain \
test/fuzz/checkqueue \
test/fuzz/coins_deserialize \
test/fuzz/coins_view \
test/fuzz/crypto \
+ test/fuzz/crypto_aes256 \
+ test/fuzz/crypto_aes256cbc \
+ test/fuzz/crypto_chacha20 \
+ test/fuzz/crypto_chacha20_poly1305_aead \
test/fuzz/crypto_common \
+ test/fuzz/crypto_hkdf_hmac_sha256_l32 \
+ test/fuzz/crypto_poly1305 \
test/fuzz/cuckoocache \
test/fuzz/decode_tx \
test/fuzz/descriptor_parse \
@@ -54,6 +63,7 @@ FUZZ_TARGETS = \
test/fuzz/key_io \
test/fuzz/key_origin_info_deserialize \
test/fuzz/kitchen_sink \
+ test/fuzz/load_external_block_file \
test/fuzz/locale \
test/fuzz/merkle_block_deserialize \
test/fuzz/merkleblock \
@@ -73,6 +83,7 @@ FUZZ_TARGETS = \
test/fuzz/partial_merkle_tree_deserialize \
test/fuzz/partially_signed_transaction_deserialize \
test/fuzz/policy_estimator \
+ test/fuzz/policy_estimator_io \
test/fuzz/pow \
test/fuzz/prefilled_transaction_deserialize \
test/fuzz/prevector \
@@ -349,12 +360,24 @@ test_fuzz_asmap_direct_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_asmap_direct_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_asmap_direct_SOURCES = test/fuzz/asmap_direct.cpp
+test_fuzz_autofile_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_autofile_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_autofile_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_autofile_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_autofile_SOURCES = test/fuzz/autofile.cpp
+
test_fuzz_banentry_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBANENTRY_DESERIALIZE=1
test_fuzz_banentry_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_banentry_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_banentry_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_banentry_deserialize_SOURCES = test/fuzz/deserialize.cpp
+test_fuzz_banman_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_banman_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_banman_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_banman_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_banman_SOURCES = test/fuzz/banman.cpp
+
test_fuzz_base_encode_decode_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_base_encode_decode_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_base_encode_decode_LDADD = $(FUZZ_SUITE_LD_COMMON)
@@ -457,6 +480,12 @@ test_fuzz_bloomfilter_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_bloomfilter_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_bloomfilter_deserialize_SOURCES = test/fuzz/deserialize.cpp
+test_fuzz_buffered_file_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_buffered_file_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_buffered_file_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_buffered_file_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_buffered_file_SOURCES = test/fuzz/buffered_file.cpp
+
test_fuzz_chain_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_chain_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_chain_LDADD = $(FUZZ_SUITE_LD_COMMON)
@@ -487,12 +516,48 @@ test_fuzz_crypto_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_crypto_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_crypto_SOURCES = test/fuzz/crypto.cpp
+test_fuzz_crypto_aes256_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_aes256_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_aes256_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_aes256_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_aes256_SOURCES = test/fuzz/crypto_aes256.cpp
+
+test_fuzz_crypto_aes256cbc_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_aes256cbc_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_aes256cbc_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_aes256cbc_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_aes256cbc_SOURCES = test/fuzz/crypto_aes256cbc.cpp
+
+test_fuzz_crypto_chacha20_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_chacha20_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_chacha20_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_chacha20_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_chacha20_SOURCES = test/fuzz/crypto_chacha20.cpp
+
+test_fuzz_crypto_chacha20_poly1305_aead_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_chacha20_poly1305_aead_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_chacha20_poly1305_aead_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_chacha20_poly1305_aead_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_chacha20_poly1305_aead_SOURCES = test/fuzz/crypto_chacha20_poly1305_aead.cpp
+
test_fuzz_crypto_common_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_crypto_common_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_crypto_common_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_crypto_common_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_crypto_common_SOURCES = test/fuzz/crypto_common.cpp
+test_fuzz_crypto_hkdf_hmac_sha256_l32_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_hkdf_hmac_sha256_l32_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_hkdf_hmac_sha256_l32_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_hkdf_hmac_sha256_l32_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_hkdf_hmac_sha256_l32_SOURCES = test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp
+
+test_fuzz_crypto_poly1305_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_poly1305_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_poly1305_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_poly1305_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_poly1305_SOURCES = test/fuzz/crypto_poly1305.cpp
+
test_fuzz_cuckoocache_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_cuckoocache_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_cuckoocache_LDADD = $(FUZZ_SUITE_LD_COMMON)
@@ -613,6 +678,12 @@ test_fuzz_kitchen_sink_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_kitchen_sink_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_kitchen_sink_SOURCES = test/fuzz/kitchen_sink.cpp
+test_fuzz_load_external_block_file_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_load_external_block_file_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_load_external_block_file_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_load_external_block_file_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_load_external_block_file_SOURCES = test/fuzz/load_external_block_file.cpp
+
test_fuzz_locale_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_locale_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_locale_LDADD = $(FUZZ_SUITE_LD_COMMON)
@@ -733,6 +804,12 @@ test_fuzz_policy_estimator_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_policy_estimator_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_policy_estimator_SOURCES = test/fuzz/policy_estimator.cpp
+test_fuzz_policy_estimator_io_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_policy_estimator_io_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_policy_estimator_io_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_policy_estimator_io_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_policy_estimator_io_SOURCES = test/fuzz/policy_estimator_io.cpp
+
test_fuzz_pow_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_pow_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_pow_LDADD = $(FUZZ_SUITE_LD_COMMON)
diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp
index 1b75854210..1f872ce700 100644
--- a/src/bench/bench_bitcoin.cpp
+++ b/src/bench/bench_bitcoin.cpp
@@ -4,6 +4,7 @@
#include <bench/bench.h>
+#include <crypto/sha256.h>
#include <util/strencodings.h>
#include <util/system.h>
@@ -35,6 +36,7 @@ int main(int argc, char** argv)
{
ArgsManager argsman;
SetupBenchArgs(argsman);
+ SHA256AutoDetect();
std::string error;
if (!argsman.ParseParameters(argc, argv, error)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index f5125f22db..9afcda4578 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -516,8 +516,8 @@ static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
*/
static void GetWalletBalances(UniValue& result)
{
- std::unique_ptr<BaseRequestHandler> rh{MakeUnique<DefaultRequestHandler>()};
- const UniValue listwallets = ConnectAndCallRPC(rh.get(), "listwallets", /* args=*/{});
+ DefaultRequestHandler rh;
+ const UniValue listwallets = ConnectAndCallRPC(&rh, "listwallets", /* args=*/{});
if (!find_value(listwallets, "error").isNull()) return;
const UniValue& wallets = find_value(listwallets, "result");
if (wallets.size() <= 1) return;
@@ -525,7 +525,7 @@ static void GetWalletBalances(UniValue& result)
UniValue balances(UniValue::VOBJ);
for (const UniValue& wallet : wallets.getValues()) {
const std::string wallet_name = wallet.get_str();
- const UniValue getbalances = ConnectAndCallRPC(rh.get(), "getbalances", /* args=*/{}, wallet_name);
+ const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"];
balances.pushKV(wallet_name, balance);
}
@@ -540,8 +540,8 @@ static UniValue GetNewAddress()
{
Optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
- std::unique_ptr<BaseRequestHandler> rh{MakeUnique<DefaultRequestHandler>()};
- return ConnectAndCallRPC(rh.get(), "getnewaddress", /* args=*/{}, wallet_name);
+ DefaultRequestHandler rh;
+ return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
}
/**
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 3dcce92ab5..b04cc12059 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -101,6 +101,11 @@ static bool AppInit(int argc, char* argv[])
}
}
+ if (!gArgs.InitSettings(error)) {
+ InitError(Untranslated(error));
+ return false;
+ }
+
// -server defaults to true for bitcoind but not for the GUI so do this here
gArgs.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index a79e7b9d12..8de7a8f2d8 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -30,12 +30,16 @@ enum class TxValidationResult {
TX_MISSING_INPUTS, //!< transaction was missing some of its inputs
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
/**
- * Transaction might be missing a witness, have a witness prior to SegWit
+ * Transaction might have a witness prior to SegWit
* activation, or witness may have been malleated (which includes
* non-standard witnesses).
*/
TX_WITNESS_MUTATED,
/**
+ * Transaction is missing a witness.
+ */
+ TX_WITNESS_STRIPPED,
+ /**
* Tx already in mempool or conflicts with a tx in the chain
* (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold)
* Currently this is only used if the transaction already exists in the mempool or on chain.
@@ -75,37 +79,39 @@ enum class BlockValidationResult {
* by TxValidationState and BlockValidationState for validation information on transactions
* and blocks respectively. */
template <typename Result>
-class ValidationState {
+class ValidationState
+{
private:
- enum mode_state {
- MODE_VALID, //!< everything ok
- MODE_INVALID, //!< network rule violation (DoS value may be set)
- MODE_ERROR, //!< run-time error
- } m_mode{MODE_VALID};
+ enum class ModeState {
+ M_VALID, //!< everything ok
+ M_INVALID, //!< network rule violation (DoS value may be set)
+ M_ERROR, //!< run-time error
+ } m_mode{ModeState::M_VALID};
Result m_result{};
std::string m_reject_reason;
std::string m_debug_message;
+
public:
bool Invalid(Result result,
- const std::string &reject_reason="",
- const std::string &debug_message="")
+ const std::string& reject_reason = "",
+ const std::string& debug_message = "")
{
m_result = result;
m_reject_reason = reject_reason;
m_debug_message = debug_message;
- if (m_mode != MODE_ERROR) m_mode = MODE_INVALID;
+ if (m_mode != ModeState::M_ERROR) m_mode = ModeState::M_INVALID;
return false;
}
bool Error(const std::string& reject_reason)
{
- if (m_mode == MODE_VALID)
+ if (m_mode == ModeState::M_VALID)
m_reject_reason = reject_reason;
- m_mode = MODE_ERROR;
+ m_mode = ModeState::M_ERROR;
return false;
}
- bool IsValid() const { return m_mode == MODE_VALID; }
- bool IsInvalid() const { return m_mode == MODE_INVALID; }
- bool IsError() const { return m_mode == MODE_ERROR; }
+ bool IsValid() const { return m_mode == ModeState::M_VALID; }
+ bool IsInvalid() const { return m_mode == ModeState::M_INVALID; }
+ bool IsError() const { return m_mode == ModeState::M_ERROR; }
Result GetResult() const { return m_result; }
std::string GetRejectReason() const { return m_reject_reason; }
std::string GetDebugMessage() const { return m_debug_message; }
diff --git a/src/core_write.cpp b/src/core_write.cpp
index 69b62df901..34cfeecc6f 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -179,7 +179,9 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
{
entry.pushKV("txid", tx.GetHash().GetHex());
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
- entry.pushKV("version", tx.nVersion);
+ // Transaction version is actually unsigned in consensus checks, just signed in memory,
+ // so cast to unsigned before giving it to the user.
+ entry.pushKV("version", static_cast<int64_t>(static_cast<uint32_t>(tx.nVersion)));
entry.pushKV("size", (int)::GetSerializeSize(tx, PROTOCOL_VERSION));
entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
entry.pushKV("weight", GetTransactionWeight(tx));
diff --git a/src/init.cpp b/src/init.cpp
index 4a4f33d6ef..acf9f8bd91 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -396,8 +396,8 @@ void SetupServerArgs(NodeContext& node)
gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
gArgs.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless '-whitelistforcerelay' is '1', in which case whitelisted peers' transactions will be relayed. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ gArgs.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -418,6 +418,7 @@ void SetupServerArgs(NodeContext& node)
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ gArgs.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#ifndef WIN32
gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#else
@@ -431,7 +432,6 @@ void SetupServerArgs(NodeContext& node)
gArgs.AddArg("-addnode=<ip>", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
gArgs.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting and discouraging misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
@@ -456,6 +456,7 @@ void SetupServerArgs(NodeContext& node)
gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ gArgs.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -775,13 +776,14 @@ static bool InitSanityCheck()
return true;
}
-static bool AppInitServers(const util::Ref& context)
+static bool AppInitServers(const util::Ref& context, NodeContext& node)
{
RPCServer::OnStarted(&OnRPCStarted);
RPCServer::OnStopped(&OnRPCStopped);
if (!InitHTTPServer())
return false;
StartRPC();
+ node.rpc_interruption_point = RpcInterruptionPoint;
if (!StartHTTPRPC(context))
return false;
if (gArgs.GetBoolArg("-rest", DEFAULT_REST_ENABLE)) StartREST(context);
@@ -1352,7 +1354,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
if (gArgs.GetBoolArg("-server", false))
{
uiInterface.InitMessage_connect(SetRPCWarmupStatus);
- if (!AppInitServers(context))
+ if (!AppInitServers(context, node))
return InitError(_("Unable to start HTTP server. See debug log for details."));
}
@@ -1372,7 +1374,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
assert(!node.banman);
node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
- node.connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max())));
+ node.connman = MakeUnique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), gArgs.GetBoolArg("-networkactive", true));
// Make mempool generally available in the node context. For example the connection manager, wallet, or RPC threads,
// which are all started after this, may use it from the node context.
assert(!node.mempool);
@@ -1888,7 +1890,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
- connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay);
+ connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS;
connOptions.nBestHeight = chain_active_height;
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index 65695707f7..bbeb0fa801 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -15,6 +15,7 @@
#include <string>
#include <vector>
+class ArgsManager;
class CBlock;
class CFeeRate;
class CRPCCommand;
@@ -322,7 +323,7 @@ std::unique_ptr<Chain> MakeChain(NodeContext& node);
//! analysis, or fee estimation. These clients need to expose their own
//! MakeXXXClient functions returning their implementations of the ChainClient
//! interface.
-std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames);
+std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames);
} // namespace interfaces
diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp
index 834a16ecf5..33f0dac263 100644
--- a/src/interfaces/node.cpp
+++ b/src/interfaces/node.cpp
@@ -66,6 +66,7 @@ public:
bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); }
bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); }
void selectParams(const std::string& network) override { SelectParams(network); }
+ bool initSettings(std::string& error) override { return gArgs.InitSettings(error); }
uint64_t getAssumedBlockchainSize() override { return Params().AssumedBlockchainSize(); }
uint64_t getAssumedChainStateSize() override { return Params().AssumedChainStateSize(); }
std::string getNetwork() override { return Params().NetworkIDString(); }
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index b88b5bc14e..a9680c42b5 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -66,6 +66,11 @@ public:
//! Choose network parameters.
virtual void selectParams(const std::string& network) = 0;
+ //! Read and update <datadir>/settings.json file with saved settings. This
+ //! needs to be called after selectParams() because the settings file
+ //! location is network-specific.
+ virtual bool initSettings(std::string& error) = 0;
+
//! Get the (assumed) blockchain size.
virtual uint64_t getAssumedBlockchainSize() = 0;
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index f6806aed65..7fd24425cf 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -483,10 +483,11 @@ public:
class WalletClientImpl : public ChainClient
{
public:
- WalletClientImpl(Chain& chain, std::vector<std::string> wallet_filenames)
+ WalletClientImpl(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames)
: m_wallet_filenames(std::move(wallet_filenames))
{
m_context.chain = &chain;
+ m_context.args = &args;
}
void registerRpcs() override
{
@@ -499,7 +500,7 @@ public:
}
bool verify() override { return VerifyWallets(*m_context.chain, m_wallet_filenames); }
bool load() override { return LoadWallets(*m_context.chain, m_wallet_filenames); }
- void start(CScheduler& scheduler) override { return StartWallets(scheduler); }
+ void start(CScheduler& scheduler) override { return StartWallets(scheduler, *Assert(m_context.args)); }
void flush() override { return FlushWallets(); }
void stop() override { return StopWallets(); }
void setMockTime(int64_t time) override { return SetMockTime(time); }
@@ -514,7 +515,7 @@ public:
~WalletClientImpl() override { UnloadWallets(); }
WalletContext m_context;
- std::vector<std::string> m_wallet_filenames;
+ const std::vector<std::string> m_wallet_filenames;
std::vector<std::unique_ptr<Handler>> m_rpc_handlers;
std::list<CRPCCommand> m_rpc_commands;
};
@@ -523,9 +524,9 @@ public:
std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? MakeUnique<WalletImpl>(wallet) : nullptr; }
-std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames)
+std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, ArgsManager& args, std::vector<std::string> wallet_filenames)
{
- return MakeUnique<WalletClientImpl>(chain, std::move(wallet_filenames));
+ return MakeUnique<WalletClientImpl>(chain, args, std::move(wallet_filenames));
}
} // namespace interfaces
diff --git a/src/net.cpp b/src/net.cpp
index 244b0094d6..0c56cddbdc 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1013,7 +1013,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
SetSocketNoDelay(hSocket);
// Don't accept connections from banned peers.
- bool banned = m_banman->IsBanned(addr);
+ bool banned = m_banman && m_banman->IsBanned(addr);
if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && banned)
{
LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString());
@@ -1022,7 +1022,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
}
// Only accept connections from discouraged peers if our inbound slots aren't (almost) full.
- bool discouraged = m_banman->IsDiscouraged(addr);
+ bool discouraged = m_banman && m_banman->IsDiscouraged(addr);
if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && nInbound + 1 >= nMaxInbound && discouraged)
{
LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString());
@@ -2253,7 +2253,7 @@ void Discover()
void CConnman::SetNetworkActive(bool active)
{
- LogPrint(BCLog::NET, "SetNetworkActive: %s\n", active);
+ LogPrintf("%s: %s\n", __func__, active);
if (fNetworkActive == active) {
return;
@@ -2264,12 +2264,14 @@ void CConnman::SetNetworkActive(bool active)
uiInterface.NotifyNetworkActiveChanged(fNetworkActive);
}
-CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In) : nSeed0(nSeed0In), nSeed1(nSeed1In)
+CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, bool network_active)
+ : nSeed0(nSeed0In), nSeed1(nSeed1In)
{
SetTryNewOutboundPeer(false);
Options connOptions;
Init(connOptions);
+ SetNetworkActive(network_active);
}
NodeId CConnman::GetNewNodeId()
diff --git a/src/net.h b/src/net.h
index 6673e02d68..17d8fda372 100644
--- a/src/net.h
+++ b/src/net.h
@@ -61,7 +61,7 @@ static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8;
/** Maximum number of addnode outgoing nodes */
static const int MAX_ADDNODE_CONNECTIONS = 8;
/** Maximum number of block-relay-only outgoing connections */
-static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2;
+static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2;
/** Maximum number of feeler connections */
static const int MAX_FEELER_CONNECTIONS = 1;
/** -listen default */
@@ -181,7 +181,7 @@ public:
}
}
- CConnman(uint64_t seed0, uint64_t seed1);
+ CConnman(uint64_t seed0, uint64_t seed1, bool network_active = true);
~CConnman();
bool Start(CScheduler& scheduler, const Options& options);
@@ -447,6 +447,7 @@ private:
std::atomic<int> nBestHeight;
CClientUIInterface* clientInterface;
NetEventsInterface* m_msgproc;
+ /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
BanMan* m_banman;
/** SipHasher seeds for deterministic randomness */
@@ -964,11 +965,11 @@ public:
}
- void AddInventoryKnown(const CInv& inv)
+ void AddKnownTx(const uint256& hash)
{
if (m_tx_relay != nullptr) {
LOCK(m_tx_relay->cs_tx_inventory);
- m_tx_relay->filterInventoryKnown.insert(inv.hash);
+ m_tx_relay->filterInventoryKnown.insert(hash);
}
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 4321005212..f63abca847 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -38,7 +38,9 @@ static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
/** Minimum time between orphan transactions expire time checks in seconds */
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
/** How long to cache transactions in mapRelay for normal relay */
-static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME{15 * 60};
+static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME = std::chrono::minutes{15};
+/** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */
+static constexpr std::chrono::seconds UNCONDITIONAL_RELAY_DELAY = std::chrono::minutes{2};
/** Headers download timeout expressed in microseconds
* Timeout = base + per_header * (expected number of headers) */
static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes
@@ -73,6 +75,8 @@ static const unsigned int MAX_INV_SZ = 50000;
static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100;
/** Maximum number of announced transactions from a peer */
static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ;
+/** How many microseconds to delay requesting transactions via txids, if we have wtxid-relaying peers */
+static constexpr std::chrono::microseconds TXID_RELAY_DELAY{std::chrono::seconds{2}};
/** How many microseconds to delay requesting transactions from inbound peers */
static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{std::chrono::seconds{2}};
/** How long to wait (in microseconds) before downloading a transaction from an additional peer */
@@ -117,11 +121,20 @@ static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24};
/** Average delay between peer address broadcasts */
static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30};
/** Average delay between trickled inventory transmissions in seconds.
- * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */
+ * Blocks and peers with noban permission bypass this, outbound peers get half this delay. */
static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5;
-/** Maximum number of inventory items to send per transmission.
+/** Maximum rate of inventory items to send per second.
* Limits the impact of low-fee transaction floods. */
-static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL;
+static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND = 7;
+/** Maximum number of inventory items to send per transmission. */
+static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * INVENTORY_BROADCAST_INTERVAL;
+/** The number of most recently announced transactions a peer can request. */
+static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500;
+/** Verify that INVENTORY_MAX_RECENT_RELAY is enough to cache everything typically
+ * relayed before unconditional relay from the mempool kicks in. This is only a
+ * lower bound, and it should be larger to account for higher inv rate to outbound
+ * peers, and random variations in the broadcast mechanism. */
+static_assert(INVENTORY_MAX_RECENT_RELAY >= INVENTORY_BROADCAST_PER_SECOND * UNCONDITIONAL_RELAY_DELAY / std::chrono::seconds{1}, "INVENTORY_RELAY_MAX too low");
/** Average delay between feefilter broadcasts in seconds. */
static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
/** Maximum feefilter broadcast delay after significant change. */
@@ -140,6 +153,7 @@ struct COrphanTx {
};
RecursiveMutex g_cs_orphans;
std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);
+std::map<uint256, std::map<uint256, COrphanTx>::iterator> g_orphans_by_wtxid GUARDED_BY(g_cs_orphans);
void EraseOrphansFor(NodeId peer);
@@ -176,6 +190,15 @@ namespace {
* million to make it highly unlikely for users to have issues with this
* filter.
*
+ * We only need to add wtxids to this filter. For non-segwit
+ * transactions, the txid == wtxid, so this only prevents us from
+ * re-downloading non-segwit transactions when communicating with
+ * non-wtxidrelay peers -- which is important for avoiding malleation
+ * attacks that could otherwise interfere with transaction relay from
+ * non-wtxidrelay peers. For communicating with wtxidrelay peers, having
+ * the reject filter store wtxids is exactly what we want to avoid
+ * redownload of a rejected transaction.
+ *
* Memory used: 1.3 MB
*/
std::unique_ptr<CRollingBloomFilter> recentRejects GUARDED_BY(cs_main);
@@ -207,6 +230,9 @@ namespace {
/** Number of peers from which we're downloading blocks. */
int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0;
+ /** Number of peers with wtxid relay. */
+ int g_wtxid_relay_peers GUARDED_BY(cs_main) = 0;
+
/** Number of outbound peers with m_chain_sync.m_protect. */
int g_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0;
@@ -249,7 +275,7 @@ struct CNodeState {
bool fCurrentlyConnected;
//! Accumulated misbehaviour score for this peer.
int nMisbehavior;
- //! Whether this peer should be disconnected and marked as discouraged (unless whitelisted with noban).
+ //! Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission).
bool m_should_discourage;
//! String name of this peer (debugging/logging purposes).
const std::string name;
@@ -395,6 +421,12 @@ struct CNodeState {
//! Whether this peer is a manual connection
bool m_is_manual_connection;
+ //! A rolling bloom filter of all announced tx CInvs to this peer.
+ CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001};
+
+ //! Whether this peer relays txs via wtxid
+ bool m_wtxid_relay{false};
+
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
m_is_manual_connection (is_manual)
@@ -422,6 +454,7 @@ struct CNodeState {
fSupportsDesiredCmpctVersion = false;
m_chain_sync = { 0, nullptr, false, false };
m_last_block_announcement = 0;
+ m_recently_announced_invs.reset();
}
};
@@ -448,7 +481,7 @@ static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUS
nPreferredDownload += state->fPreferredDownload;
}
-static void PushNodeVersion(CNode& pnode, CConnman* connman, int64_t nTime)
+static void PushNodeVersion(CNode& pnode, CConnman& connman, 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
@@ -462,7 +495,7 @@ static void PushNodeVersion(CNode& pnode, CConnman* connman, int64_t nTime)
CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(CService(), addr.nServices));
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
- connman->PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
+ connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode.m_tx_relay != nullptr));
if (fLogIPs) {
@@ -573,7 +606,7 @@ static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIV
* lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by
* removing the first element if necessary.
*/
-static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
AssertLockHeld(cs_main);
CNodeState* nodestate = State(nodeid);
@@ -589,20 +622,20 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connma
return;
}
}
- connman->ForNode(nodeid, [connman](CNode* pfrom){
+ connman.ForNode(nodeid, [&connman](CNode* pfrom){
AssertLockHeld(cs_main);
uint64_t nCMPCTBLOCKVersion = (pfrom->GetLocalServices() & NODE_WITNESS) ? 2 : 1;
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
- connman->ForNode(lNodesAnnouncingHeaderAndIDs.front(), [connman, nCMPCTBLOCKVersion](CNode* pnodeStop){
+ connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [&connman, nCMPCTBLOCKVersion](CNode* pnodeStop){
AssertLockHeld(cs_main);
- connman->PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
+ connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
return true;
});
lNodesAnnouncingHeaderAndIDs.pop_front();
}
- connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion));
+ connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion));
lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId());
return true;
});
@@ -745,7 +778,7 @@ void UpdateTxRequestTime(const uint256& txid, std::chrono::microseconds request_
}
}
-std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay, bool use_txid_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
std::chrono::microseconds process_time;
const auto last_request_time = GetTxRequestTime(txid);
@@ -761,6 +794,9 @@ std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chron
// We delay processing announcements from inbound peers
if (use_inbound_delay) process_time += INBOUND_PEER_TX_DELAY;
+ // We delay processing announcements from peers that use txid-relay (instead of wtxid)
+ if (use_txid_delay) process_time += TXID_RELAY_DELAY;
+
return process_time;
}
@@ -778,7 +814,7 @@ void RequestTx(CNodeState* state, const uint256& txid, std::chrono::microseconds
// Calculate the time to try requesting this transaction. Use
// fPreferredDownload as a proxy for outbound peers.
- const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload);
+ const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload, !state->m_wtxid_relay && g_wtxid_relay_peers > 0);
peer_download_state.m_tx_process_time.emplace(process_time, txid);
}
@@ -810,19 +846,20 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) {
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName), pnode->fInbound, pnode->m_manual_connection));
}
if(!pnode->fInbound)
- PushNodeVersion(*pnode, connman, GetTime());
+ PushNodeVersion(*pnode, *connman, GetTime());
}
void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
{
- std::set<uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs();
+ std::map<uint256, uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs();
- for (const uint256& txid : unbroadcast_txids) {
+ for (const auto& elem : unbroadcast_txids) {
// Sanity check: all unbroadcast txns should exist in the mempool
- if (m_mempool.exists(txid)) {
- RelayTransaction(txid, *connman);
+ if (m_mempool.exists(elem.first)) {
+ LOCK(cs_main);
+ RelayTransaction(elem.first, elem.second, *connman);
} else {
- m_mempool.RemoveUnbroadcastTx(txid, true);
+ m_mempool.RemoveUnbroadcastTx(elem.first, true);
}
}
@@ -854,6 +891,8 @@ void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTim
assert(nPeersWithValidatedDownloads >= 0);
g_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect;
assert(g_outbound_peers_with_protect_from_disconnect >= 0);
+ g_wtxid_relay_peers -= state->m_wtxid_relay;
+ assert(g_wtxid_relay_peers >= 0);
mapNodeState.erase(nodeid);
@@ -863,6 +902,7 @@ void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTim
assert(nPreferredDownload == 0);
assert(nPeersWithValidatedDownloads == 0);
assert(g_outbound_peers_with_protect_from_disconnect == 0);
+ assert(g_wtxid_relay_peers == 0);
}
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
}
@@ -921,6 +961,8 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE
auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, g_orphan_list.size()});
assert(ret.second);
g_orphan_list.push_back(ret.first);
+ // Allow for lookups in the orphan pool by wtxid, as well as txid
+ g_orphans_by_wtxid.emplace(tx->GetWitnessHash(), ret.first);
for (const CTxIn& txin : tx->vin) {
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first);
}
@@ -957,6 +999,7 @@ int static EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans)
it_last->second.list_pos = old_pos;
}
g_orphan_list.pop_back();
+ g_orphans_by_wtxid.erase(it->second.tx->GetWitnessHash());
mapOrphanTransactions.erase(it);
return 1;
@@ -1016,7 +1059,8 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans)
}
/**
- * Increment peer's misbehavior score. If the new value surpasses banscore (specified on startup or by default), mark node to be discouraged, meaning the peer might be disconnected & added to the discouragement filter.
+ * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node
+ * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter.
*/
void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
@@ -1028,9 +1072,8 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV
return;
state->nMisbehavior += howmuch;
- int banscore = gArgs.GetArg("-banscore", DEFAULT_BANSCORE_THRESHOLD);
std::string message_prefixed = message.empty() ? "" : (": " + message);
- if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore)
+ if (state->nMisbehavior >= DISCOURAGEMENT_THRESHOLD && state->nMisbehavior - howmuch < DISCOURAGEMENT_THRESHOLD)
{
LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d) DISCOURAGE THRESHOLD EXCEEDED%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed);
state->m_should_discourage = true;
@@ -1126,6 +1169,7 @@ static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state,
case TxValidationResult::TX_MISSING_INPUTS:
case TxValidationResult::TX_PREMATURE_SPEND:
case TxValidationResult::TX_WITNESS_MUTATED:
+ case TxValidationResult::TX_WITNESS_STRIPPED:
case TxValidationResult::TX_CONFLICT:
case TxValidationResult::TX_MEMPOOL_POLICY:
break;
@@ -1166,14 +1210,15 @@ PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CS
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
// Blocks don't typically have more than 4000 transactions, so this should
- // be at least six blocks (~1 hr) worth of transactions that we can store.
+ // be at least six blocks (~1 hr) worth of transactions that we can store,
+ // inserting both a txid and wtxid for every observed transaction.
// If the number of transactions appearing in a block goes up, or if we are
// seeing getdata requests more than an hour after initial announcement, we
// can increase this number.
// The false positive rate of 1/1M should come out to less than 1
// transaction per day that would be inadvertently ignored (which is the
// same probability that we have in the reject filter).
- g_recent_confirmed_transactions.reset(new CRollingBloomFilter(24000, 0.000001));
+ g_recent_confirmed_transactions.reset(new CRollingBloomFilter(48000, 0.000001));
const Consensus::Params& consensusParams = Params().GetConsensus();
// Stale tip checking and peer eviction are on two different timers, but we
@@ -1229,6 +1274,9 @@ void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pb
LOCK(g_cs_recent_confirmed_transactions);
for (const auto& ptx : pblock->vtx) {
g_recent_confirmed_transactions->insert(ptx->GetHash());
+ if (ptx->GetHash() != ptx->GetWitnessHash()) {
+ g_recent_confirmed_transactions->insert(ptx->GetWitnessHash());
+ }
}
}
}
@@ -1363,7 +1411,7 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const BlockValidatio
!::ChainstateActive().IsInitialBlockDownload() &&
mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) {
if (it != mapBlockSource.end()) {
- MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, connman);
+ MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, *connman);
}
}
if (it != mapBlockSource.end())
@@ -1382,6 +1430,7 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
{
case MSG_TX:
case MSG_WITNESS_TX:
+ case MSG_WTX:
{
assert(recentRejects);
if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip)
@@ -1396,7 +1445,11 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
{
LOCK(g_cs_orphans);
- if (mapOrphanTransactions.count(inv.hash)) return true;
+ if (inv.type != MSG_WTX && mapOrphanTransactions.count(inv.hash)) {
+ return true;
+ } else if (inv.type == MSG_WTX && g_orphans_by_wtxid.count(inv.hash)) {
+ return true;
+ }
}
{
@@ -1404,8 +1457,8 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
}
- return recentRejects->contains(inv.hash) ||
- mempool.exists(inv.hash);
+ const bool by_wtxid = (inv.type == MSG_WTX);
+ return recentRejects->contains(inv.hash) || mempool.exists(inv.hash, by_wtxid);
}
case MSG_BLOCK:
case MSG_WITNESS_BLOCK:
@@ -1415,11 +1468,17 @@ bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LO
return true;
}
-void RelayTransaction(const uint256& txid, const CConnman& connman)
+void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
{
- connman.ForEachNode([&txid](CNode* pnode)
+ connman.ForEachNode([&txid, &wtxid](CNode* pnode)
{
- pnode->PushTxInventory(txid);
+ AssertLockHeld(cs_main);
+ CNodeState &state = *State(pnode->GetId());
+ if (state.m_wtxid_relay) {
+ pnode->PushTxInventory(wtxid);
+ } else {
+ pnode->PushTxInventory(txid);
+ }
});
}
@@ -1459,7 +1518,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, const CConnman&
connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc));
}
-void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, const CInv& inv, CConnman* connman)
+void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, const CInv& inv, CConnman& connman)
{
bool send = false;
std::shared_ptr<const CBlock> a_recent_block;
@@ -1507,7 +1566,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
// disconnect node in case we have reached the outbound limit for serving historical blocks
if (send &&
- connman->OutboundTargetReached(true) &&
+ connman.OutboundTargetReached(true) &&
(((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) &&
!pfrom.HasPermission(PF_DOWNLOAD) // nodes with the download permission may exceed target
) {
@@ -1541,7 +1600,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
if (!ReadRawBlockFromDisk(block_data, pindex, chainparams.MessageStart())) {
assert(!"cannot load block from disk");
}
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data)));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data)));
// Don't set pblock as we've sent the block
} else {
// Send block from disk
@@ -1552,9 +1611,9 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
}
if (pblock) {
if (inv.type == MSG_BLOCK)
- connman->PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock));
+ connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock));
else if (inv.type == MSG_WITNESS_BLOCK)
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock));
else if (inv.type == MSG_FILTERED_BLOCK)
{
bool sendMerkleBlock = false;
@@ -1567,7 +1626,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
}
}
if (sendMerkleBlock) {
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock));
// CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see
// This avoids hurting performance by pointlessly requiring a round-trip
// Note that there is currently no way for a node to request any single transactions we didn't send here -
@@ -1576,7 +1635,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
// however we MUST always provide at least what the remote peer needs
typedef std::pair<unsigned int, uint256> PairType;
for (PairType& pair : merkleBlock.vMatchedTxn)
- connman->PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first]));
+ connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first]));
}
// else
// no response
@@ -1591,13 +1650,13 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS;
if (CanDirectFetch(consensusParams) && pindex->nHeight >= ::ChainActive().Height() - MAX_CMPCTBLOCK_DEPTH) {
if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) {
- connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block));
+ connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block));
} else {
CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness);
- connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock));
+ connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock));
}
} else {
- connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock));
+ connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock));
}
}
}
@@ -1610,44 +1669,41 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
// wait for other stuff first.
std::vector<CInv> vInv;
vInv.push_back(CInv(MSG_BLOCK, ::ChainActive().Tip()->GetBlockHash()));
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv));
pfrom.hashContinue.SetNull();
}
}
}
//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
-CTransactionRef static FindTxForGetData(CNode& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds longlived_mempool_time) LOCKS_EXCLUDED(cs_main)
+CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid_or_wtxid, bool use_wtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
{
- // Check if the requested transaction is so recent that we're just
- // about to announce it to the peer; if so, they certainly shouldn't
- // know we already have it.
- {
- LOCK(peer.m_tx_relay->cs_tx_inventory);
- if (peer.m_tx_relay->setInventoryTxToSend.count(txid)) return {};
+ auto txinfo = mempool.info(txid_or_wtxid, use_wtxid);
+ if (txinfo.tx) {
+ // If a TX could have been INVed in reply to a MEMPOOL request,
+ // or is older than UNCONDITIONAL_RELAY_DELAY, permit the request
+ // unconditionally.
+ if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= now - UNCONDITIONAL_RELAY_DELAY) {
+ return std::move(txinfo.tx);
+ }
}
{
LOCK(cs_main);
- // Look up transaction in relay pool
- auto mi = mapRelay.find(txid);
- if (mi != mapRelay.end()) return mi->second;
- }
-
- auto txinfo = mempool.info(txid);
- if (txinfo.tx) {
- // To protect privacy, do not answer getdata using the mempool when
- // that TX couldn't have been INVed in reply to a MEMPOOL request,
- // or when it's too recent to have expired from mapRelay.
- if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= longlived_mempool_time) {
- return txinfo.tx;
+ // Otherwise, the transaction must have been announced recently.
+ if (State(peer.GetId())->m_recently_announced_invs.contains(txid_or_wtxid)) {
+ // If it was, it can be relayed from either the mempool...
+ if (txinfo.tx) return std::move(txinfo.tx);
+ // ... or the relay pool.
+ auto mi = mapRelay.find(txid_or_wtxid);
+ if (mi != mapRelay.end()) return mi->second;
}
}
return {};
}
-void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnman* connman, CTxMemPool& mempool, const std::atomic<bool>& interruptMsgProc) LOCKS_EXCLUDED(cs_main)
+void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnman& connman, CTxMemPool& mempool, const std::atomic<bool>& interruptMsgProc) LOCKS_EXCLUDED(cs_main)
{
AssertLockNotHeld(cs_main);
@@ -1655,8 +1711,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
std::vector<CInv> vNotFound;
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
- // mempool entries added before this time have likely expired from mapRelay
- const std::chrono::seconds longlived_mempool_time = GetTime<std::chrono::seconds>() - RELAY_TX_CACHE_TIME;
+ const std::chrono::seconds now = GetTime<std::chrono::seconds>();
// Get last mempool request time
const std::chrono::seconds mempool_req = pfrom.m_tx_relay != nullptr ? pfrom.m_tx_relay->m_last_mempool_req.load()
: std::chrono::seconds::min();
@@ -1664,7 +1719,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
// Process as many TX items from the front of the getdata queue as
// possible, since they're common and it's efficient to batch process
// them.
- while (it != pfrom.vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
+ while (it != pfrom.vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX || it->type == MSG_WTX)) {
if (interruptMsgProc) return;
// The send buffer provides backpressure. If there's no space in
// the buffer, pause processing until the next call.
@@ -1677,11 +1732,23 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
continue;
}
- CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, mempool_req, longlived_mempool_time);
+ CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, inv.type == MSG_WTX, mempool_req, now);
if (tx) {
+ // WTX and WITNESS_TX imply we serialize with witness
int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
- connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx));
- mempool.RemoveUnbroadcastTx(inv.hash);
+ connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx));
+ mempool.RemoveUnbroadcastTx(tx->GetHash());
+ // As we're going to send tx, make sure its unconfirmed parents are made requestable.
+ for (const auto& txin : tx->vin) {
+ auto txinfo = mempool.info(txin.prevout.hash);
+ if (txinfo.tx && txinfo.m_time > now - UNCONDITIONAL_RELAY_DELAY) {
+ // Relaying a transaction with a recent but unconfirmed parent.
+ if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(txin.prevout.hash))) {
+ LOCK(cs_main);
+ State(pfrom.GetId())->m_recently_announced_invs.insert(txin.prevout.hash);
+ }
+ }
+ }
} else {
vNotFound.push_back(inv);
}
@@ -1715,7 +1782,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
// In normal operation, we often send NOTFOUND messages for parents of
// transactions that we relay; if a peer is missing a parent, they may
// assume we have them and request the parents from us.
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
}
}
@@ -1727,7 +1794,7 @@ static uint32_t GetFetchFlags(const CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_ma
return nFetchFlags;
}
-inline void static SendBlockTransactions(const CBlock& block, const BlockTransactionsRequest& req, CNode& pfrom, CConnman* connman) {
+inline void static SendBlockTransactions(const CBlock& block, const BlockTransactionsRequest& req, CNode& pfrom, CConnman& connman) {
BlockTransactions resp(req);
for (size_t i = 0; i < req.indexes.size(); i++) {
if (req.indexes[i] >= block.vtx.size()) {
@@ -1740,10 +1807,10 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac
LOCK(cs_main);
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
int nSendFlags = State(pfrom.GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS;
- connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
+ connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
}
-static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateManager& chainman, CTxMemPool& mempool, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block)
+static void ProcessHeadersMessage(CNode& pfrom, CConnman& connman, ChainstateManager& chainman, CTxMemPool& mempool, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block)
{
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
size_t nCount = headers.size();
@@ -1769,7 +1836,7 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateMan
// nUnconnectingHeaders gets reset back to 0.
if (!LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) {
nodestate->nUnconnectingHeaders++;
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256()));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256()));
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
headers[0].GetHash().ToString(),
headers[0].hashPrevBlock.ToString(),
@@ -1834,7 +1901,7 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateMan
// TODO: optimize: if pindexLast is an ancestor of ::ChainActive().Tip or pindexBestHeader, continue
// from there instead.
LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom.GetId(), pfrom.nStartingHeight);
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256()));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256()));
}
bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus());
@@ -1884,7 +1951,7 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateMan
// In any case, we want to download using a compact block, not a regular one
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
}
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
}
}
}
@@ -1895,8 +1962,8 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateMan
// headers to fetch from this peer.
if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
// This peer has too little work on their headers chain to help
- // us sync -- disconnect if using an outbound slot (unless
- // whitelisted or addnode).
+ // us sync -- disconnect if it is an outbound disconnection
+ // candidate.
// Note: We compare their tip to nMinimumChainWork (rather than
// ::ChainActive().Tip()) because we won't start block download
// until we have a headers chain that has at least
@@ -1925,7 +1992,7 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman* connman, ChainstateMan
return;
}
-void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uint256>& orphan_work_set, std::list<CTransactionRef>& removed_txn) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans)
+void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uint256>& orphan_work_set, std::list<CTransactionRef>& removed_txn) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans)
{
AssertLockHeld(cs_main);
AssertLockHeld(g_cs_orphans);
@@ -1949,7 +2016,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
if (setMisbehaving.count(fromPeer)) continue;
if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
- RelayTransaction(orphanHash, *connman);
+ RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), connman);
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i));
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
@@ -1966,17 +2033,30 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
if (MaybePunishNodeForTx(fromPeer, orphan_state)) {
setMisbehaving.insert(fromPeer);
}
- LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString());
+ LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s from peer=%d. %s\n",
+ orphanHash.ToString(),
+ fromPeer,
+ orphan_state.ToString());
}
// Has inputs but not accepted to mempool
// Probably non-standard or insufficient fee
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString());
- if (!orphanTx.HasWitness() && orphan_state.GetResult() != TxValidationResult::TX_WITNESS_MUTATED) {
- // Do not use rejection cache for witness transactions or
- // witness-stripped transactions, as they can have been malleated.
- // See https://github.com/bitcoin/bitcoin/issues/8279 for details.
+ if (orphan_state.GetResult() != TxValidationResult::TX_WITNESS_STRIPPED) {
+ // We can add the wtxid of this transaction to our reject filter.
+ // Do not add txids of witness transactions or witness-stripped
+ // transactions to the filter, as they can have been malleated;
+ // adding such txids to the reject filter would potentially
+ // interfere with relay of valid transactions from peers that
+ // do not support wtxid-based relay. See
+ // https://github.com/bitcoin/bitcoin/issues/8279 for details.
+ // We can remove this restriction (and always add wtxids to
+ // the filter even for witness stripped transactions) once
+ // wtxid-based relay is broadly deployed.
+ // See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034
+ // for concerns around weakening security of unupgraded nodes
+ // if we start doing this too early.
assert(recentRejects);
- recentRejects->insert(orphanHash);
+ recentRejects->insert(orphanTx.GetWitnessHash());
}
EraseOrphanTx(orphanHash);
done = true;
@@ -2210,7 +2290,7 @@ void ProcessMessage(
const CChainParams& chainparams,
ChainstateManager& chainman,
CTxMemPool& mempool,
- CConnman* connman,
+ CConnman& connman,
BanMan* banman,
const std::atomic<bool>& interruptMsgProc)
{
@@ -2248,7 +2328,7 @@ void ProcessMessage(
nServices = ServiceFlags(nServiceInt);
if (!pfrom.fInbound)
{
- connman->SetServices(pfrom.addr, nServices);
+ connman.SetServices(pfrom.addr, nServices);
}
if (!pfrom.fInbound && !pfrom.fFeeler && !pfrom.m_manual_connection && !HasAllDesirableServiceFlags(nServices))
{
@@ -2277,7 +2357,7 @@ void ProcessMessage(
if (!vRecv.empty())
vRecv >> fRelay;
// Disconnect if we connected to ourself
- if (pfrom.fInbound && !connman->CheckIncomingNonce(nNonce))
+ if (pfrom.fInbound && !connman.CheckIncomingNonce(nNonce))
{
LogPrintf("connected to self at %s, disconnecting\n", pfrom.addr.ToString());
pfrom.fDisconnect = true;
@@ -2293,7 +2373,11 @@ void ProcessMessage(
if (pfrom.fInbound)
PushNodeVersion(pfrom, connman, GetAdjustedTime());
- connman->PushMessage(&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK));
+ if (nVersion >= WTXID_RELAY_VERSION) {
+ connman.PushMessage(&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::WTXIDRELAY));
+ }
+
+ connman.PushMessage(&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK));
pfrom.nServices = nServices;
pfrom.SetAddrLocal(addrMe);
@@ -2349,9 +2433,9 @@ void ProcessMessage(
}
// Get recent addresses
- connman->PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
+ connman.PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
pfrom.fGetAddr = true;
- connman->MarkAddressGood(pfrom.addr);
+ connman.MarkAddressGood(pfrom.addr);
}
std::string remoteAddr;
@@ -2370,7 +2454,7 @@ void ProcessMessage(
// If the peer is old enough to have the old alert system, send it the final alert.
if (pfrom.nVersion <= 70012) {
CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION);
- connman->PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert));
+ connman.PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert));
}
// Feeler connections exist only to verify if address is online.
@@ -2410,7 +2494,7 @@ void ProcessMessage(
// We send this to non-NODE NETWORK peers as well, because even
// non-NODE NETWORK peers can announce blocks (such as pruning
// nodes)
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS));
}
if (pfrom.nVersion >= SHORT_IDS_BLOCKS_VERSION) {
// Tell our peer we are willing to provide version 1 or 2 cmpctblocks
@@ -2421,14 +2505,33 @@ void ProcessMessage(
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 2;
if (pfrom.GetLocalServices() & NODE_WITNESS)
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
nCMPCTBLOCKVersion = 1;
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
}
pfrom.fSuccessfullyConnected = true;
return;
}
+ // Feature negotiation of wtxidrelay should happen between VERSION and
+ // VERACK, to avoid relay problems from switching after a connection is up
+ if (msg_type == NetMsgType::WTXIDRELAY) {
+ if (pfrom.fSuccessfullyConnected) {
+ // Disconnect peers that send wtxidrelay message after VERACK; this
+ // must be negotiated between VERSION and VERACK.
+ pfrom.fDisconnect = true;
+ return;
+ }
+ if (pfrom.nVersion >= WTXID_RELAY_VERSION) {
+ LOCK(cs_main);
+ if (!State(pfrom.GetId())->m_wtxid_relay) {
+ State(pfrom.GetId())->m_wtxid_relay = true;
+ g_wtxid_relay_peers++;
+ }
+ }
+ return;
+ }
+
if (!pfrom.fSuccessfullyConnected) {
// Must have a verack message before anything else
LOCK(cs_main);
@@ -2468,19 +2571,21 @@ void ProcessMessage(
if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
addr.nTime = nNow - 5 * 24 * 60 * 60;
pfrom.AddAddressKnown(addr);
- if (banman->IsDiscouraged(addr)) continue; // Do not process banned/discouraged addresses beyond remembering we received them
- if (banman->IsBanned(addr)) continue;
+ if (banman && (banman->IsDiscouraged(addr) || banman->IsBanned(addr))) {
+ // Do not process banned/discouraged addresses beyond remembering we received them
+ continue;
+ }
bool fReachable = IsReachable(addr);
if (addr.nTime > nSince && !pfrom.fGetAddr && vAddr.size() <= 10 && addr.IsRoutable())
{
// Relay to a limited number of other nodes
- RelayAddress(addr, fReachable, *connman);
+ RelayAddress(addr, fReachable, connman);
}
// Do not store addresses outside our network
if (fReachable)
vAddrOk.push_back(addr);
}
- connman->AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60);
+ connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60);
if (vAddr.size() < 1000)
pfrom.fGetAddr = false;
if (pfrom.fOneShot)
@@ -2531,9 +2636,10 @@ void ProcessMessage(
// block-relay-only peer
bool fBlocksOnly = !g_relay_txes || (pfrom.m_tx_relay == nullptr);
- // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
- if (pfrom.HasPermission(PF_RELAY))
+ // Allow peers with relay permission to send data other than blocks in blocks only mode
+ if (pfrom.HasPermission(PF_RELAY)) {
fBlocksOnly = false;
+ }
LOCK(cs_main);
@@ -2546,6 +2652,13 @@ void ProcessMessage(
if (interruptMsgProc)
return;
+ // ignore INVs that don't match wtxidrelay setting
+ if (State(pfrom.GetId())->m_wtxid_relay) {
+ if (inv.type == MSG_TX) continue;
+ } else {
+ if (inv.type == MSG_WTX) continue;
+ }
+
bool fAlreadyHave = AlreadyHave(inv, mempool);
LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
@@ -2564,7 +2677,7 @@ void ProcessMessage(
best_block = &inv.hash;
}
} else {
- pfrom.AddInventoryKnown(inv);
+ pfrom.AddKnownTx(inv.hash);
if (fBlocksOnly) {
LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom.GetId());
pfrom.fDisconnect = true;
@@ -2576,7 +2689,7 @@ void ProcessMessage(
}
if (best_block != nullptr) {
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), *best_block));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), *best_block));
LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, best_block->ToString(), pfrom.GetId());
}
@@ -2784,7 +2897,7 @@ void ProcessMessage(
// will re-announce the new block via headers (or compact blocks again)
// in the SendMessages logic.
nodestate->pindexBestHeaderSent = pindex ? pindex : ::ChainActive().Tip();
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders));
return;
}
@@ -2803,26 +2916,50 @@ void ProcessMessage(
vRecv >> ptx;
const CTransaction& tx = *ptx;
- CInv inv(MSG_TX, tx.GetHash());
- pfrom.AddInventoryKnown(inv);
+ const uint256& txid = ptx->GetHash();
+ const uint256& wtxid = ptx->GetWitnessHash();
LOCK2(cs_main, g_cs_orphans);
+ CNodeState* nodestate = State(pfrom.GetId());
+
+ const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid;
+ pfrom.AddKnownTx(hash);
+ if (nodestate->m_wtxid_relay && txid != wtxid) {
+ // Insert txid into filterInventoryKnown, even for
+ // wtxidrelay peers. This prevents re-adding of
+ // unconfirmed parents to the recently_announced
+ // filter, when a child tx is requested. See
+ // ProcessGetData().
+ pfrom.AddKnownTx(txid);
+ }
+
TxValidationState state;
- CNodeState* nodestate = State(pfrom.GetId());
- nodestate->m_tx_download.m_tx_announced.erase(inv.hash);
- nodestate->m_tx_download.m_tx_in_flight.erase(inv.hash);
- EraseTxRequest(inv.hash);
+ nodestate->m_tx_download.m_tx_announced.erase(hash);
+ nodestate->m_tx_download.m_tx_in_flight.erase(hash);
+ EraseTxRequest(hash);
std::list<CTransactionRef> lRemovedTxn;
- if (!AlreadyHave(inv, mempool) &&
+ // We do the AlreadyHave() check using wtxid, rather than txid - in the
+ // absence of witness malleation, this is strictly better, because the
+ // recent rejects filter may contain the wtxid but will never contain
+ // the txid of a segwit transaction that has been rejected.
+ // In the presence of witness malleation, it's possible that by only
+ // doing the check with wtxid, we could overlook a transaction which
+ // was confirmed with a different witness, or exists in our mempool
+ // with a different witness, but this has limited downside:
+ // mempool validation does its own lookup of whether we have the txid
+ // already; and an adversary can already relay us old transactions
+ // (older than our recency filter) if trying to DoS us, without any need
+ // for witness malleation.
+ if (!AlreadyHave(CInv(MSG_WTX, wtxid), mempool) &&
AcceptToMemoryPool(mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
mempool.check(&::ChainstateActive().CoinsTip());
- RelayTransaction(tx.GetHash(), *connman);
+ RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), connman);
for (unsigned int i = 0; i < tx.vout.size(); i++) {
- auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(inv.hash, i));
+ auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i));
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
for (const auto& elem : it_by_prev->second) {
pfrom.orphan_work_set.insert(elem->first);
@@ -2853,10 +2990,17 @@ void ProcessMessage(
uint32_t nFetchFlags = GetFetchFlags(pfrom);
const auto current_time = GetTime<std::chrono::microseconds>();
- for (const CTxIn& txin : tx.vin) {
- CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
- pfrom.AddInventoryKnown(_inv);
- if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), _inv.hash, current_time);
+ if (!State(pfrom.GetId())->m_wtxid_relay) {
+ for (const CTxIn& txin : tx.vin) {
+ // Here, we only have the txid (and not wtxid) of the
+ // inputs, so we only request parents from
+ // non-wtxid-relay peers.
+ // Eventually we should replace this with an improved
+ // protocol for getting all unconfirmed parents.
+ CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
+ pfrom.AddKnownTx(txin.prevout.hash);
+ if (!AlreadyHave(_inv, mempool)) RequestTx(State(pfrom.GetId()), _inv.hash, current_time);
+ }
}
AddOrphanTx(ptx, pfrom.GetId());
@@ -2870,15 +3014,30 @@ void ProcessMessage(
LogPrint(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString());
// We will continue to reject this tx since it has rejected
// parents so avoid re-requesting it from other peers.
+ // Here we add both the txid and the wtxid, as we know that
+ // regardless of what witness is provided, we will not accept
+ // this, so we don't need to allow for redownload of this txid
+ // from any of our non-wtxidrelay peers.
recentRejects->insert(tx.GetHash());
+ recentRejects->insert(tx.GetWitnessHash());
}
} else {
- if (!tx.HasWitness() && state.GetResult() != TxValidationResult::TX_WITNESS_MUTATED) {
- // Do not use rejection cache for witness transactions or
- // witness-stripped transactions, as they can have been malleated.
- // See https://github.com/bitcoin/bitcoin/issues/8279 for details.
+ if (state.GetResult() != TxValidationResult::TX_WITNESS_STRIPPED) {
+ // We can add the wtxid of this transaction to our reject filter.
+ // Do not add txids of witness transactions or witness-stripped
+ // transactions to the filter, as they can have been malleated;
+ // adding such txids to the reject filter would potentially
+ // interfere with relay of valid transactions from peers that
+ // do not support wtxid-based relay. See
+ // https://github.com/bitcoin/bitcoin/issues/8279 for details.
+ // We can remove this restriction (and always add wtxids to
+ // the filter even for witness stripped transactions) once
+ // wtxid-based relay is broadly deployed.
+ // See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034
+ // for concerns around weakening security of unupgraded nodes
+ // if we start doing this too early.
assert(recentRejects);
- recentRejects->insert(tx.GetHash());
+ recentRejects->insert(tx.GetWitnessHash());
if (RecursiveDynamicUsage(*ptx) < 100000) {
AddToCompactExtraTransactions(ptx);
}
@@ -2887,15 +3046,15 @@ void ProcessMessage(
}
if (pfrom.HasPermission(PF_FORCERELAY)) {
- // Always relay transactions received from whitelisted peers, even
+ // Always relay transactions received from peers with forcerelay permission, even
// if they were already in the mempool,
// allowing the node to function as a gateway for
// nodes hidden behind it.
if (!mempool.exists(tx.GetHash())) {
- LogPrintf("Not relaying non-mempool transaction %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
+ LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
} else {
- LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
- RelayTransaction(tx.GetHash(), *connman);
+ LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
+ RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), connman);
}
}
}
@@ -2920,8 +3079,7 @@ void ProcessMessage(
// peer simply for relaying a tx that our recentRejects has caught,
// regardless of false positives.
- if (state.IsInvalid())
- {
+ if (state.IsInvalid()) {
LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(),
pfrom.GetId(),
state.ToString());
@@ -2949,7 +3107,7 @@ void ProcessMessage(
if (!LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
if (!::ChainstateActive().IsInitialBlockDownload())
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256()));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256()));
return;
}
@@ -3010,7 +3168,7 @@ void ProcessMessage(
// so we just grab the block via normal getdata
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash());
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
}
return;
}
@@ -3044,14 +3202,14 @@ void ProcessMessage(
PartiallyDownloadedBlock& partialBlock = *(*queuedBlockIt)->partialBlock;
ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status == READ_STATUS_INVALID) {
- MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case of whitelist
+ MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect
Misbehaving(pfrom.GetId(), 100, strprintf("Peer %d sent us invalid compact block\n", pfrom.GetId()));
return;
} else if (status == READ_STATUS_FAILED) {
// Duplicate txindexes, the block is now in-flight, so just request it
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash());
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return;
}
@@ -3068,7 +3226,7 @@ void ProcessMessage(
fProcessBLOCKTXN = true;
} else {
req.blockhash = pindex->GetBlockHash();
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
}
} else {
// This block is either already in flight from a different
@@ -3094,7 +3252,7 @@ void ProcessMessage(
// mempool will probably be useless - request the block normally
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash());
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return;
} else {
// If this was an announce-cmpctblock, we want the same treatment as a header message
@@ -3177,14 +3335,14 @@ void ProcessMessage(
PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock;
ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn);
if (status == READ_STATUS_INVALID) {
- MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case of whitelist
+ MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect
Misbehaving(pfrom.GetId(), 100, strprintf("Peer %d sent us invalid compact block/non-matching block transactions\n", pfrom.GetId()));
return;
} else if (status == READ_STATUS_FAILED) {
// Might have collided, fall back to getdata now :(
std::vector<CInv> invs;
invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash));
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
} else {
// Block is either okay, or possibly we received
// READ_STATUS_CHECKBLOCK_FAILED.
@@ -3319,10 +3477,11 @@ void ProcessMessage(
pfrom.fSentAddr = true;
pfrom.vAddrToSend.clear();
- std::vector<CAddress> vAddr = connman->GetAddresses();
+ std::vector<CAddress> vAddr = connman.GetAddresses();
FastRandomContext insecure_rand;
for (const CAddress &addr : vAddr) {
- if (!banman->IsDiscouraged(addr) && !banman->IsBanned(addr)) {
+ bool banned_or_discouraged = banman && (banman->IsDiscouraged(addr) || banman->IsBanned(addr));
+ if (!banned_or_discouraged) {
pfrom.PushAddress(addr, insecure_rand);
}
}
@@ -3340,7 +3499,7 @@ void ProcessMessage(
return;
}
- if (connman->OutboundTargetReached(false) && !pfrom.HasPermission(PF_MEMPOOL))
+ if (connman.OutboundTargetReached(false) && !pfrom.HasPermission(PF_MEMPOOL))
{
if (!pfrom.HasPermission(PF_NOBAN))
{
@@ -3373,7 +3532,7 @@ void ProcessMessage(
// it, if the remote node sends a ping once per second and this node takes 5
// seconds to respond to each, the 5th ping the remote sends would appear to
// return very quickly.
- connman->PushMessage(&pfrom, msgMaker.Make(NetMsgType::PONG, nonce));
+ connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::PONG, nonce));
}
return;
}
@@ -3513,17 +3672,17 @@ void ProcessMessage(
}
if (msg_type == NetMsgType::GETCFILTERS) {
- ProcessGetCFilters(pfrom, vRecv, chainparams, *connman);
+ ProcessGetCFilters(pfrom, vRecv, chainparams, connman);
return;
}
if (msg_type == NetMsgType::GETCFHEADERS) {
- ProcessGetCFHeaders(pfrom, vRecv, chainparams, *connman);
+ ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
return;
}
if (msg_type == NetMsgType::GETCFCHECKPT) {
- ProcessGetCFCheckPt(pfrom, vRecv, chainparams, *connman);
+ ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
return;
}
@@ -3535,7 +3694,7 @@ void ProcessMessage(
vRecv >> vInv;
if (vInv.size() <= MAX_PEER_TX_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
for (CInv &inv : vInv) {
- if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX) {
+ if (inv.type == MSG_TX || inv.type == MSG_WITNESS_TX || inv.type == MSG_WTX) {
// If we receive a NOTFOUND message for a txid we requested, erase
// it from our data structures for this peer.
auto in_flight_it = state->m_tx_download.m_tx_in_flight.find(inv.hash);
@@ -3616,12 +3775,12 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
bool fMoreWork = false;
if (!pfrom->vRecvGetData.empty())
- ProcessGetData(*pfrom, chainparams, connman, m_mempool, interruptMsgProc);
+ ProcessGetData(*pfrom, chainparams, *connman, m_mempool, interruptMsgProc);
if (!pfrom->orphan_work_set.empty()) {
std::list<CTransactionRef> removed_txn;
LOCK2(cs_main, g_cs_orphans);
- ProcessOrphanTx(connman, m_mempool, pfrom->orphan_work_set, removed_txn);
+ ProcessOrphanTx(*connman, m_mempool, pfrom->orphan_work_set, removed_txn);
for (const CTransactionRef& removedTx : removed_txn) {
AddToCompactExtraTransactions(removedTx);
}
@@ -3681,7 +3840,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
}
try {
- ProcessMessage(*pfrom, msg_type, vRecv, msg.m_time, chainparams, m_chainman, m_mempool, connman, m_banman, interruptMsgProc);
+ ProcessMessage(*pfrom, msg_type, vRecv, msg.m_time, chainparams, m_chainman, m_mempool, *connman, m_banman, interruptMsgProc);
if (interruptMsgProc)
return false;
if (!pfrom->vRecvGetData.empty())
@@ -3834,17 +3993,19 @@ namespace {
class CompareInvMempoolOrder
{
CTxMemPool *mp;
+ bool m_wtxid_relay;
public:
- explicit CompareInvMempoolOrder(CTxMemPool *_mempool)
+ explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid)
{
mp = _mempool;
+ m_wtxid_relay = use_wtxid;
}
bool operator()(std::set<uint256>::iterator a, std::set<uint256>::iterator b)
{
/* As std::make_heap produces a max-heap, we want the entries with the
* fewest ancestors/highest fee to sort later. */
- return mp->CompareDepthAndScore(*b, *a);
+ return mp->CompareDepthAndScore(*b, *a, m_wtxid_relay);
}
};
}
@@ -4152,8 +4313,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
LOCK(pto->m_tx_relay->cs_filter);
for (const auto& txinfo : vtxinfo) {
- const uint256& hash = txinfo.tx->GetHash();
- CInv inv(MSG_TX, hash);
+ const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
+ CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
pto->m_tx_relay->setInventoryTxToSend.erase(hash);
// Don't send transactions that peers will not put into their mempool
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
@@ -4163,6 +4324,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
}
pto->m_tx_relay->filterInventoryKnown.insert(hash);
+ // Responses to MEMPOOL requests bypass the m_recently_announced_invs filter.
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
@@ -4187,7 +4349,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
}
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
// A heap is used so that not all items need sorting if only a few are being sent.
- CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool);
+ CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay);
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
// No reason to drain out at many times the network's capacity,
// especially since we have many peers and some will draw much shorter delays.
@@ -4206,17 +4368,20 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
continue;
}
// Not in the mempool anymore? don't bother sending it.
- auto txinfo = m_mempool.info(hash);
+ auto txinfo = m_mempool.info(hash, state.m_wtxid_relay);
if (!txinfo.tx) {
continue;
}
+ auto txid = txinfo.tx->GetHash();
+ auto wtxid = txinfo.tx->GetWitnessHash();
// Peer told you to not send transactions at that feerate? Don't bother sending it.
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
// Send
- vInv.push_back(CInv(MSG_TX, hash));
+ State(pto->GetId())->m_recently_announced_invs.insert(hash);
+ vInv.push_back(CInv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash));
nRelayedTransactions++;
{
// Expire old relay messages
@@ -4226,9 +4391,14 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
vRelayExpiration.pop_front();
}
- auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx)));
+ auto ret = mapRelay.emplace(txid, std::move(txinfo.tx));
if (ret.second) {
- vRelayExpiration.push_back(std::make_pair(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first));
+ vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret.first);
+ }
+ // Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid
+ auto ret2 = mapRelay.emplace(wtxid, ret.first->second);
+ if (ret2.second) {
+ vRelayExpiration.emplace_back(nNow + std::chrono::microseconds{RELAY_TX_CACHE_TIME}.count(), ret2.first);
}
}
if (vInv.size() == MAX_INV_SZ) {
@@ -4236,6 +4406,14 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
vInv.clear();
}
pto->m_tx_relay->filterInventoryKnown.insert(hash);
+ if (hash != txid) {
+ // Insert txid into filterInventoryKnown, even for
+ // wtxidrelay peers. This prevents re-adding of
+ // unconfirmed parents to the recently_announced
+ // filter, when a child tx is requested. See
+ // ProcessGetData().
+ pto->m_tx_relay->filterInventoryKnown.insert(txid);
+ }
}
}
}
@@ -4273,9 +4451,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// Check for headers sync timeouts
if (state.fSyncStarted && state.nHeadersSyncTimeout < std::numeric_limits<int64_t>::max()) {
// Detect whether this is a stalling initial-headers-sync peer
- if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24*60*60) {
+ if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) {
if (nNow > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) {
- // Disconnect a (non-whitelisted) peer if it is our only sync peer,
+ // Disconnect a peer (without the noban permission) if it is our only sync peer,
// and we have others we could be using instead.
// Note: If all our peers are inbound, then we won't
// disconnect our sync peer for stalling; we have bigger
@@ -4285,7 +4463,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
pto->fDisconnect = true;
return true;
} else {
- LogPrintf("Timeout downloading headers from whitelisted peer=%d, not disconnecting\n", pto->GetId());
+ LogPrintf("Timeout downloading headers from noban peer=%d, not disconnecting\n", pto->GetId());
// Reset the headers sync state so that we have a
// chance to try downloading from a different peer.
// Note: this will also result in at least one more
@@ -4360,7 +4538,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// Erase this entry from tx_process_time (it may be added back for
// processing at a later time, see below)
tx_process_time.erase(tx_process_time.begin());
- CInv inv(MSG_TX | GetFetchFlags(*pto), txid);
+ CInv inv(state.m_wtxid_relay ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), txid);
if (!AlreadyHave(inv, m_mempool)) {
// If this transaction was last requested more than 1 minute ago,
// then request.
@@ -4379,7 +4557,15 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// up processing to happen after the download times out
// (with a slight delay for inbound peers, to prefer
// requests to outbound peers).
- const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload);
+ // Don't apply the txid-delay to re-requests of a
+ // transaction; the heuristic of delaying requests to
+ // txid-relay peers is to save bandwidth on initial
+ // announcement of a transaction, and doesn't make sense
+ // for a followup request if our first peer times out (and
+ // would open us up to an attacker using inbound
+ // wtxid-relay to prevent us from requesting transactions
+ // from outbound txid-relay peers).
+ const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload, false);
tx_process_time.emplace(next_process_time, txid);
}
} else {
@@ -4443,6 +4629,7 @@ public:
// orphan transactions
mapOrphanTransactions.clear();
mapOrphanTransactionsByPrev.clear();
+ g_orphans_by_wtxid.clear();
}
};
static CNetProcessingCleanup instance_of_cnetprocessingcleanup;
diff --git a/src/net_processing.h b/src/net_processing.h
index 48bcbc1a6c..2d98714122 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -23,10 +23,13 @@ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100;
static const bool DEFAULT_PEERBLOOMFILTERS = false;
static const bool DEFAULT_PEERBLOCKFILTERS = false;
+/** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */
+static const int DISCOURAGEMENT_THRESHOLD{100};
class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface {
private:
CConnman* const connman;
+ /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
BanMan* const m_banman;
ChainstateManager& m_chainman;
CTxMemPool& m_mempool;
@@ -97,6 +100,6 @@ struct CNodeStateStats {
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats);
/** Relay transaction to every node */
-void RelayTransaction(const uint256&, const CConnman& connman);
+void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
#endif // BITCOIN_NET_PROCESSING_H
diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index 6744391616..0aaba440b8 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -726,12 +726,10 @@ bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
*/
std::vector<unsigned char> CService::GetKey() const
{
- std::vector<unsigned char> vKey;
- vKey.resize(18);
- memcpy(vKey.data(), ip, 16);
- vKey[16] = port / 0x100; // most significant byte of our port
- vKey[17] = port & 0x0FF; // least significant byte of our port
- return vKey;
+ auto key = GetAddrBytes();
+ key.push_back(port / 0x100); // most significant byte of our port
+ key.push_back(port & 0x0FF); // least significant byte of our port
+ return key;
}
std::string CService::ToStringPort() const
diff --git a/src/netaddress.h b/src/netaddress.h
index c201012154..f2daad7fb6 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -160,7 +160,11 @@ class CService : public CNetAddr
CService(const struct in6_addr& ipv6Addr, uint16_t port);
explicit CService(const struct sockaddr_in6& addr);
- SERIALIZE_METHODS(CService, obj) { READWRITE(obj.ip, Using<BigEndianFormatter<2>>(obj.port)); }
+ SERIALIZE_METHODS(CService, obj)
+ {
+ READWRITEAS(CNetAddr, obj);
+ READWRITE(Using<BigEndianFormatter<2>>(obj.port));
+ }
};
bool SanityCheckASMap(const std::vector<bool>& asmap);
diff --git a/src/node/context.h b/src/node/context.h
index c783c39cd6..be568cba36 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -6,6 +6,7 @@
#define BITCOIN_NODE_CONTEXT_H
#include <cassert>
+#include <functional>
#include <memory>
#include <vector>
@@ -41,6 +42,7 @@ struct NodeContext {
std::unique_ptr<interfaces::Chain> chain;
std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients;
std::unique_ptr<CScheduler> scheduler;
+ std::function<void()> rpc_interruption_point = [] {};
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index 3841d8687d..5633abe817 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -80,9 +80,10 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
if (relay) {
// the mempool tracks locally submitted transactions to make a
// best-effort of initial broadcast
- node.mempool->AddUnbroadcastTx(hashTx);
+ node.mempool->AddUnbroadcastTx(hashTx, tx->GetWitnessHash());
- RelayTransaction(hashTx, *node.connman);
+ LOCK(cs_main);
+ RelayTransaction(hashTx, tx->GetWitnessHash(), *node.connman);
}
return TransactionError::OK;
diff --git a/src/policy/fees.h b/src/policy/fees.h
index e445c1590d..e79dbc9868 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -273,7 +273,7 @@ public:
/** Create new FeeFilterRounder */
explicit FeeFilterRounder(const CFeeRate& minIncrementalFee);
- /** Quantize a minimum fee for privacy purpose before broadcast **/
+ /** Quantize a minimum fee for privacy purpose before broadcast. Not thread-safe due to use of FastRandomContext */
CAmount round(CAmount currentMinFee);
private:
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 2dfe4bee74..ee77ca3b94 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -46,6 +46,7 @@ const char *GETCFHEADERS="getcfheaders";
const char *CFHEADERS="cfheaders";
const char *GETCFCHECKPT="getcfcheckpt";
const char *CFCHECKPT="cfcheckpt";
+const char *WTXIDRELAY="wtxidrelay";
} // namespace NetMsgType
/** All known message types. Keep this in the same order as the list of
@@ -83,6 +84,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::CFHEADERS,
NetMsgType::GETCFCHECKPT,
NetMsgType::CFCHECKPT,
+ NetMsgType::WTXIDRELAY,
};
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
@@ -177,6 +179,8 @@ std::string CInv::GetCommand() const
switch (masked)
{
case MSG_TX: return cmd.append(NetMsgType::TX);
+ // WTX is not a message type, just an inv type
+ case MSG_WTX: return cmd.append("wtx");
case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK);
case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK);
case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK);
diff --git a/src/protocol.h b/src/protocol.h
index 985f44640b..d83da2034a 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -261,6 +261,12 @@ extern const char* GETCFCHECKPT;
* evenly spaced filter headers for blocks on the requested chain.
*/
extern const char* CFCHECKPT;
+/**
+ * Indicates that a node prefers to relay transactions via wtxid, rather than
+ * txid.
+ * @since protocol version 70016 as described by BIP 339.
+ */
+extern const char *WTXIDRELAY;
}; // namespace NetMsgType
/* Get a vector of all valid message types (see above) */
@@ -371,7 +377,13 @@ public:
READWRITE(nVersion);
}
if ((s.GetType() & SER_DISK) ||
- (nVersion >= CADDR_TIME_VERSION && !(s.GetType() & SER_GETHASH))) {
+ (nVersion != INIT_PROTO_VERSION && !(s.GetType() & SER_GETHASH))) {
+ // 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.
READWRITE(obj.nTime);
}
READWRITE(Using<CustomUintFormatter<8>>(obj.nServices));
@@ -391,11 +403,12 @@ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 2;
* These numbers are defined by the protocol. When adding a new value, be sure
* to mention it in the respective BIP.
*/
-enum GetDataMsg {
+enum GetDataMsg : uint32_t {
UNDEFINED = 0,
MSG_TX = 1,
MSG_BLOCK = 2,
- // The following can only occur in getdata. Invs always use TX or BLOCK.
+ MSG_WTX = 5, //!< Defined in BIP 339
+ // The following can only occur in getdata. Invs always use TX/WTX or BLOCK.
MSG_FILTERED_BLOCK = 3, //!< Defined in BIP37
MSG_CMPCT_BLOCK = 4, //!< Defined in BIP152
MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG, //!< Defined in BIP144
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index c7dd16d2ed..ecb753a306 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -528,6 +528,11 @@ int GuiMain(int argc, char* argv[])
// Parse URIs on command line -- this can affect Params()
PaymentServer::ipcParseCommandLine(*node, argc, argv);
#endif
+ if (!node->initSettings(error)) {
+ node->initError(Untranslated(error));
+ QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error initializing settings: %1").arg(QString::fromStdString(error)));
+ return EXIT_FAILURE;
+ }
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().NetworkIDString()));
assert(!networkStyle.isNull());
@@ -556,6 +561,8 @@ int GuiMain(int argc, char* argv[])
/// 9. Main GUI initialization
// Install global event filter that makes sure that long tooltips can be word-wrapped
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
+ // Install global event filter that makes sure that out-of-focus labels do not contain text cursor.
+ app.installEventFilter(new GUIUtil::LabelOutOfFocusEventFilter(&app));
#if defined(Q_OS_WIN)
// Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION)
qApp->installNativeEventFilter(new WinShutdownMonitor());
diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc
index 037b23e4b2..7115459808 100644
--- a/src/qt/bitcoin.qrc
+++ b/src/qt/bitcoin.qrc
@@ -45,42 +45,42 @@
<file alias="network_disabled">res/icons/network_disabled.png</file>
<file alias="proxy">res/icons/proxy.png</file>
</qresource>
- <qresource prefix="/movies">
- <file alias="spinner-000">res/movies/spinner-000.png</file>
- <file alias="spinner-001">res/movies/spinner-001.png</file>
- <file alias="spinner-002">res/movies/spinner-002.png</file>
- <file alias="spinner-003">res/movies/spinner-003.png</file>
- <file alias="spinner-004">res/movies/spinner-004.png</file>
- <file alias="spinner-005">res/movies/spinner-005.png</file>
- <file alias="spinner-006">res/movies/spinner-006.png</file>
- <file alias="spinner-007">res/movies/spinner-007.png</file>
- <file alias="spinner-008">res/movies/spinner-008.png</file>
- <file alias="spinner-009">res/movies/spinner-009.png</file>
- <file alias="spinner-010">res/movies/spinner-010.png</file>
- <file alias="spinner-011">res/movies/spinner-011.png</file>
- <file alias="spinner-012">res/movies/spinner-012.png</file>
- <file alias="spinner-013">res/movies/spinner-013.png</file>
- <file alias="spinner-014">res/movies/spinner-014.png</file>
- <file alias="spinner-015">res/movies/spinner-015.png</file>
- <file alias="spinner-016">res/movies/spinner-016.png</file>
- <file alias="spinner-017">res/movies/spinner-017.png</file>
- <file alias="spinner-018">res/movies/spinner-018.png</file>
- <file alias="spinner-019">res/movies/spinner-019.png</file>
- <file alias="spinner-020">res/movies/spinner-020.png</file>
- <file alias="spinner-021">res/movies/spinner-021.png</file>
- <file alias="spinner-022">res/movies/spinner-022.png</file>
- <file alias="spinner-023">res/movies/spinner-023.png</file>
- <file alias="spinner-024">res/movies/spinner-024.png</file>
- <file alias="spinner-025">res/movies/spinner-025.png</file>
- <file alias="spinner-026">res/movies/spinner-026.png</file>
- <file alias="spinner-027">res/movies/spinner-027.png</file>
- <file alias="spinner-028">res/movies/spinner-028.png</file>
- <file alias="spinner-029">res/movies/spinner-029.png</file>
- <file alias="spinner-030">res/movies/spinner-030.png</file>
- <file alias="spinner-031">res/movies/spinner-031.png</file>
- <file alias="spinner-032">res/movies/spinner-032.png</file>
- <file alias="spinner-033">res/movies/spinner-033.png</file>
- <file alias="spinner-034">res/movies/spinner-034.png</file>
- <file alias="spinner-035">res/movies/spinner-035.png</file>
+ <qresource prefix="/animation">
+ <file alias="spinner-000">res/animation/spinner-000.png</file>
+ <file alias="spinner-001">res/animation/spinner-001.png</file>
+ <file alias="spinner-002">res/animation/spinner-002.png</file>
+ <file alias="spinner-003">res/animation/spinner-003.png</file>
+ <file alias="spinner-004">res/animation/spinner-004.png</file>
+ <file alias="spinner-005">res/animation/spinner-005.png</file>
+ <file alias="spinner-006">res/animation/spinner-006.png</file>
+ <file alias="spinner-007">res/animation/spinner-007.png</file>
+ <file alias="spinner-008">res/animation/spinner-008.png</file>
+ <file alias="spinner-009">res/animation/spinner-009.png</file>
+ <file alias="spinner-010">res/animation/spinner-010.png</file>
+ <file alias="spinner-011">res/animation/spinner-011.png</file>
+ <file alias="spinner-012">res/animation/spinner-012.png</file>
+ <file alias="spinner-013">res/animation/spinner-013.png</file>
+ <file alias="spinner-014">res/animation/spinner-014.png</file>
+ <file alias="spinner-015">res/animation/spinner-015.png</file>
+ <file alias="spinner-016">res/animation/spinner-016.png</file>
+ <file alias="spinner-017">res/animation/spinner-017.png</file>
+ <file alias="spinner-018">res/animation/spinner-018.png</file>
+ <file alias="spinner-019">res/animation/spinner-019.png</file>
+ <file alias="spinner-020">res/animation/spinner-020.png</file>
+ <file alias="spinner-021">res/animation/spinner-021.png</file>
+ <file alias="spinner-022">res/animation/spinner-022.png</file>
+ <file alias="spinner-023">res/animation/spinner-023.png</file>
+ <file alias="spinner-024">res/animation/spinner-024.png</file>
+ <file alias="spinner-025">res/animation/spinner-025.png</file>
+ <file alias="spinner-026">res/animation/spinner-026.png</file>
+ <file alias="spinner-027">res/animation/spinner-027.png</file>
+ <file alias="spinner-028">res/animation/spinner-028.png</file>
+ <file alias="spinner-029">res/animation/spinner-029.png</file>
+ <file alias="spinner-030">res/animation/spinner-030.png</file>
+ <file alias="spinner-031">res/animation/spinner-031.png</file>
+ <file alias="spinner-032">res/animation/spinner-032.png</file>
+ <file alias="spinner-033">res/animation/spinner-033.png</file>
+ <file alias="spinner-034">res/animation/spinner-034.png</file>
+ <file alias="spinner-035">res/animation/spinner-035.png</file>
</qresource>
</RCC>
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 65f226a925..ebcc04a5eb 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -112,6 +112,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
Q_EMIT consoleShown(rpcConsole);
}
+ modalOverlay = new ModalOverlay(enableWallet, this->centralWidget());
+
// Accept D&D of URIs
setAcceptDrops(true);
@@ -201,7 +203,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK);
});
- modalOverlay = new ModalOverlay(enableWallet, this->centralWidget());
connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay);
connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay);
#ifdef ENABLE_WALLET
@@ -238,6 +239,7 @@ BitcoinGUI::~BitcoinGUI()
void BitcoinGUI::createActions()
{
QActionGroup *tabGroup = new QActionGroup(this);
+ connect(modalOverlay, &ModalOverlay::triggered, tabGroup, &QActionGroup::setEnabled);
overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this);
overviewAction->setStatusTip(tr("Show general overview of wallet"));
@@ -1038,7 +1040,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
if(count != prevBlocks)
{
labelBlocksIcon->setPixmap(platformStyle->SingleColorIcon(QString(
- ":/movies/spinner-%1").arg(spinnerFrame, 3, 10, QChar('0')))
+ ":/animation/spinner-%1").arg(spinnerFrame, 3, 10, QChar('0')))
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES;
}
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 1217ca3e2e..93840b4169 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -1082,12 +1082,12 @@
<item row="0" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
- <string>Whitelisted</string>
+ <string>Permissions</string>
</property>
</widget>
</item>
<item row="0" column="1">
- <widget class="QLabel" name="peerWhitelisted">
+ <widget class="QLabel" name="peerPermissions">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
@@ -1264,36 +1264,13 @@
</widget>
</item>
<item row="8" column="0">
- <widget class="QLabel" name="label_24">
- <property name="text">
- <string>Ban Score</string>
- </property>
- </widget>
- </item>
- <item row="8" column="1">
- <widget class="QLabel" name="peerBanScore">
- <property name="cursor">
- <cursorShape>IBeamCursor</cursorShape>
- </property>
- <property name="text">
- <string>N/A</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
- </property>
- </widget>
- </item>
- <item row="9" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Connection Time</string>
</property>
</widget>
</item>
- <item row="9" column="1">
+ <item row="8" column="1">
<widget class="QLabel" name="peerConnTime">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1309,14 +1286,14 @@
</property>
</widget>
</item>
- <item row="10" column="0">
+ <item row="9" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Last Send</string>
</property>
</widget>
</item>
- <item row="10" column="1">
+ <item row="9" column="1">
<widget class="QLabel" name="peerLastSend">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1332,14 +1309,14 @@
</property>
</widget>
</item>
- <item row="11" column="0">
+ <item row="10" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Last Receive</string>
</property>
</widget>
</item>
- <item row="11" column="1">
+ <item row="10" column="1">
<widget class="QLabel" name="peerLastRecv">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1355,14 +1332,14 @@
</property>
</widget>
</item>
- <item row="12" column="0">
+ <item row="11" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Sent</string>
</property>
</widget>
</item>
- <item row="12" column="1">
+ <item row="11" column="1">
<widget class="QLabel" name="peerBytesSent">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1378,14 +1355,14 @@
</property>
</widget>
</item>
- <item row="13" column="0">
+ <item row="12" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Received</string>
</property>
</widget>
</item>
- <item row="13" column="1">
+ <item row="12" column="1">
<widget class="QLabel" name="peerBytesRecv">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1401,14 +1378,14 @@
</property>
</widget>
</item>
- <item row="14" column="0">
+ <item row="13" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Ping Time</string>
</property>
</widget>
</item>
- <item row="14" column="1">
+ <item row="13" column="1">
<widget class="QLabel" name="peerPingTime">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1424,7 +1401,7 @@
</property>
</widget>
</item>
- <item row="15" column="0">
+ <item row="14" column="0">
<widget class="QLabel" name="peerPingWaitLabel">
<property name="toolTip">
<string>The duration of a currently outstanding ping.</string>
@@ -1434,7 +1411,7 @@
</property>
</widget>
</item>
- <item row="15" column="1">
+ <item row="14" column="1">
<widget class="QLabel" name="peerPingWait">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1450,14 +1427,14 @@
</property>
</widget>
</item>
- <item row="16" column="0">
+ <item row="15" column="0">
<widget class="QLabel" name="peerMinPingLabel">
<property name="text">
<string>Min Ping</string>
</property>
</widget>
</item>
- <item row="16" column="1">
+ <item row="15" column="1">
<widget class="QLabel" name="peerMinPing">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1473,14 +1450,14 @@
</property>
</widget>
</item>
- <item row="17" column="0">
+ <item row="16" column="0">
<widget class="QLabel" name="label_timeoffset">
<property name="text">
<string>Time Offset</string>
</property>
</widget>
</item>
- <item row="17" column="1">
+ <item row="16" column="1">
<widget class="QLabel" name="timeoffset">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1496,7 +1473,7 @@
</property>
</widget>
</item>
- <item row="18" column="0">
+ <item row="17" column="0">
<widget class="QLabel" name="peerMappedASLabel">
<property name="toolTip">
<string>The mapped Autonomous System used for diversifying peer selection.</string>
@@ -1506,7 +1483,7 @@
</property>
</widget>
</item>
- <item row="18" column="1">
+ <item row="17" column="1">
<widget class="QLabel" name="peerMappedAS">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
@@ -1522,7 +1499,7 @@
</property>
</widget>
</item>
- <item row="19" column="0">
+ <item row="18" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 3cadac2f2f..7f439fa45e 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -450,6 +450,28 @@ bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt)
return QObject::eventFilter(obj, evt);
}
+LabelOutOfFocusEventFilter::LabelOutOfFocusEventFilter(QObject* parent)
+ : QObject(parent)
+{
+}
+
+bool LabelOutOfFocusEventFilter::eventFilter(QObject* watched, QEvent* event)
+{
+ if (event->type() == QEvent::FocusOut) {
+ auto focus_out = static_cast<QFocusEvent*>(event);
+ if (focus_out->reason() != Qt::PopupFocusReason) {
+ auto label = qobject_cast<QLabel*>(watched);
+ if (label) {
+ auto flags = label->textInteractionFlags();
+ label->setTextInteractionFlags(Qt::NoTextInteraction);
+ label->setTextInteractionFlags(flags);
+ }
+ }
+ }
+
+ return QObject::eventFilter(watched, event);
+}
+
void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
{
connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized);
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index 8741d90102..2bd94b5eb3 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -162,6 +162,21 @@ namespace GUIUtil
};
/**
+ * Qt event filter that intercepts QEvent::FocusOut events for QLabel objects, and
+ * resets their `textInteractionFlags' property to get rid of the visible cursor.
+ *
+ * This is a temporary fix of QTBUG-59514.
+ */
+ class LabelOutOfFocusEventFilter : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ explicit LabelOutOfFocusEventFilter(QObject* parent);
+ bool eventFilter(QObject* watched, QEvent* event) override;
+ };
+
+ /**
* Makes a QTableView last column feel as if it was being resized from its left border.
* Also makes sure the column widths are never larger than the table's viewport.
* In Qt, all columns are resizable from the right, but it's not intuitive resizing the last column from the right.
diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp
index 0ba1beaf3e..8070aa627c 100644
--- a/src/qt/modaloverlay.cpp
+++ b/src/qt/modaloverlay.cpp
@@ -171,6 +171,8 @@ void ModalOverlay::showHide(bool hide, bool userRequested)
if ( (layerIsVisible && !hide) || (!layerIsVisible && hide) || (!hide && userClosed && !userRequested))
return;
+ Q_EMIT triggered(hide);
+
if (!isVisible() && !hide)
setVisible(true);
diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h
index 1d84046d3d..7b07777641 100644
--- a/src/qt/modaloverlay.h
+++ b/src/qt/modaloverlay.h
@@ -25,16 +25,20 @@ public:
explicit ModalOverlay(bool enable_wallet, QWidget *parent);
~ModalOverlay();
-public Q_SLOTS:
void tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress);
void setKnownBestHeight(int count, const QDateTime& blockDate);
- void toggleVisibility();
// will show or hide the modal layer
void showHide(bool hide = false, bool userRequested = false);
- void closeClicked();
bool isLayerVisible() const { return layerIsVisible; }
+public Q_SLOTS:
+ void toggleVisibility();
+ void closeClicked();
+
+Q_SIGNALS:
+ void triggered(bool hidden);
+
protected:
bool eventFilter(QObject * obj, QEvent * ev) override;
bool event(QEvent* ev) override;
diff --git a/src/qt/res/movies/makespinner.sh b/src/qt/res/animation/makespinner.sh
index 4fa8dadf86..4fa8dadf86 100755
--- a/src/qt/res/movies/makespinner.sh
+++ b/src/qt/res/animation/makespinner.sh
diff --git a/src/qt/res/movies/spinner-000.png b/src/qt/res/animation/spinner-000.png
index 0dc48d0d8c..0dc48d0d8c 100644
--- a/src/qt/res/movies/spinner-000.png
+++ b/src/qt/res/animation/spinner-000.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-001.png b/src/qt/res/animation/spinner-001.png
index d167f20541..d167f20541 100644
--- a/src/qt/res/movies/spinner-001.png
+++ b/src/qt/res/animation/spinner-001.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-002.png b/src/qt/res/animation/spinner-002.png
index 4a1f1f8e56..4a1f1f8e56 100644
--- a/src/qt/res/movies/spinner-002.png
+++ b/src/qt/res/animation/spinner-002.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-003.png b/src/qt/res/animation/spinner-003.png
index fb1c2cd4ad..fb1c2cd4ad 100644
--- a/src/qt/res/movies/spinner-003.png
+++ b/src/qt/res/animation/spinner-003.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-004.png b/src/qt/res/animation/spinner-004.png
index 4df2132344..4df2132344 100644
--- a/src/qt/res/movies/spinner-004.png
+++ b/src/qt/res/animation/spinner-004.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-005.png b/src/qt/res/animation/spinner-005.png
index 5d6f41e0dc..5d6f41e0dc 100644
--- a/src/qt/res/movies/spinner-005.png
+++ b/src/qt/res/animation/spinner-005.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-006.png b/src/qt/res/animation/spinner-006.png
index c1f7d18899..c1f7d18899 100644
--- a/src/qt/res/movies/spinner-006.png
+++ b/src/qt/res/animation/spinner-006.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-007.png b/src/qt/res/animation/spinner-007.png
index 1e794b2626..1e794b2626 100644
--- a/src/qt/res/movies/spinner-007.png
+++ b/src/qt/res/animation/spinner-007.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-008.png b/src/qt/res/animation/spinner-008.png
index df12ea8719..df12ea8719 100644
--- a/src/qt/res/movies/spinner-008.png
+++ b/src/qt/res/animation/spinner-008.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-009.png b/src/qt/res/animation/spinner-009.png
index 18fc3a7d16..18fc3a7d16 100644
--- a/src/qt/res/movies/spinner-009.png
+++ b/src/qt/res/animation/spinner-009.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-010.png b/src/qt/res/animation/spinner-010.png
index a79c845fe8..a79c845fe8 100644
--- a/src/qt/res/movies/spinner-010.png
+++ b/src/qt/res/animation/spinner-010.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-011.png b/src/qt/res/animation/spinner-011.png
index 57baf66895..57baf66895 100644
--- a/src/qt/res/movies/spinner-011.png
+++ b/src/qt/res/animation/spinner-011.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-012.png b/src/qt/res/animation/spinner-012.png
index 9deae7853a..9deae7853a 100644
--- a/src/qt/res/movies/spinner-012.png
+++ b/src/qt/res/animation/spinner-012.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-013.png b/src/qt/res/animation/spinner-013.png
index 0659d48dec..0659d48dec 100644
--- a/src/qt/res/movies/spinner-013.png
+++ b/src/qt/res/animation/spinner-013.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-014.png b/src/qt/res/animation/spinner-014.png
index bc1ef51bde..bc1ef51bde 100644
--- a/src/qt/res/movies/spinner-014.png
+++ b/src/qt/res/animation/spinner-014.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-015.png b/src/qt/res/animation/spinner-015.png
index 24b57b62c2..24b57b62c2 100644
--- a/src/qt/res/movies/spinner-015.png
+++ b/src/qt/res/animation/spinner-015.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-016.png b/src/qt/res/animation/spinner-016.png
index d622872651..d622872651 100644
--- a/src/qt/res/movies/spinner-016.png
+++ b/src/qt/res/animation/spinner-016.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-017.png b/src/qt/res/animation/spinner-017.png
index f48f688db2..f48f688db2 100644
--- a/src/qt/res/movies/spinner-017.png
+++ b/src/qt/res/animation/spinner-017.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-018.png b/src/qt/res/animation/spinner-018.png
index a2c8f38b1d..a2c8f38b1d 100644
--- a/src/qt/res/movies/spinner-018.png
+++ b/src/qt/res/animation/spinner-018.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-019.png b/src/qt/res/animation/spinner-019.png
index 9d7cc35d82..9d7cc35d82 100644
--- a/src/qt/res/movies/spinner-019.png
+++ b/src/qt/res/animation/spinner-019.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-020.png b/src/qt/res/animation/spinner-020.png
index 1a07acc454..1a07acc454 100644
--- a/src/qt/res/movies/spinner-020.png
+++ b/src/qt/res/animation/spinner-020.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-021.png b/src/qt/res/animation/spinner-021.png
index 9cea8f2543..9cea8f2543 100644
--- a/src/qt/res/movies/spinner-021.png
+++ b/src/qt/res/animation/spinner-021.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-022.png b/src/qt/res/animation/spinner-022.png
index 60250f6dea..60250f6dea 100644
--- a/src/qt/res/movies/spinner-022.png
+++ b/src/qt/res/animation/spinner-022.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-023.png b/src/qt/res/animation/spinner-023.png
index fc290a0cf2..fc290a0cf2 100644
--- a/src/qt/res/movies/spinner-023.png
+++ b/src/qt/res/animation/spinner-023.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-024.png b/src/qt/res/animation/spinner-024.png
index c5dcf1eae9..c5dcf1eae9 100644
--- a/src/qt/res/movies/spinner-024.png
+++ b/src/qt/res/animation/spinner-024.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-025.png b/src/qt/res/animation/spinner-025.png
index 7f3577a4de..7f3577a4de 100644
--- a/src/qt/res/movies/spinner-025.png
+++ b/src/qt/res/animation/spinner-025.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-026.png b/src/qt/res/animation/spinner-026.png
index 1663ddf44c..1663ddf44c 100644
--- a/src/qt/res/movies/spinner-026.png
+++ b/src/qt/res/animation/spinner-026.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-027.png b/src/qt/res/animation/spinner-027.png
index d0e6da4503..d0e6da4503 100644
--- a/src/qt/res/movies/spinner-027.png
+++ b/src/qt/res/animation/spinner-027.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-028.png b/src/qt/res/animation/spinner-028.png
index 2a7aba50e2..2a7aba50e2 100644
--- a/src/qt/res/movies/spinner-028.png
+++ b/src/qt/res/animation/spinner-028.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-029.png b/src/qt/res/animation/spinner-029.png
index c8ca15c1e1..c8ca15c1e1 100644
--- a/src/qt/res/movies/spinner-029.png
+++ b/src/qt/res/animation/spinner-029.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-030.png b/src/qt/res/animation/spinner-030.png
index c847c99a93..c847c99a93 100644
--- a/src/qt/res/movies/spinner-030.png
+++ b/src/qt/res/animation/spinner-030.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-031.png b/src/qt/res/animation/spinner-031.png
index 403443144e..403443144e 100644
--- a/src/qt/res/movies/spinner-031.png
+++ b/src/qt/res/animation/spinner-031.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-032.png b/src/qt/res/animation/spinner-032.png
index f9db080567..f9db080567 100644
--- a/src/qt/res/movies/spinner-032.png
+++ b/src/qt/res/animation/spinner-032.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-033.png b/src/qt/res/animation/spinner-033.png
index 43f57719e7..43f57719e7 100644
--- a/src/qt/res/movies/spinner-033.png
+++ b/src/qt/res/animation/spinner-033.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-034.png b/src/qt/res/animation/spinner-034.png
index c26656ff17..c26656ff17 100644
--- a/src/qt/res/movies/spinner-034.png
+++ b/src/qt/res/animation/spinner-034.png
Binary files differ
diff --git a/src/qt/res/movies/spinner-035.png b/src/qt/res/animation/spinner-035.png
index e471f950a3..e471f950a3 100644
--- a/src/qt/res/movies/spinner-035.png
+++ b/src/qt/res/animation/spinner-035.png
Binary files differ
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 71094f7112..821a337a62 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -24,7 +24,6 @@
#include <univalue.h>
#ifdef ENABLE_WALLET
-#include <wallet/bdb.h>
#include <wallet/db.h>
#include <wallet/wallet.h>
#endif
@@ -1120,15 +1119,20 @@ void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats)
ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound"));
ui->peerHeight->setText(QString::number(stats->nodeStats.nStartingHeight));
- ui->peerWhitelisted->setText(stats->nodeStats.m_legacyWhitelisted ? tr("Yes") : tr("No"));
+ if (stats->nodeStats.m_permissionFlags == PF_NONE) {
+ ui->peerPermissions->setText(tr("N/A"));
+ } else {
+ QStringList permissions;
+ for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permissionFlags)) {
+ permissions.append(QString::fromStdString(permission));
+ }
+ ui->peerPermissions->setText(permissions.join(" & "));
+ }
ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : tr("N/A"));
// This check fails for example if the lock was busy and
// nodeStateStats couldn't be fetched.
if (stats->fNodeStateStatsAvailable) {
- // Ban score is init to 0
- ui->peerBanScore->setText(QString("%1").arg(stats->nodeStateStats.nMisbehavior));
-
// Sync height is init to -1
if (stats->nodeStateStats.nSyncHeight > -1)
ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight));
diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp
index f88d57c716..443e2d047d 100644
--- a/src/qt/test/apptests.cpp
+++ b/src/qt/test/apptests.cpp
@@ -62,9 +62,10 @@ void AppTests::appTests()
}
#endif
- BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to
- ECC_Stop(); // Already started by the common test setup, so stop it to avoid interference
- LogInstance().DisconnectTestLogger();
+ fs::create_directories([] {
+ BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to
+ return GetDataDir() / "blocks";
+ }());
m_app.parameterSetup();
m_app.createOptionsModel(true /* reset settings */);
@@ -80,6 +81,7 @@ void AppTests::appTests()
m_app.exec();
// Reset global state to avoid interfering with later tests.
+ LogInstance().DisconnectTestLogger();
AbortShutdown();
UnloadBlockIndex();
WITH_LOCK(::cs_main, g_chainman.Reset());
diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp
index aefdcd2716..12efca2503 100644
--- a/src/qt/test/test_main.cpp
+++ b/src/qt/test/test_main.cpp
@@ -40,7 +40,7 @@ Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin);
const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
// This is all you need to run all the tests
-int main(int argc, char *argv[])
+int main(int argc, char* argv[])
{
// Initialize persistent globals with the testing setup state for sanity.
// E.g. -datadir in gArgs is set to a temp directory dummy value (instead
@@ -70,6 +70,8 @@ int main(int argc, char *argv[])
BitcoinApplication app(*node);
app.setApplicationName("Bitcoin-Qt-test");
+ node->setupServerArgs(); // Make gArgs available in the NodeContext
+ node->context()->args->ClearArgs(); // Clear added args again
AppTests app_tests(app);
if (QTest::qExec(&app_tests) != 0) {
fInvalid = true;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 98d4b2ce81..2afc9a3d4a 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -798,10 +798,8 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex)
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
// Block not found on disk. This could be because we have the block
- // header in our index but don't have the block (for example if a
- // non-whitelisted node sends us an unrequested long chain of valid
- // blocks, we add the headers to our index, but don't accept the
- // block).
+ // header in our index but not yet have the block or did not accept the
+ // block.
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}
@@ -1004,7 +1002,8 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED);
CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB());
- if (GetUTXOStats(coins_view, stats, hash_type, RpcInterruptionPoint)) {
+ NodeContext& node = EnsureNodeContext(request.context);
+ if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) {
ret.pushKV("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("transactions", (int64_t)stats.nTransactions);
@@ -1974,8 +1973,10 @@ static UniValue savemempool(const JSONRPCRequest& request)
return NullUniValue;
}
+namespace {
//! Search for a given set of pubkey scripts
-bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
+bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
+{
scan_progress = 0;
count = 0;
while (cursor->Valid()) {
@@ -1983,7 +1984,7 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
Coin coin;
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
if (++count % 8192 == 0) {
- RpcInterruptionPoint();
+ interruption_point();
if (should_abort) {
// allow to abort the scan via the abort reference
return false;
@@ -2002,6 +2003,7 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
scan_progress = 100;
return true;
}
+} // namespace
/** RAII object to prevent concurrency issue when scanning the txout set */
static std::atomic<int> g_scan_progress;
@@ -2150,7 +2152,8 @@ UniValue scantxoutset(const JSONRPCRequest& request)
tip = ::ChainActive().Tip();
CHECK_NONFATAL(tip);
}
- bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
+ NodeContext& node = EnsureNodeContext(request.context);
+ bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point);
result.pushKV("success", res);
result.pushKV("txouts", count);
result.pushKV("height", tip->nHeight);
@@ -2305,6 +2308,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats;
CBlockIndex* tip;
+ NodeContext& node = EnsureNodeContext(request.context);
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
@@ -2323,7 +2327,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
::ChainstateActive().ForceFlushStateToDisk();
- if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, RpcInterruptionPoint)) {
+ if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
@@ -2341,7 +2345,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
unsigned int iter{0};
while (pcursor->Valid()) {
- if (iter % 5000 == 0) RpcInterruptionPoint();
+ if (iter % 5000 == 0) node.rpc_interruption_point();
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
afile << key;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 5f8c02df65..d5e902cadd 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -28,6 +28,7 @@
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
+#include <util/bip32.h>
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
@@ -938,25 +939,6 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
return result;
}
-static std::string WriteHDKeypath(std::vector<uint32_t>& keypath)
-{
- std::string keypath_str = "m";
- for (uint32_t num : keypath) {
- keypath_str += "/";
- bool hardened = false;
- if (num & 0x80000000) {
- hardened = true;
- num &= ~0x80000000;
- }
-
- keypath_str += ToString(num);
- if (hardened) {
- keypath_str += "'";
- }
- }
- return keypath_str;
-}
-
UniValue decodepsbt(const JSONRPCRequest& request)
{
RPCHelpMan{"decodepsbt",
@@ -1105,39 +1087,38 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue in(UniValue::VOBJ);
// UTXOs
bool have_a_utxo = false;
+ CTxOut txout;
if (!input.witness_utxo.IsNull()) {
- const CTxOut& txout = input.witness_utxo;
-
- UniValue out(UniValue::VOBJ);
-
- out.pushKV("amount", ValueFromAmount(txout.nValue));
- if (MoneyRange(txout.nValue) && MoneyRange(total_in + txout.nValue)) {
- total_in += txout.nValue;
- } else {
- // Hack to just not show fee later
- have_all_utxos = false;
- }
+ txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true);
+
+ UniValue out(UniValue::VOBJ);
+ out.pushKV("amount", ValueFromAmount(txout.nValue));
out.pushKV("scriptPubKey", o);
+
in.pushKV("witness_utxo", out);
+
have_a_utxo = true;
}
if (input.non_witness_utxo) {
+ txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
+
UniValue non_wit(UniValue::VOBJ);
TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
in.pushKV("non_witness_utxo", non_wit);
- CAmount utxo_val = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue;
- if (MoneyRange(utxo_val) && MoneyRange(total_in + utxo_val)) {
- total_in += utxo_val;
+
+ have_a_utxo = true;
+ }
+ if (have_a_utxo) {
+ if (MoneyRange(txout.nValue) && MoneyRange(total_in + txout.nValue)) {
+ total_in += txout.nValue;
} else {
// Hack to just not show fee later
have_all_utxos = false;
}
- have_a_utxo = true;
- }
- if (!have_a_utxo) {
+ } else {
have_all_utxos = false;
}
@@ -1633,7 +1614,7 @@ UniValue joinpsbts(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "At least two PSBTs are required to join PSBTs.");
}
- int32_t best_version = 1;
+ uint32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (unsigned int i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
@@ -1643,8 +1624,8 @@ UniValue joinpsbts(const JSONRPCRequest& request)
}
psbtxs.push_back(psbtx);
// Choose the highest version number
- if (psbtx.tx->nVersion > best_version) {
- best_version = psbtx.tx->nVersion;
+ if (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) {
+ best_version = static_cast<uint32_t>(psbtx.tx->nVersion);
}
// Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) {
@@ -1655,7 +1636,7 @@ UniValue joinpsbts(const JSONRPCRequest& request)
// Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
- merged_psbt.tx->nVersion = best_version;
+ merged_psbt.tx->nVersion = static_cast<int32_t>(best_version);
merged_psbt.tx->nLockTime = best_locktime;
// Merge
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index de8791a935..e5f6b1b9f1 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -130,11 +130,9 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
return strRet;
}
-UniValue help(const JSONRPCRequest& jsonRequest)
+static RPCHelpMan help()
{
- if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
- throw std::runtime_error(
- RPCHelpMan{"help",
+ return RPCHelpMan{"help",
"\nList all commands, or get help for a specified command.\n",
{
{"command", RPCArg::Type::STR, /* default */ "all commands", "The command to get help on"},
@@ -143,32 +141,32 @@ UniValue help(const JSONRPCRequest& jsonRequest)
RPCResult::Type::STR, "", "The help text"
},
RPCExamples{""},
- }.ToString()
- );
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
+{
std::string strCommand;
if (jsonRequest.params.size() > 0)
strCommand = jsonRequest.params[0].get_str();
return tableRPC.help(strCommand, jsonRequest);
+},
+ };
}
-
-UniValue stop(const JSONRPCRequest& jsonRequest)
+static RPCHelpMan stop()
{
static const std::string RESULT{PACKAGE_NAME " stopping"};
- // Accept the deprecated and ignored 'detach' boolean argument
+ return RPCHelpMan{"stop",
// Also accept the hidden 'wait' integer argument (milliseconds)
// For instance, 'stop 1000' makes the call wait 1 second before returning
// to the client (intended for testing)
- if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
- throw std::runtime_error(
- RPCHelpMan{"stop",
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
- {},
+ {
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
+ },
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
RPCExamples{""},
- }.ToString());
+ [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
+{
// Event loop will exit after current HTTP requests have been handled, so
// this reply will get back to the client.
StartShutdown();
@@ -176,11 +174,13 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()});
}
return RESULT;
+},
+ };
}
-static UniValue uptime(const JSONRPCRequest& jsonRequest)
+static RPCHelpMan uptime()
{
- RPCHelpMan{"uptime",
+ return RPCHelpMan{"uptime",
"\nReturns the total uptime of the server.\n",
{},
RPCResult{
@@ -190,14 +190,16 @@ static UniValue uptime(const JSONRPCRequest& jsonRequest)
HelpExampleCli("uptime", "")
+ HelpExampleRpc("uptime", "")
},
- }.Check(jsonRequest);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
return GetTime() - GetStartupTime();
}
+ };
+}
-static UniValue getrpcinfo(const JSONRPCRequest& request)
+static RPCHelpMan getrpcinfo()
{
- RPCHelpMan{"getrpcinfo",
+ return RPCHelpMan{"getrpcinfo",
"\nReturns details of the RPC server.\n",
{},
RPCResult{
@@ -217,8 +219,8 @@ static UniValue getrpcinfo(const JSONRPCRequest& request)
RPCExamples{
HelpExampleCli("getrpcinfo", "")
+ HelpExampleRpc("getrpcinfo", "")},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
LOCK(g_rpc_server_info.mutex);
UniValue active_commands(UniValue::VARR);
for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
@@ -237,6 +239,8 @@ static UniValue getrpcinfo(const JSONRPCRequest& request)
return result;
}
+ };
+}
// clang-format off
static const CRPCCommand vRPCCommands[] =
diff --git a/src/rpc/server.h b/src/rpc/server.h
index d7a04ff6e8..6da3e94ea2 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -8,6 +8,7 @@
#include <amount.h>
#include <rpc/request.h>
+#include <rpc/util.h>
#include <functional>
#include <map>
@@ -85,6 +86,7 @@ void RPCUnsetTimerInterface(RPCTimerInterface *iface);
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds);
typedef UniValue(*rpcfn_type)(const JSONRPCRequest& jsonRequest);
+typedef RPCHelpMan (*RpcMethodFnType)();
class CRPCCommand
{
@@ -101,6 +103,19 @@ public:
{
}
+ //! Simplified constructor taking plain RpcMethodFnType function pointer.
+ CRPCCommand(std::string category, std::string name_in, RpcMethodFnType fn, std::vector<std::string> args_in)
+ : CRPCCommand(
+ category,
+ fn().m_name,
+ [fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn().HandleRequest(request); return true; },
+ fn().GetArgNames(),
+ intptr_t(fn))
+ {
+ CHECK_NONFATAL(fn().m_name == name_in);
+ CHECK_NONFATAL(fn().GetArgNames() == args_in);
+ }
+
//! Simplified constructor taking plain rpcfn_type function pointer.
CRPCCommand(const char* category, const char* name, rpcfn_type fn, std::initializer_list<const char*> args)
: CRPCCommand(category, name,
@@ -117,7 +132,7 @@ public:
};
/**
- * Bitcoin RPC command dispatcher.
+ * RPC command dispatcher.
*/
class CRPCTable
{
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index ca73c699c9..9f4c7bee9c 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -385,9 +385,7 @@ struct Sections {
PushSection({indent + "]" + (outer_type != OuterType::NONE ? "," : ""), ""});
break;
}
-
- // no default case, so the compiler can warn about missing cases
- }
+ } // no default case, so the compiler can warn about missing cases
}
/**
@@ -398,6 +396,9 @@ struct Sections {
std::string ret;
const size_t pad = m_max_pad + 4;
for (const auto& s : m_sections) {
+ // The left part of a section is assumed to be a single line, usually it is the name of the JSON struct or a
+ // brace like {, }, [, or ]
+ CHECK_NONFATAL(s.m_left.find('\n') == std::string::npos);
if (s.m_right.empty()) {
ret += s.m_left;
ret += "\n";
@@ -432,7 +433,11 @@ struct Sections {
};
RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples)
+ : RPCHelpMan{std::move(name), std::move(description), std::move(args), std::move(results), std::move(examples), nullptr} {}
+
+RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun)
: m_name{std::move(name)},
+ m_fun{std::move(fun)},
m_description{std::move(description)},
m_args{std::move(args)},
m_results{std::move(results)},
@@ -481,6 +486,16 @@ bool RPCHelpMan::IsValidNumArgs(size_t num_args) const
}
return num_required_args <= num_args && num_args <= m_args.size();
}
+
+std::vector<std::string> RPCHelpMan::GetArgNames() const
+{
+ std::vector<std::string> ret;
+ for (const auto& arg : m_args) {
+ ret.emplace_back(arg.m_names);
+ }
+ return ret;
+}
+
std::string RPCHelpMan::ToString() const
{
std::string ret;
@@ -489,6 +504,7 @@ std::string RPCHelpMan::ToString() const
ret += m_name;
bool was_optional{false};
for (const auto& arg : m_args) {
+ if (arg.m_hidden) continue;
const bool optional = arg.IsOptional();
ret += " ";
if (optional) {
@@ -510,6 +526,7 @@ std::string RPCHelpMan::ToString() const
Sections sections;
for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg = m_args.at(i);
+ if (arg.m_hidden) continue;
if (i == 0) ret += "\nArguments:\n";
@@ -589,9 +606,7 @@ std::string RPCArg::ToDescriptionString() const
ret += "json array";
break;
}
-
- // no default case, so the compiler can warn about missing cases
- }
+ } // no default case, so the compiler can warn about missing cases
}
if (m_fallback.which() == 1) {
ret += ", optional, default=" + boost::get<std::string>(m_fallback);
@@ -609,9 +624,7 @@ std::string RPCArg::ToDescriptionString() const
ret += ", required";
break;
}
-
- // no default case, so the compiler can warn about missing cases
- }
+ } // no default case, so the compiler can warn about missing cases
}
ret += ")";
ret += m_description.empty() ? "" : " " + m_description;
@@ -706,10 +719,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
sections.PushSection({indent + "}" + maybe_separator, ""});
return;
}
-
- // no default case, so the compiler can warn about missing cases
- }
-
+ } // no default case, so the compiler can warn about missing cases
CHECK_NONFATAL(false);
}
@@ -746,9 +756,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const
case Type::OBJ_USER_KEYS:
// Currently unused, so avoid writing dead code
CHECK_NONFATAL(false);
-
- // no default case, so the compiler can warn about missing cases
- }
+ } // no default case, so the compiler can warn about missing cases
CHECK_NONFATAL(false);
}
@@ -783,9 +791,7 @@ std::string RPCArg::ToString(const bool oneline) const
}
return "[" + res + "...]";
}
-
- // no default case, so the compiler can warn about missing cases
- }
+ } // no default case, so the compiler can warn about missing cases
CHECK_NONFATAL(false);
}
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 96dd1ea74a..45b0bb0c7e 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -147,6 +147,7 @@ struct RPCArg {
using Fallback = boost::variant<Optional, /* default value for optional args */ std::string>;
const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments)
const Type m_type;
+ const bool m_hidden;
const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts
const Fallback m_fallback;
const std::string m_description;
@@ -159,9 +160,11 @@ struct RPCArg {
const Fallback fallback,
const std::string description,
const std::string oneline_description = "",
- const std::vector<std::string> type_str = {})
+ const std::vector<std::string> type_str = {},
+ const bool hidden = false)
: m_names{std::move(name)},
m_type{std::move(type)},
+ m_hidden{hidden},
m_fallback{std::move(fallback)},
m_description{std::move(description)},
m_oneline_description{std::move(oneline_description)},
@@ -180,6 +183,7 @@ struct RPCArg {
const std::vector<std::string> type_str = {})
: m_names{std::move(name)},
m_type{std::move(type)},
+ m_hidden{false},
m_inner{std::move(inner)},
m_fallback{std::move(fallback)},
m_description{std::move(description)},
@@ -329,8 +333,15 @@ class RPCHelpMan
{
public:
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples);
+ using RPCMethodImpl = std::function<UniValue(const RPCHelpMan&, const JSONRPCRequest&)>;
+ RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun);
std::string ToString() const;
+ UniValue HandleRequest(const JSONRPCRequest& request)
+ {
+ Check(request);
+ return m_fun(*this, request);
+ }
/** If the supplied number of args is neither too small nor too high */
bool IsValidNumArgs(size_t num_args) const;
/**
@@ -343,8 +354,12 @@ public:
}
}
-private:
+ std::vector<std::string> GetArgNames() const;
+
const std::string m_name;
+
+private:
+ const RPCMethodImpl m_fun;
const std::string m_description;
const std::vector<RPCArg> m_args;
const RPCResults m_results;
diff --git a/src/sync.cpp b/src/sync.cpp
index 9abdedbed4..10f0483189 100644
--- a/src/sync.cpp
+++ b/src/sync.cpp
@@ -60,7 +60,7 @@ struct CLockLocation {
std::string ToString() const
{
return strprintf(
- "%s %s:%s%s (in thread %s)",
+ "'%s' in %s:%s%s (in thread '%s')",
mutexName, sourceFile, sourceLine, (fTry ? " (TRY)" : ""), m_thread_name);
}
@@ -105,7 +105,7 @@ static void potential_deadlock_detected(const LockPair& mismatch, const LockStac
{
LogPrintf("POTENTIAL DEADLOCK DETECTED\n");
LogPrintf("Previous lock order was:\n");
- for (const LockStackItem& i : s2) {
+ for (const LockStackItem& i : s1) {
if (i.first == mismatch.first) {
LogPrintf(" (1)"); /* Continued */
}
@@ -114,21 +114,25 @@ static void potential_deadlock_detected(const LockPair& mismatch, const LockStac
}
LogPrintf(" %s\n", i.second.ToString());
}
+
+ std::string mutex_a, mutex_b;
LogPrintf("Current lock order is:\n");
- for (const LockStackItem& i : s1) {
+ for (const LockStackItem& i : s2) {
if (i.first == mismatch.first) {
LogPrintf(" (1)"); /* Continued */
+ mutex_a = i.second.Name();
}
if (i.first == mismatch.second) {
LogPrintf(" (2)"); /* Continued */
+ mutex_b = i.second.Name();
}
LogPrintf(" %s\n", i.second.ToString());
}
if (g_debug_lockorder_abort) {
- tfm::format(std::cerr, "Assertion failed: detected inconsistent lock order at %s:%i, details in debug log.\n", __FILE__, __LINE__);
+ tfm::format(std::cerr, "Assertion failed: detected inconsistent lock order for %s, details in debug log.\n", s2.back().second.ToString());
abort();
}
- throw std::logic_error("potential deadlock detected");
+ throw std::logic_error(strprintf("potential deadlock detected: %s -> %s -> %s", mutex_b, mutex_a, mutex_b));
}
static void push_lock(void* c, const CLockLocation& locklocation)
diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json
index c01ef307b7..724789bbf9 100644
--- a/src/test/data/script_tests.json
+++ b/src/test/data/script_tests.json
@@ -678,7 +678,7 @@
["0 0x02 0x0000 0", "CHECKMULTISIGVERIFY 1", "", "OK"],
["While not really correctly DER encoded, the empty signature is allowed by"],
-["STRICTENC to provide a compact way to provide a delibrately invalid signature."],
+["STRICTENC to provide a compact way to provide a deliberately invalid signature."],
["0", "0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 CHECKSIG NOT", "STRICTENC", "OK"],
["0 0", "1 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 1 CHECKMULTISIG NOT", "STRICTENC", "OK"],
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 1fe01fae04..b1a635d9da 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -4,10 +4,12 @@
// Unit tests for denial-of-service detection/prevention code
+#include <arith_uint256.h>
#include <banman.h>
#include <chainparams.h>
#include <net.h>
#include <net_processing.h>
+#include <pubkey.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
@@ -98,11 +100,11 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
// Test starts here
{
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
+ LOCK(dummyNode1.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders
}
{
- LOCK2(cs_main, dummyNode1.cs_vSend);
+ LOCK(dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
dummyNode1.vSendMsg.clear();
}
@@ -111,17 +113,17 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
// Wait 21 minutes
SetMockTime(nStartTime+21*60);
{
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
+ LOCK(dummyNode1.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders
}
{
- LOCK2(cs_main, dummyNode1.cs_vSend);
+ LOCK(dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
}
// Wait 3 more minutes
SetMockTime(nStartTime+24*60);
{
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
+ LOCK(dummyNode1.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in disconnect
}
BOOST_CHECK(dummyNode1.fDisconnect == true);
@@ -217,7 +219,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
connman->ClearNodes();
}
-BOOST_AUTO_TEST_CASE(DoS_banning)
+BOOST_AUTO_TEST_CASE(peer_discouragement)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
@@ -232,14 +234,14 @@ BOOST_AUTO_TEST_CASE(DoS_banning)
dummyNode1.fSuccessfullyConnected = true;
{
LOCK(cs_main);
- Misbehaving(dummyNode1.GetId(), 100); // Should get banned
+ Misbehaving(dummyNode1.GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged
}
{
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
+ LOCK(dummyNode1.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
}
BOOST_CHECK(banman->IsDiscouraged(addr1));
- BOOST_CHECK(!banman->IsDiscouraged(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned
+ BOOST_CHECK(!banman->IsDiscouraged(ip(0xa0b0c001|0x0000ff00))); // Different IP, not discouraged
CAddress addr2(ip(0xa0b0c002), NODE_NONE);
CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", true);
@@ -249,76 +251,30 @@ BOOST_AUTO_TEST_CASE(DoS_banning)
dummyNode2.fSuccessfullyConnected = true;
{
LOCK(cs_main);
- Misbehaving(dummyNode2.GetId(), 50);
+ Misbehaving(dummyNode2.GetId(), DISCOURAGEMENT_THRESHOLD - 1);
}
{
- LOCK2(cs_main, dummyNode2.cs_sendProcessing);
+ LOCK(dummyNode2.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
}
- BOOST_CHECK(!banman->IsDiscouraged(addr2)); // 2 not banned yet...
+ BOOST_CHECK(!banman->IsDiscouraged(addr2)); // 2 not discouraged yet...
BOOST_CHECK(banman->IsDiscouraged(addr1)); // ... but 1 still should be
{
LOCK(cs_main);
- Misbehaving(dummyNode2.GetId(), 50);
+ Misbehaving(dummyNode2.GetId(), 1); // 2 reaches discouragement threshold
}
{
- LOCK2(cs_main, dummyNode2.cs_sendProcessing);
+ LOCK(dummyNode2.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
}
- BOOST_CHECK(banman->IsDiscouraged(addr2));
+ BOOST_CHECK(banman->IsDiscouraged(addr1)); // Expect both 1 and 2
+ BOOST_CHECK(banman->IsDiscouraged(addr2)); // to be discouraged now
bool dummy;
peerLogic->FinalizeNode(dummyNode1.GetId(), dummy);
peerLogic->FinalizeNode(dummyNode2.GetId(), dummy);
}
-BOOST_AUTO_TEST_CASE(DoS_banscore)
-{
- auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
- auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
-
- banman->ClearBanned();
- gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number
- CAddress addr1(ip(0xa0b0c001), NODE_NONE);
- CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1, CAddress(), "", true);
- dummyNode1.SetSendVersion(PROTOCOL_VERSION);
- peerLogic->InitializeNode(&dummyNode1);
- dummyNode1.nVersion = 1;
- dummyNode1.fSuccessfullyConnected = true;
- {
- LOCK(cs_main);
- Misbehaving(dummyNode1.GetId(), 100);
- }
- {
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
- }
- BOOST_CHECK(!banman->IsDiscouraged(addr1));
- {
- LOCK(cs_main);
- Misbehaving(dummyNode1.GetId(), 10);
- }
- {
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
- }
- BOOST_CHECK(!banman->IsDiscouraged(addr1));
- {
- LOCK(cs_main);
- Misbehaving(dummyNode1.GetId(), 1);
- }
- {
- LOCK2(cs_main, dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
- }
- BOOST_CHECK(banman->IsDiscouraged(addr1));
- gArgs.ForceSetArg("-banscore", ToString(DEFAULT_BANSCORE_THRESHOLD));
-
- bool dummy;
- peerLogic->FinalizeNode(dummyNode1.GetId(), dummy);
-}
-
BOOST_AUTO_TEST_CASE(DoS_bantime)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
@@ -338,10 +294,10 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
{
LOCK(cs_main);
- Misbehaving(dummyNode.GetId(), 100);
+ Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD);
}
{
- LOCK2(cs_main, dummyNode.cs_sendProcessing);
+ LOCK(dummyNode.cs_sendProcessing);
BOOST_CHECK(peerLogic->SendMessages(&dummyNode));
}
BOOST_CHECK(banman->IsDiscouraged(addr));
@@ -360,10 +316,26 @@ static CTransactionRef RandomOrphan()
return it->second.tx;
}
+static void MakeNewKeyWithFastRandomContext(CKey& key)
+{
+ std::vector<unsigned char> keydata;
+ keydata = g_insecure_rand_ctx.randbytes(32);
+ key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn*/ true);
+ assert(key.IsValid());
+}
+
BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
{
+ // This test had non-deterministic coverage due to
+ // randomly selected seeds.
+ // This seed is chosen so that all branches of the function
+ // ecdsa_signature_parse_der_lax are executed during this test.
+ // Specifically branches that run only when an ECDSA
+ // signature's R and S values have leading zeros.
+ g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33)));
+
CKey key;
- key.MakeNewKey(true);
+ MakeNewKeyWithFastRandomContext(key);
FillableSigningProvider keystore;
BOOST_CHECK(keystore.AddKey(key));
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 5d7065dafb..20132d5782 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -135,7 +135,7 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
// When the descriptor is hardened, evaluate with access to the private keys inside.
const FlatSigningProvider& key_provider = (flags & HARDENED) ? keys_priv : keys_pub;
- // Evaluate the descriptor selected by `t` in poisition `i`.
+ // Evaluate the descriptor selected by `t` in position `i`.
FlatSigningProvider script_provider, script_provider_cached;
std::vector<CScript> spks, spks_cached;
DescriptorCache desc_cache;
diff --git a/src/test/fuzz/addrdb.cpp b/src/test/fuzz/addrdb.cpp
index ad6461650f..16b1cb755a 100644
--- a/src/test/fuzz/addrdb.cpp
+++ b/src/test/fuzz/addrdb.cpp
@@ -17,6 +17,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
{
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:
@@ -32,4 +33,5 @@ void test_one_input(const std::vector<uint8_t>& buffer)
}
return CBanEntry{};
}();
+ (void)ban_entry; // currently unused
}
diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp
new file mode 100644
index 0000000000..7ea0bdd2a7
--- /dev/null
+++ b/src/test/fuzz/autofile.cpp
@@ -0,0 +1,72 @@
+// 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 <optional.h>
+#include <streams.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider);
+ CAutoFile auto_file = fuzzed_auto_file_provider.open();
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) {
+ case 0: {
+ std::array<uint8_t, 4096> arr{};
+ try {
+ auto_file.read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ } catch (const std::ios_base::failure&) {
+ }
+ break;
+ }
+ case 1: {
+ const std::array<uint8_t, 4096> arr{};
+ try {
+ auto_file.write((const char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ } catch (const std::ios_base::failure&) {
+ }
+ break;
+ }
+ case 2: {
+ try {
+ auto_file.ignore(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ } catch (const std::ios_base::failure&) {
+ }
+ break;
+ }
+ case 3: {
+ auto_file.fclose();
+ break;
+ }
+ case 4: {
+ ReadFromStream(fuzzed_data_provider, auto_file);
+ break;
+ }
+ case 5: {
+ WriteToStream(fuzzed_data_provider, auto_file);
+ break;
+ }
+ }
+ }
+ (void)auto_file.Get();
+ (void)auto_file.GetType();
+ (void)auto_file.GetVersion();
+ (void)auto_file.IsNull();
+ if (fuzzed_data_provider.ConsumeBool()) {
+ FILE* f = auto_file.release();
+ if (f != nullptr) {
+ fclose(f);
+ }
+ }
+}
diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp
new file mode 100644
index 0000000000..fc4a1d9261
--- /dev/null
+++ b/src/test/fuzz/banman.cpp
@@ -0,0 +1,88 @@
+// 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 <banman.h>
+#include <fs.h>
+#include <netaddress.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <util/system.h>
+
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <vector>
+
+namespace {
+int64_t ConsumeBanTimeOffset(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ // Avoid signed integer overflow by capping to int32_t max:
+ // banman.cpp:137:73: runtime error: signed integer overflow: 1591700817 + 9223372036854775807 cannot be represented in type 'long'
+ return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(std::numeric_limits<int64_t>::min(), std::numeric_limits<int32_t>::max());
+}
+} // namespace
+
+void initialize()
+{
+ InitializeFuzzingContext();
+}
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ const fs::path banlist_file = GetDataDir() / "fuzzed_banlist.dat";
+ fs::remove(banlist_file);
+ {
+ BanMan ban_man{banlist_file, nullptr, ConsumeBanTimeOffset(fuzzed_data_provider)};
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 11)) {
+ case 0: {
+ ban_man.Ban(ConsumeNetAddr(fuzzed_data_provider),
+ ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool());
+ break;
+ }
+ case 1: {
+ ban_man.Ban(ConsumeSubNet(fuzzed_data_provider),
+ ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool());
+ break;
+ }
+ case 2: {
+ ban_man.ClearBanned();
+ break;
+ }
+ case 4: {
+ ban_man.IsBanned(ConsumeNetAddr(fuzzed_data_provider));
+ break;
+ }
+ case 5: {
+ ban_man.IsBanned(ConsumeSubNet(fuzzed_data_provider));
+ break;
+ }
+ case 6: {
+ ban_man.Unban(ConsumeNetAddr(fuzzed_data_provider));
+ break;
+ }
+ case 7: {
+ ban_man.Unban(ConsumeSubNet(fuzzed_data_provider));
+ break;
+ }
+ case 8: {
+ banmap_t banmap;
+ ban_man.GetBanned(banmap);
+ break;
+ }
+ case 9: {
+ ban_man.DumpBanlist();
+ break;
+ }
+ case 11: {
+ ban_man.Discourage(ConsumeNetAddr(fuzzed_data_provider));
+ break;
+ }
+ }
+ }
+ }
+ fs::remove(banlist_file);
+}
diff --git a/src/test/fuzz/buffered_file.cpp b/src/test/fuzz/buffered_file.cpp
new file mode 100644
index 0000000000..6bbd13eb5c
--- /dev/null
+++ b/src/test/fuzz/buffered_file.cpp
@@ -0,0 +1,78 @@
+// 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 <optional.h>
+#include <streams.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ FuzzedFileProvider fuzzed_file_provider = ConsumeFile(fuzzed_data_provider);
+ std::optional<CBufferedFile> opt_buffered_file;
+ FILE* fuzzed_file = fuzzed_file_provider.open();
+ try {
+ opt_buffered_file.emplace(fuzzed_file, fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096), fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096), fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<int>());
+ } catch (const std::ios_base::failure&) {
+ if (fuzzed_file != nullptr) {
+ fclose(fuzzed_file);
+ }
+ }
+ if (opt_buffered_file && fuzzed_file != nullptr) {
+ bool setpos_fail = false;
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) {
+ case 0: {
+ std::array<uint8_t, 4096> arr{};
+ try {
+ opt_buffered_file->read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ } catch (const std::ios_base::failure&) {
+ }
+ break;
+ }
+ case 1: {
+ opt_buffered_file->Seek(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096));
+ break;
+ }
+ case 2: {
+ opt_buffered_file->SetLimit(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096));
+ break;
+ }
+ case 3: {
+ if (!opt_buffered_file->SetPos(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096))) {
+ setpos_fail = true;
+ }
+ break;
+ }
+ case 4: {
+ if (setpos_fail) {
+ // Calling FindByte(...) after a failed SetPos(...) call may result in an infinite loop.
+ break;
+ }
+ try {
+ opt_buffered_file->FindByte(fuzzed_data_provider.ConsumeIntegral<char>());
+ } catch (const std::ios_base::failure&) {
+ }
+ break;
+ }
+ case 5: {
+ ReadFromStream(fuzzed_data_provider, *opt_buffered_file);
+ break;
+ }
+ }
+ }
+ opt_buffered_file->GetPos();
+ opt_buffered_file->GetType();
+ opt_buffered_file->GetVersion();
+ }
+}
diff --git a/src/test/fuzz/crypto_aes256.cpp b/src/test/fuzz/crypto_aes256.cpp
new file mode 100644
index 0000000000..ae14073c96
--- /dev/null
+++ b/src/test/fuzz/crypto_aes256.cpp
@@ -0,0 +1,30 @@
+// 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 <crypto/aes.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cassert>
+#include <cstdint>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ const std::vector<uint8_t> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, AES256_KEYSIZE);
+
+ AES256Encrypt encrypt{key.data()};
+ AES256Decrypt decrypt{key.data()};
+
+ while (fuzzed_data_provider.ConsumeBool()) {
+ const std::vector<uint8_t> plaintext = ConsumeFixedLengthByteVector(fuzzed_data_provider, AES_BLOCKSIZE);
+ std::vector<uint8_t> ciphertext(AES_BLOCKSIZE);
+ encrypt.Encrypt(ciphertext.data(), plaintext.data());
+ std::vector<uint8_t> decrypted_plaintext(AES_BLOCKSIZE);
+ decrypt.Decrypt(decrypted_plaintext.data(), ciphertext.data());
+ assert(decrypted_plaintext == plaintext);
+ }
+}
diff --git a/src/test/fuzz/crypto_aes256cbc.cpp b/src/test/fuzz/crypto_aes256cbc.cpp
new file mode 100644
index 0000000000..52983c7e79
--- /dev/null
+++ b/src/test/fuzz/crypto_aes256cbc.cpp
@@ -0,0 +1,34 @@
+// 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 <crypto/aes.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cassert>
+#include <cstdint>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ const std::vector<uint8_t> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, AES256_KEYSIZE);
+ const std::vector<uint8_t> iv = ConsumeFixedLengthByteVector(fuzzed_data_provider, AES_BLOCKSIZE);
+ const bool pad = fuzzed_data_provider.ConsumeBool();
+
+ AES256CBCEncrypt encrypt{key.data(), iv.data(), pad};
+ AES256CBCDecrypt decrypt{key.data(), iv.data(), pad};
+
+ while (fuzzed_data_provider.ConsumeBool()) {
+ const std::vector<uint8_t> plaintext = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+ std::vector<uint8_t> ciphertext(plaintext.size() + AES_BLOCKSIZE);
+ const int encrypt_ret = encrypt.Encrypt(plaintext.data(), plaintext.size(), ciphertext.data());
+ ciphertext.resize(encrypt_ret);
+ std::vector<uint8_t> decrypted_plaintext(ciphertext.size());
+ const int decrypt_ret = decrypt.Decrypt(ciphertext.data(), ciphertext.size(), decrypted_plaintext.data());
+ decrypted_plaintext.resize(decrypt_ret);
+ assert(decrypted_plaintext == plaintext || (!pad && plaintext.size() % AES_BLOCKSIZE != 0 && encrypt_ret == 0 && decrypt_ret == 0));
+ }
+}
diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp
new file mode 100644
index 0000000000..b7438d312d
--- /dev/null
+++ b/src/test/fuzz/crypto_chacha20.cpp
@@ -0,0 +1,50 @@
+// 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 <crypto/chacha20.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+
+ ChaCha20 chacha20;
+ if (fuzzed_data_provider.ConsumeBool()) {
+ const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
+ chacha20 = ChaCha20{key.data(), key.size()};
+ }
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 4)) {
+ case 0: {
+ const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
+ chacha20.SetKey(key.data(), key.size());
+ break;
+ }
+ case 1: {
+ chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
+ break;
+ }
+ case 2: {
+ chacha20.Seek(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
+ break;
+ }
+ case 3: {
+ std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ chacha20.Keystream(output.data(), output.size());
+ break;
+ }
+ case 4: {
+ std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
+ chacha20.Crypt(input.data(), output.data(), input.size());
+ break;
+ }
+ }
+ }
+}
diff --git a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp
new file mode 100644
index 0000000000..48e4263f27
--- /dev/null
+++ b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp
@@ -0,0 +1,72 @@
+// 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 <crypto/chacha_poly_aead.h>
+#include <crypto/poly1305.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+
+ const std::vector<uint8_t> k1 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN);
+ const std::vector<uint8_t> k2 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN);
+
+ ChaCha20Poly1305AEAD aead(k1.data(), k1.size(), k2.data(), k2.size());
+ uint64_t seqnr_payload = 0;
+ uint64_t seqnr_aad = 0;
+ int aad_pos = 0;
+ size_t buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
+ std::vector<uint8_t> in(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0);
+ std::vector<uint8_t> out(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0);
+ bool is_encrypt = fuzzed_data_provider.ConsumeBool();
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) {
+ case 0: {
+ buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(64, 4096);
+ in = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0);
+ out = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0);
+ break;
+ }
+ case 1: {
+ (void)aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffer_size, is_encrypt);
+ break;
+ }
+ case 2: {
+ uint32_t len = 0;
+ const bool ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data());
+ assert(ok);
+ break;
+ }
+ case 3: {
+ seqnr_payload += 1;
+ aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN;
+ if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) {
+ aad_pos = 0;
+ seqnr_aad += 1;
+ }
+ break;
+ }
+ case 4: {
+ seqnr_payload = fuzzed_data_provider.ConsumeIntegral<int>();
+ break;
+ }
+ case 5: {
+ seqnr_aad = fuzzed_data_provider.ConsumeIntegral<int>();
+ break;
+ }
+ case 6: {
+ is_encrypt = fuzzed_data_provider.ConsumeBool();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp b/src/test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp
new file mode 100644
index 0000000000..e0a4e90c10
--- /dev/null
+++ b/src/test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp
@@ -0,0 +1,25 @@
+// 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 <crypto/hkdf_sha256_32.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+
+ const std::vector<uint8_t> initial_key_material = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+
+ CHKDF_HMAC_SHA256_L32 hkdf_hmac_sha256_l32(initial_key_material.data(), initial_key_material.size(), fuzzed_data_provider.ConsumeRandomLengthString(1024));
+ while (fuzzed_data_provider.ConsumeBool()) {
+ std::vector<uint8_t> out(32);
+ hkdf_hmac_sha256_l32.Expand32(fuzzed_data_provider.ConsumeRandomLengthString(128), out.data());
+ }
+}
diff --git a/src/test/fuzz/crypto_poly1305.cpp b/src/test/fuzz/crypto_poly1305.cpp
new file mode 100644
index 0000000000..5681e6a693
--- /dev/null
+++ b/src/test/fuzz/crypto_poly1305.cpp
@@ -0,0 +1,22 @@
+// 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 <crypto/poly1305.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+
+ const std::vector<uint8_t> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, POLY1305_KEYLEN);
+ const std::vector<uint8_t> in = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+
+ std::vector<uint8_t> tag_out(POLY1305_TAGLEN);
+ poly1305_auth(tag_out.data(), in.data(), in.size(), key.data());
+}
diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp
index ebf89749e9..36d44e361f 100644
--- a/src/test/fuzz/http_request.cpp
+++ b/src/test/fuzz/http_request.cpp
@@ -7,6 +7,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <util/strencodings.h>
#include <event2/buffer.h>
#include <event2/event.h>
@@ -48,7 +49,14 @@ void test_one_input(const std::vector<uint8_t>& buffer)
assert(evbuf != nullptr);
const std::vector<uint8_t> http_buffer = ConsumeRandomLengthByteVector(fuzzed_data_provider, 4096);
evbuffer_add(evbuf, http_buffer.data(), http_buffer.size());
- if (evhttp_parse_firstline_(evreq, evbuf) != 1 || evhttp_parse_headers_(evreq, evbuf) != 1) {
+ // Avoid constructing requests that will be interpreted by libevent as PROXY requests to avoid triggering
+ // a nullptr dereference. The dereference (req->evcon->http_server) takes place in evhttp_parse_request_line
+ // and is a consequence of our hacky but necessary use of the internal function evhttp_parse_firstline_ in
+ // this fuzzing harness. The workaround is not aesthetically pleasing, but it successfully avoids the troublesome
+ // code path. " http:// HTTP/1.1\n" was a crashing input prior to this workaround.
+ const std::string http_buffer_str = ToLower({http_buffer.begin(), http_buffer.end()});
+ if (http_buffer_str.find(" http://") != std::string::npos || http_buffer_str.find(" https://") != std::string::npos ||
+ evhttp_parse_firstline_(evreq, evbuf) != 1 || evhttp_parse_headers_(evreq, evbuf) != 1) {
evbuffer_free(evbuf);
evhttp_request_free(evreq);
return;
diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp
new file mode 100644
index 0000000000..d9de9d9866
--- /dev/null
+++ b/src/test/fuzz/load_external_block_file.cpp
@@ -0,0 +1,31 @@
+// 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 <chainparams.h>
+#include <flatfile.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+
+#include <cstdint>
+#include <vector>
+
+void initialize()
+{
+ InitializeFuzzingContext();
+}
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ FuzzedFileProvider fuzzed_file_provider = ConsumeFile(fuzzed_data_provider);
+ FILE* fuzzed_block_file = fuzzed_file_provider.open();
+ if (fuzzed_block_file == nullptr) {
+ return;
+ }
+ FlatFilePos flat_file_pos;
+ LoadExternalBlockFile(Params(), fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr);
+}
diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp
index d8d53566c7..2901c704f6 100644
--- a/src/test/fuzz/netaddress.cpp
+++ b/src/test/fuzz/netaddress.cpp
@@ -5,41 +5,13 @@
#include <netaddress.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
#include <cassert>
#include <cstdint>
#include <netinet/in.h>
#include <vector>
-namespace {
-CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept
-{
- const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION});
- if (network == Network::NET_IPV4) {
- const in_addr v4_addr = {
- .s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
- return CNetAddr{v4_addr};
- } else if (network == Network::NET_IPV6) {
- if (fuzzed_data_provider.remaining_bytes() < 16) {
- return CNetAddr{};
- }
- in6_addr v6_addr = {};
- memcpy(v6_addr.s6_addr, fuzzed_data_provider.ConsumeBytes<uint8_t>(16).data(), 16);
- return CNetAddr{v6_addr, fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
- } else if (network == Network::NET_INTERNAL) {
- CNetAddr net_addr;
- net_addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32));
- return net_addr;
- } else if (network == Network::NET_ONION) {
- CNetAddr net_addr;
- net_addr.SetSpecial(fuzzed_data_provider.ConsumeBytesAsString(32));
- return net_addr;
- } else {
- assert(false);
- }
-}
-}; // namespace
-
void test_one_input(const std::vector<uint8_t>& buffer)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp
index 1cbf9b347f..6c94a47f3c 100644
--- a/src/test/fuzz/policy_estimator.cpp
+++ b/src/test/fuzz/policy_estimator.cpp
@@ -14,6 +14,11 @@
#include <string>
#include <vector>
+void initialize()
+{
+ InitializeFuzzingContext();
+}
+
void test_one_input(const std::vector<uint8_t>& buffer)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
@@ -66,4 +71,10 @@ void test_one_input(const std::vector<uint8_t>& buffer)
(void)block_policy_estimator.estimateSmartFee(fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeBool() ? &fee_calculation : nullptr, fuzzed_data_provider.ConsumeBool());
(void)block_policy_estimator.HighestTargetTracked(fuzzed_data_provider.PickValueInArray({FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}));
}
+ {
+ FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider);
+ CAutoFile fuzzed_auto_file = fuzzed_auto_file_provider.open();
+ block_policy_estimator.Write(fuzzed_auto_file);
+ block_policy_estimator.Read(fuzzed_auto_file);
+ }
}
diff --git a/src/test/fuzz/policy_estimator_io.cpp b/src/test/fuzz/policy_estimator_io.cpp
new file mode 100644
index 0000000000..0edcf201c7
--- /dev/null
+++ b/src/test/fuzz/policy_estimator_io.cpp
@@ -0,0 +1,28 @@
+// 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 <policy/fees.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <vector>
+
+void initialize()
+{
+ InitializeFuzzingContext();
+}
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider);
+ CAutoFile fuzzed_auto_file = fuzzed_auto_file_provider.open();
+ // Re-using block_policy_estimator across runs to avoid costly creation of CBlockPolicyEstimator object.
+ static CBlockPolicyEstimator block_policy_estimator;
+ if (block_policy_estimator.Read(fuzzed_auto_file)) {
+ block_policy_estimator.Write(fuzzed_auto_file);
+ }
+}
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index fa8d67059c..9e40d5cd55 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -38,7 +38,7 @@ void ProcessMessage(
const CChainParams& chainparams,
ChainstateManager& chainman,
CTxMemPool& mempool,
- CConnman* connman,
+ CConnman& connman,
BanMan* banman,
const std::atomic<bool>& interruptMsgProc);
@@ -89,7 +89,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
try {
ProcessMessage(p2p_node, random_message_type, random_bytes_data_stream, GetTime<std::chrono::microseconds>(),
Params(), *g_setup->m_node.chainman, *g_setup->m_node.mempool,
- g_setup->m_node.connman.get(), g_setup->m_node.banman.get(),
+ *g_setup->m_node.connman, g_setup->m_node.banman.get(),
std::atomic<bool>{false});
} catch (const std::ios_base::failure&) {
}
diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp
index cad548178d..85aac6ac7a 100644
--- a/src/test/fuzz/script.cpp
+++ b/src/test/fuzz/script.cpp
@@ -48,7 +48,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
if (CompressScript(script, compressed)) {
const unsigned int size = compressed[0];
compressed.erase(compressed.begin());
- assert(size >= 0 && size <= 5);
+ assert(size <= 5);
CScript decompressed_script;
const bool ok = DecompressScript(decompressed_script, size, compressed);
assert(ok);
diff --git a/src/test/fuzz/scriptnum_ops.cpp b/src/test/fuzz/scriptnum_ops.cpp
index f4e079fb89..68c1ae58ca 100644
--- a/src/test/fuzz/scriptnum_ops.cpp
+++ b/src/test/fuzz/scriptnum_ops.cpp
@@ -33,7 +33,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
case 0: {
const int64_t i = fuzzed_data_provider.ConsumeIntegral<int64_t>();
assert((script_num == i) != (script_num != i));
- assert((script_num <= i) != script_num > i);
+ assert((script_num <= i) != (script_num > i));
assert((script_num >= i) != (script_num < i));
// Avoid signed integer overflow:
// script/script.h:264:93: runtime error: signed integer overflow: -2261405121394637306 + -9223372036854775802 cannot be represented in type 'long'
diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp
index 4a8c7a63af..3aaeb66649 100644
--- a/src/test/fuzz/signature_checker.cpp
+++ b/src/test/fuzz/signature_checker.cpp
@@ -28,17 +28,17 @@ public:
{
}
- virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
+ bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
- virtual bool CheckLockTime(const CScriptNum& nLockTime) const
+ bool CheckLockTime(const CScriptNum& nLockTime) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
- virtual bool CheckSequence(const CScriptNum& nSequence) const
+ bool CheckSequence(const CScriptNum& nSequence) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 1c1b2cd254..9f9552edb9 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -8,8 +8,11 @@
#include <amount.h>
#include <arith_uint256.h>
#include <attributes.h>
+#include <chainparamsbase.h>
#include <coins.h>
#include <consensus/consensus.h>
+#include <netaddress.h>
+#include <netbase.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <script/standard.h>
@@ -17,12 +20,14 @@
#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
+#include <test/util/setup_common.h>
#include <txmempool.h>
#include <uint256.h>
#include <version.h>
#include <algorithm>
#include <cstdint>
+#include <cstdio>
#include <optional>
#include <string>
#include <vector>
@@ -228,4 +233,241 @@ NODISCARD inline std::vector<uint8_t> ConsumeFixedLengthByteVector(FuzzedDataPro
return result;
}
+CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION});
+ CNetAddr net_addr;
+ if (network == Network::NET_IPV4) {
+ const in_addr v4_addr = {
+ .s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ net_addr = CNetAddr{v4_addr};
+ } else if (network == Network::NET_IPV6) {
+ if (fuzzed_data_provider.remaining_bytes() >= 16) {
+ in6_addr v6_addr = {};
+ memcpy(v6_addr.s6_addr, fuzzed_data_provider.ConsumeBytes<uint8_t>(16).data(), 16);
+ net_addr = CNetAddr{v6_addr, fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ }
+ } else if (network == Network::NET_INTERNAL) {
+ net_addr.SetInternal(fuzzed_data_provider.ConsumeBytesAsString(32));
+ } else if (network == Network::NET_ONION) {
+ net_addr.SetSpecial(fuzzed_data_provider.ConsumeBytesAsString(32));
+ }
+ return net_addr;
+}
+
+CSubNet ConsumeSubNet(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int32_t>()};
+}
+
+void InitializeFuzzingContext(const std::string& chain_name = CBaseChainParams::REGTEST)
+{
+ static const BasicTestingSetup basic_testing_setup{chain_name, {"-nodebuglogfile"}};
+}
+
+class FuzzedFileProvider
+{
+ FuzzedDataProvider& m_fuzzed_data_provider;
+ int64_t m_offset = 0;
+
+public:
+ FuzzedFileProvider(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}
+ {
+ }
+
+ FILE* open()
+ {
+ if (m_fuzzed_data_provider.ConsumeBool()) {
+ return nullptr;
+ }
+ std::string mode;
+ switch (m_fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) {
+ case 0: {
+ mode = "r";
+ break;
+ }
+ case 1: {
+ mode = "r+";
+ break;
+ }
+ case 2: {
+ mode = "w";
+ break;
+ }
+ case 3: {
+ mode = "w+";
+ break;
+ }
+ case 4: {
+ mode = "a";
+ break;
+ }
+ case 5: {
+ mode = "a+";
+ break;
+ }
+ }
+#ifdef _GNU_SOURCE
+ const cookie_io_functions_t io_hooks = {
+ FuzzedFileProvider::read,
+ FuzzedFileProvider::write,
+ FuzzedFileProvider::seek,
+ FuzzedFileProvider::close,
+ };
+ return fopencookie(this, mode.c_str(), io_hooks);
+#else
+ (void)mode;
+ return nullptr;
+#endif
+ }
+
+ static ssize_t read(void* cookie, char* buf, size_t size)
+ {
+ FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
+ if (buf == nullptr || size == 0 || fuzzed_file->m_fuzzed_data_provider.ConsumeBool()) {
+ return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
+ }
+ const std::vector<uint8_t> random_bytes = fuzzed_file->m_fuzzed_data_provider.ConsumeBytes<uint8_t>(size);
+ if (random_bytes.empty()) {
+ return 0;
+ }
+ std::memcpy(buf, random_bytes.data(), random_bytes.size());
+ if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)random_bytes.size())) {
+ return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
+ }
+ fuzzed_file->m_offset += random_bytes.size();
+ return random_bytes.size();
+ }
+
+ static ssize_t write(void* cookie, const char* buf, size_t size)
+ {
+ FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
+ const ssize_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(0, size);
+ if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)n)) {
+ return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
+ }
+ fuzzed_file->m_offset += n;
+ return n;
+ }
+
+ static int seek(void* cookie, int64_t* offset, int whence)
+ {
+ assert(whence == SEEK_SET || whence == SEEK_CUR); // SEEK_END not implemented yet.
+ FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
+ int64_t new_offset = 0;
+ if (whence == SEEK_SET) {
+ new_offset = *offset;
+ } else if (whence == SEEK_CUR) {
+ if (AdditionOverflow(fuzzed_file->m_offset, *offset)) {
+ return -1;
+ }
+ new_offset = fuzzed_file->m_offset + *offset;
+ }
+ if (new_offset < 0) {
+ return -1;
+ }
+ fuzzed_file->m_offset = new_offset;
+ *offset = new_offset;
+ return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0);
+ }
+
+ static int close(void* cookie)
+ {
+ FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie;
+ return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0);
+ }
+};
+
+NODISCARD inline FuzzedFileProvider ConsumeFile(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ return {fuzzed_data_provider};
+}
+
+class FuzzedAutoFileProvider
+{
+ FuzzedDataProvider& m_fuzzed_data_provider;
+ FuzzedFileProvider m_fuzzed_file_provider;
+
+public:
+ FuzzedAutoFileProvider(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}, m_fuzzed_file_provider{fuzzed_data_provider}
+ {
+ }
+
+ CAutoFile open()
+ {
+ return {m_fuzzed_file_provider.open(), m_fuzzed_data_provider.ConsumeIntegral<int>(), m_fuzzed_data_provider.ConsumeIntegral<int>()};
+ }
+};
+
+NODISCARD inline FuzzedAutoFileProvider ConsumeAutoFile(FuzzedDataProvider& fuzzed_data_provider) noexcept
+{
+ return {fuzzed_data_provider};
+}
+
+#define WRITE_TO_STREAM_CASE(id, type, consume) \
+ case id: { \
+ type o = consume; \
+ stream << o; \
+ break; \
+ }
+template <typename Stream>
+void WriteToStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) noexcept
+{
+ while (fuzzed_data_provider.ConsumeBool()) {
+ try {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 13)) {
+ WRITE_TO_STREAM_CASE(0, bool, fuzzed_data_provider.ConsumeBool())
+ WRITE_TO_STREAM_CASE(1, char, fuzzed_data_provider.ConsumeIntegral<char>())
+ WRITE_TO_STREAM_CASE(2, int8_t, fuzzed_data_provider.ConsumeIntegral<int8_t>())
+ WRITE_TO_STREAM_CASE(3, uint8_t, fuzzed_data_provider.ConsumeIntegral<uint8_t>())
+ WRITE_TO_STREAM_CASE(4, int16_t, fuzzed_data_provider.ConsumeIntegral<int16_t>())
+ WRITE_TO_STREAM_CASE(5, uint16_t, fuzzed_data_provider.ConsumeIntegral<uint16_t>())
+ WRITE_TO_STREAM_CASE(6, int32_t, fuzzed_data_provider.ConsumeIntegral<int32_t>())
+ WRITE_TO_STREAM_CASE(7, uint32_t, fuzzed_data_provider.ConsumeIntegral<uint32_t>())
+ WRITE_TO_STREAM_CASE(8, int64_t, fuzzed_data_provider.ConsumeIntegral<int64_t>())
+ WRITE_TO_STREAM_CASE(9, uint64_t, fuzzed_data_provider.ConsumeIntegral<uint64_t>())
+ WRITE_TO_STREAM_CASE(10, float, fuzzed_data_provider.ConsumeFloatingPoint<float>())
+ WRITE_TO_STREAM_CASE(11, double, fuzzed_data_provider.ConsumeFloatingPoint<double>())
+ WRITE_TO_STREAM_CASE(12, std::string, fuzzed_data_provider.ConsumeRandomLengthString(32))
+ WRITE_TO_STREAM_CASE(13, std::vector<char>, ConsumeRandomLengthIntegralVector<char>(fuzzed_data_provider))
+ }
+ } catch (const std::ios_base::failure&) {
+ break;
+ }
+ }
+}
+
+#define READ_FROM_STREAM_CASE(id, type) \
+ case id: { \
+ type o; \
+ stream >> o; \
+ break; \
+ }
+template <typename Stream>
+void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) noexcept
+{
+ while (fuzzed_data_provider.ConsumeBool()) {
+ try {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 13)) {
+ READ_FROM_STREAM_CASE(0, bool)
+ READ_FROM_STREAM_CASE(1, char)
+ READ_FROM_STREAM_CASE(2, int8_t)
+ READ_FROM_STREAM_CASE(3, uint8_t)
+ READ_FROM_STREAM_CASE(4, int16_t)
+ READ_FROM_STREAM_CASE(5, uint16_t)
+ READ_FROM_STREAM_CASE(6, int32_t)
+ READ_FROM_STREAM_CASE(7, uint32_t)
+ READ_FROM_STREAM_CASE(8, int64_t)
+ READ_FROM_STREAM_CASE(9, uint64_t)
+ READ_FROM_STREAM_CASE(10, float)
+ READ_FROM_STREAM_CASE(11, double)
+ READ_FROM_STREAM_CASE(12, std::string)
+ READ_FROM_STREAM_CASE(13, std::vector<char>)
+ }
+ } catch (const std::ios_base::failure&) {
+ break;
+ }
+ }
+}
+
#endif // BITCOIN_TEST_FUZZ_UTIL_H
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index c4d5d0b6d0..ea3e633cc2 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -383,7 +383,7 @@ BOOST_AUTO_TEST_CASE(netpermissions_test)
BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error));
BOOST_CHECK(error.original.find("Invalid P2P permission") != std::string::npos);
- // Check whitelist error
+ // Check netmask error
BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error));
BOOST_CHECK(error.original.find("Invalid netmask specified in -whitelist") != std::string::npos);
diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp
index fcd831ccda..1a2d775f49 100644
--- a/src/test/settings_tests.cpp
+++ b/src/test/settings_tests.cpp
@@ -12,10 +12,90 @@
#include <univalue.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/system.h>
#include <vector>
+inline bool operator==(const util::SettingsValue& a, const util::SettingsValue& b)
+{
+ return a.write() == b.write();
+}
+
+inline std::ostream& operator<<(std::ostream& os, const util::SettingsValue& value)
+{
+ os << value.write();
+ return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const std::pair<std::string, util::SettingsValue>& kv)
+{
+ util::SettingsValue out(util::SettingsValue::VOBJ);
+ out.__pushKV(kv.first, kv.second);
+ os << out.write();
+ return os;
+}
+
+inline void WriteText(const fs::path& path, const std::string& text)
+{
+ fsbridge::ofstream file;
+ file.open(path);
+ file << text;
+}
+
BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup)
+BOOST_AUTO_TEST_CASE(ReadWrite)
+{
+ fs::path path = GetDataDir() / "settings.json";
+
+ WriteText(path, R"({
+ "string": "string",
+ "num": 5,
+ "bool": true,
+ "null": null
+ })");
+
+ std::map<std::string, util::SettingsValue> expected{
+ {"string", "string"},
+ {"num", 5},
+ {"bool", true},
+ {"null", {}},
+ };
+
+ // Check file read.
+ std::map<std::string, util::SettingsValue> values;
+ std::vector<std::string> errors;
+ BOOST_CHECK(util::ReadSettings(path, values, errors));
+ BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end());
+ BOOST_CHECK(errors.empty());
+
+ // Check no errors if file doesn't exist.
+ fs::remove(path);
+ BOOST_CHECK(util::ReadSettings(path, values, errors));
+ BOOST_CHECK(values.empty());
+ BOOST_CHECK(errors.empty());
+
+ // Check duplicate keys not allowed
+ WriteText(path, R"({
+ "dupe": "string",
+ "dupe": "dupe"
+ })");
+ BOOST_CHECK(!util::ReadSettings(path, values, errors));
+ std::vector<std::string> dup_keys = {strprintf("Found duplicate key dupe in settings file %s", path.string())};
+ BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), dup_keys.begin(), dup_keys.end());
+
+ // Check non-kv json files not allowed
+ WriteText(path, R"("non-kv")");
+ BOOST_CHECK(!util::ReadSettings(path, values, errors));
+ std::vector<std::string> non_kv = {strprintf("Found non-object value \"non-kv\" in settings file %s", path.string())};
+ BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(), non_kv.end());
+
+ // Check invalid json not allowed
+ WriteText(path, R"(invalid json)");
+ BOOST_CHECK(!util::ReadSettings(path, values, errors));
+ std::vector<std::string> fail_parse = {strprintf("Unable to parse settings file %s", path.string())};
+ BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
+}
+
//! Check settings struct contents against expected json strings.
static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val)
{
diff --git a/src/test/sync_tests.cpp b/src/test/sync_tests.cpp
index 5c6c2ee38e..3ea8714f3a 100644
--- a/src/test/sync_tests.cpp
+++ b/src/test/sync_tests.cpp
@@ -18,7 +18,7 @@ void TestPotentialDeadLockDetected(MutexType& mutex1, MutexType& mutex2)
try {
LOCK2(mutex2, mutex1);
} catch (const std::logic_error& e) {
- BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected");
+ BOOST_CHECK_EQUAL(e.what(), "potential deadlock detected: mutex1 -> mutex2 -> mutex1");
error_thrown = true;
}
#ifdef DEBUG_LOCKORDER
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 257328974b..a30e366028 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -9,6 +9,7 @@
#include <key.h> // For CKey
#include <optional.h>
#include <sync.h>
+#include <test/util/logging.h>
#include <test/util/setup_common.h>
#include <test/util/str.h>
#include <uint256.h>
@@ -41,6 +42,16 @@ namespace BCLog {
BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup)
+BOOST_AUTO_TEST_CASE(util_check)
+{
+ // Check that Assert can forward
+ const std::unique_ptr<int> p_two = Assert(MakeUnique<int>(2));
+ // Check that Assert works on lvalues and rvalues
+ const int two = *Assert(p_two);
+ Assert(two == 2);
+ Assert(true);
+}
+
BOOST_AUTO_TEST_CASE(util_criticalsection)
{
RecursiveMutex cs;
@@ -1128,6 +1139,28 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
BOOST_CHECK_EQUAL(out_sha_hex, "f0b3a3c29869edc765d579c928f7f1690a71fbb673b49ccf39cbc4de18156a0d");
}
+BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
+{
+ // Test writing setting.
+ TestArgsManager args1;
+ args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; });
+ args1.WriteSettingsFile();
+
+ // Test reading setting.
+ TestArgsManager args2;
+ args2.ReadSettingsFile();
+ args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); });
+
+ // Test error logging, and remove previously written setting.
+ {
+ ASSERT_DEBUG_LOG("Failed renaming settings file");
+ fs::remove(GetDataDir() / "settings.json");
+ fs::create_directory(GetDataDir() / "settings.json");
+ args2.WriteSettingsFile();
+ fs::remove(GetDataDir() / "settings.json");
+ }
+}
+
BOOST_AUTO_TEST_CASE(util_FormatMoney)
{
BOOST_CHECK_EQUAL(FormatMoney(0), "0.00");
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 7d8eb8a323..1d9f6a4a46 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -726,12 +726,12 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
assert(innerUsage == cachedInnerUsage);
}
-bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb)
+bool CTxMemPool::CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid)
{
LOCK(cs);
- indexed_transaction_set::const_iterator i = mapTx.find(hasha);
+ indexed_transaction_set::const_iterator i = wtxid ? get_iter_from_wtxid(hasha) : mapTx.find(hasha);
if (i == mapTx.end()) return false;
- indexed_transaction_set::const_iterator j = mapTx.find(hashb);
+ indexed_transaction_set::const_iterator j = wtxid ? get_iter_from_wtxid(hashb) : mapTx.find(hashb);
if (j == mapTx.end()) return true;
uint64_t counta = i->GetCountWithAncestors();
uint64_t countb = j->GetCountWithAncestors();
@@ -811,10 +811,10 @@ CTransactionRef CTxMemPool::get(const uint256& hash) const
return i->GetSharedTx();
}
-TxMempoolInfo CTxMemPool::info(const uint256& hash) const
+TxMempoolInfo CTxMemPool::info(const uint256& hash, bool wtxid) const
{
LOCK(cs);
- indexed_transaction_set::const_iterator i = mapTx.find(hash);
+ indexed_transaction_set::const_iterator i = (wtxid ? get_iter_from_wtxid(hash) : mapTx.find(hash));
if (i == mapTx.end())
return TxMempoolInfo();
return GetInfo(i);
@@ -917,8 +917,8 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
size_t CTxMemPool::DynamicMemoryUsage() const {
LOCK(cs);
- // Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
- return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
+ // Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
+ return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
}
void CTxMemPool::RemoveUnbroadcastTx(const uint256& txid, const bool unchecked) {
diff --git a/src/txmempool.h b/src/txmempool.h
index 583f7614b7..d4e9845942 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -198,6 +198,22 @@ struct mempoolentry_txid
}
};
+// extracts a transaction witness-hash from CTxMemPoolEntry or CTransactionRef
+struct mempoolentry_wtxid
+{
+ typedef uint256 result_type;
+ result_type operator() (const CTxMemPoolEntry &entry) const
+ {
+ return entry.GetTx().GetWitnessHash();
+ }
+
+ result_type operator() (const CTransactionRef& tx) const
+ {
+ return tx->GetWitnessHash();
+ }
+};
+
+
/** \class CompareTxMemPoolEntryByDescendantScore
*
* Sort an entry by max(score/size of entry's tx, score/size with all descendants).
@@ -318,6 +334,7 @@ public:
struct descendant_score {};
struct entry_time {};
struct ancestor_score {};
+struct index_by_wtxid {};
class CBlockPolicyEstimator;
@@ -383,8 +400,9 @@ public:
*
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
*
- * mapTx is a boost::multi_index that sorts the mempool on 4 criteria:
- * - transaction hash
+ * mapTx is a boost::multi_index that sorts the mempool on 5 criteria:
+ * - transaction hash (txid)
+ * - witness-transaction hash (wtxid)
* - descendant feerate [we use max(feerate of tx, feerate of tx with all descendants)]
* - time in mempool
* - ancestor feerate [we use min(feerate of tx, feerate of tx with all unconfirmed ancestors)]
@@ -469,6 +487,12 @@ public:
boost::multi_index::indexed_by<
// sorted by txid
boost::multi_index::hashed_unique<mempoolentry_txid, SaltedTxidHasher>,
+ // sorted by wtxid
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<index_by_wtxid>,
+ mempoolentry_wtxid,
+ SaltedTxidHasher
+ >,
// sorted by fee rate
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<descendant_score>,
@@ -549,8 +573,11 @@ private:
std::vector<indexed_transaction_set::const_iterator> GetSortedDepthAndScore() const EXCLUSIVE_LOCKS_REQUIRED(cs);
- /** track locally submitted transactions to periodically retry initial broadcast */
- std::set<uint256> m_unbroadcast_txids GUARDED_BY(cs);
+ /**
+ * track locally submitted transactions to periodically retry initial broadcast
+ * map of txid -> wtxid
+ */
+ std::map<uint256, uint256> m_unbroadcast_txids GUARDED_BY(cs);
public:
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs);
@@ -586,7 +613,7 @@ public:
void clear();
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
- bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);
+ bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false);
void queryHashes(std::vector<uint256>& vtxid) const;
bool isSpent(const COutPoint& outpoint) const;
unsigned int GetTransactionsUpdated() const;
@@ -689,24 +716,32 @@ public:
return totalTxSize;
}
- bool exists(const uint256& hash) const
+ bool exists(const uint256& hash, bool wtxid=false) const
{
LOCK(cs);
+ if (wtxid) {
+ return (mapTx.get<index_by_wtxid>().count(hash) != 0);
+ }
return (mapTx.count(hash) != 0);
}
CTransactionRef get(const uint256& hash) const;
- TxMempoolInfo info(const uint256& hash) const;
+ txiter get_iter_from_wtxid(const uint256& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(cs)
+ {
+ AssertLockHeld(cs);
+ return mapTx.project<0>(mapTx.get<index_by_wtxid>().find(wtxid));
+ }
+ TxMempoolInfo info(const uint256& hash, bool wtxid=false) const;
std::vector<TxMempoolInfo> infoAll() const;
size_t DynamicMemoryUsage() const;
/** Adds a transaction to the unbroadcast set */
- void AddUnbroadcastTx(const uint256& txid) {
+ void AddUnbroadcastTx(const uint256& txid, const uint256& wtxid) {
LOCK(cs);
// Sanity Check: the transaction should also be in the mempool
if (exists(txid)) {
- m_unbroadcast_txids.insert(txid);
+ m_unbroadcast_txids[txid] = wtxid;
}
}
@@ -714,7 +749,7 @@ public:
void RemoveUnbroadcastTx(const uint256& txid, const bool unchecked = false);
/** Returns transactions in unbroadcast set */
- std::set<uint256> GetUnbroadcastTxs() const {
+ std::map<uint256, uint256> GetUnbroadcastTxs() const {
LOCK(cs);
return m_unbroadcast_txids;
}
diff --git a/src/util/check.h b/src/util/check.h
index 3d534fd33e..9edf394492 100644
--- a/src/util/check.h
+++ b/src/util/check.h
@@ -54,6 +54,6 @@ T get_pure_r_value(T&& val)
}
/** Identity function. Abort if the value compares equal to zero */
-#define Assert(val) [&]() -> decltype(get_pure_r_value(val))& { auto& check = (val); assert(#val && check); return check; }()
+#define Assert(val) [&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); assert(#val && check); return std::forward<decltype(get_pure_r_value(val))>(check); }()
#endif // BITCOIN_UTIL_CHECK_H
diff --git a/src/util/settings.cpp b/src/util/settings.cpp
index e4979df755..b92b1d30c3 100644
--- a/src/util/settings.cpp
+++ b/src/util/settings.cpp
@@ -4,6 +4,7 @@
#include <util/settings.h>
+#include <tinyformat.h>
#include <univalue.h>
namespace util {
@@ -12,12 +13,13 @@ namespace {
enum class Source {
FORCED,
COMMAND_LINE,
+ RW_SETTINGS,
CONFIG_FILE_NETWORK_SECTION,
CONFIG_FILE_DEFAULT_SECTION
};
//! Merge settings from multiple sources in precedence order:
-//! Forced config > command line > config file network-specific section > config file default section
+//! Forced config > command line > read-write settings file > config file network-specific section > config file default section
//!
//! This function is provided with a callback function fn that contains
//! specific logic for how to merge the sources.
@@ -32,6 +34,10 @@ static void MergeSettings(const Settings& settings, const std::string& section,
if (auto* values = FindKey(settings.command_line_options, name)) {
fn(SettingsSpan(*values), Source::COMMAND_LINE);
}
+ // Merge in the read-write settings
+ if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
+ fn(SettingsSpan(*value), Source::RW_SETTINGS);
+ }
// Merge in the network-specific section of the config file
if (!section.empty()) {
if (auto* map = FindKey(settings.ro_config, section)) {
@@ -49,6 +55,62 @@ static void MergeSettings(const Settings& settings, const std::string& section,
}
} // namespace
+bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
+{
+ values.clear();
+ errors.clear();
+
+ fsbridge::ifstream file;
+ file.open(path);
+ if (!file.is_open()) return true; // Ok for file not to exist.
+
+ SettingsValue in;
+ if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
+ errors.emplace_back(strprintf("Unable to parse settings file %s", path.string()));
+ return false;
+ }
+
+ if (file.fail()) {
+ errors.emplace_back(strprintf("Failed reading settings file %s", path.string()));
+ return false;
+ }
+ file.close(); // Done with file descriptor. Release while copying data.
+
+ if (!in.isObject()) {
+ errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), path.string()));
+ return false;
+ }
+
+ const std::vector<std::string>& in_keys = in.getKeys();
+ const std::vector<SettingsValue>& in_values = in.getValues();
+ for (size_t i = 0; i < in_keys.size(); ++i) {
+ auto inserted = values.emplace(in_keys[i], in_values[i]);
+ if (!inserted.second) {
+ errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], path.string()));
+ }
+ }
+ return errors.empty();
+}
+
+bool WriteSettings(const fs::path& path,
+ const std::map<std::string, SettingsValue>& values,
+ std::vector<std::string>& errors)
+{
+ SettingsValue out(SettingsValue::VOBJ);
+ for (const auto& value : values) {
+ out.__pushKV(value.first, value.second);
+ }
+ fsbridge::ofstream file;
+ file.open(path);
+ if (file.fail()) {
+ errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", path.string()));
+ return false;
+ }
+ file << out.write(/* prettyIndent= */ 1, /* indentLevel= */ 4) << std::endl;
+ file.close();
+ return true;
+}
+
SettingsValue GetSetting(const Settings& settings,
const std::string& section,
const std::string& name,
diff --git a/src/util/settings.h b/src/util/settings.h
index 1d03639fa2..ed36349232 100644
--- a/src/util/settings.h
+++ b/src/util/settings.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_UTIL_SETTINGS_H
#define BITCOIN_UTIL_SETTINGS_H
+#include <fs.h>
+
#include <map>
#include <string>
#include <vector>
@@ -24,19 +26,31 @@ namespace util {
//! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812)
using SettingsValue = UniValue;
-//! Stored bitcoin settings. This struct combines settings from the command line
-//! and a read-only configuration file.
+//! Stored settings. This struct combines settings from the command line, a
+//! read-only configuration file, and a read-write runtime settings file.
struct Settings {
//! Map of setting name to forced setting value.
std::map<std::string, SettingsValue> forced_settings;
//! Map of setting name to list of command line values.
std::map<std::string, std::vector<SettingsValue>> command_line_options;
+ //! Map of setting name to read-write file setting value.
+ std::map<std::string, SettingsValue> rw_settings;
//! Map of config section name and setting name to list of config file values.
std::map<std::string, std::map<std::string, std::vector<SettingsValue>>> ro_config;
};
+//! Read settings file.
+bool ReadSettings(const fs::path& path,
+ std::map<std::string, SettingsValue>& values,
+ std::vector<std::string>& errors);
+
+//! Write settings file.
+bool WriteSettings(const fs::path& path,
+ const std::map<std::string, SettingsValue>& values,
+ std::vector<std::string>& errors);
+
//! Get settings value from combined sources: forced settings, command line
-//! arguments and the read-only config file.
+//! arguments, runtime read-write settings, and the read-only config file.
//!
//! @param ignore_default_section_config - ignore values in the default section
//! of the config file (part before any
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 7e7ba840cd..8164e884b1 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -73,6 +73,7 @@
const int64_t nStartupTime = GetTime();
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
+const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
ArgsManager gArgs;
@@ -372,6 +373,84 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const
return !GetSetting(strArg).isNull();
}
+bool ArgsManager::InitSettings(std::string& error)
+{
+ if (!GetSettingsPath()) {
+ return true; // Do nothing if settings file disabled.
+ }
+
+ std::vector<std::string> errors;
+ if (!ReadSettingsFile(&errors)) {
+ error = strprintf("Failed loading settings file:\n- %s\n", Join(errors, "\n- "));
+ return false;
+ }
+ if (!WriteSettingsFile(&errors)) {
+ error = strprintf("Failed saving settings file:\n- %s\n", Join(errors, "\n- "));
+ return false;
+ }
+ return true;
+}
+
+bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const
+{
+ if (IsArgNegated("-settings")) {
+ return false;
+ }
+ if (filepath) {
+ std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME);
+ *filepath = fs::absolute(temp ? settings + ".tmp" : settings, GetDataDir(/* net_specific= */ true));
+ }
+ return true;
+}
+
+static void SaveErrors(const std::vector<std::string> errors, std::vector<std::string>* error_out)
+{
+ for (const auto& error : errors) {
+ if (error_out) {
+ error_out->emplace_back(error);
+ } else {
+ LogPrintf("%s\n", error);
+ }
+ }
+}
+
+bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors)
+{
+ fs::path path;
+ if (!GetSettingsPath(&path, /* temp= */ false)) {
+ return true; // Do nothing if settings file disabled.
+ }
+
+ LOCK(cs_args);
+ m_settings.rw_settings.clear();
+ std::vector<std::string> read_errors;
+ if (!util::ReadSettings(path, m_settings.rw_settings, read_errors)) {
+ SaveErrors(read_errors, errors);
+ return false;
+ }
+ return true;
+}
+
+bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const
+{
+ fs::path path, path_tmp;
+ if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) {
+ throw std::logic_error("Attempt to write settings file when dynamic settings are disabled.");
+ }
+
+ LOCK(cs_args);
+ std::vector<std::string> write_errors;
+ if (!util::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) {
+ SaveErrors(write_errors, errors);
+ return false;
+ }
+ if (!RenameOver(path_tmp, path)) {
+ SaveErrors({strprintf("Failed renaming settings file %s to %s\n", path_tmp.string(), path.string())}, errors);
+ return false;
+ }
+ return true;
+}
+
bool ArgsManager::IsArgNegated(const std::string& strArg) const
{
return GetSetting(strArg).isFalse();
@@ -893,6 +972,9 @@ void ArgsManager::LogArgs() const
for (const auto& section : m_settings.ro_config) {
logArgsPrefix("Config file arg:", section.first, section.second);
}
+ for (const auto& setting : m_settings.rw_settings) {
+ LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write());
+ }
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
}
diff --git a/src/util/system.h b/src/util/system.h
index a5eea5dfab..0bd14cc9ea 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -41,6 +41,7 @@
int64_t GetStartupTime();
extern const char * const BITCOIN_CONF_FILENAME;
+extern const char * const BITCOIN_SETTINGS_FILENAME;
void SetupEnvironment();
bool SetupNetworking();
@@ -334,6 +335,39 @@ public:
Optional<unsigned int> GetArgFlags(const std::string& name) const;
/**
+ * Read and update settings file with saved settings. This needs to be
+ * called after SelectParams() because the settings file location is
+ * network-specific.
+ */
+ bool InitSettings(std::string& error);
+
+ /**
+ * Get settings file path, or return false if read-write settings were
+ * disabled with -nosettings.
+ */
+ bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false) const;
+
+ /**
+ * Read settings file. Push errors to vector, or log them if null.
+ */
+ bool ReadSettingsFile(std::vector<std::string>* errors = nullptr);
+
+ /**
+ * Write settings file. Push errors to vector, or log them if null.
+ */
+ bool WriteSettingsFile(std::vector<std::string>* errors = nullptr) const;
+
+ /**
+ * Access settings with lock held.
+ */
+ template <typename Fn>
+ void LockSettings(Fn&& fn)
+ {
+ LOCK(cs_args);
+ fn(m_settings);
+ }
+
+ /**
* Log the config file options and the command line arguments,
* useful for troubleshooting.
*/
diff --git a/src/validation.cpp b/src/validation.cpp
index edc623b205..5aa3d315d5 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -573,8 +573,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
CAmount& nConflictingFees = ws.m_conflicting_fees;
size_t& nConflictingSize = ws.m_conflicting_size;
- if (!CheckTransaction(tx, state))
+ if (!CheckTransaction(tx, state)) {
return false; // state filled in by CheckTransaction
+ }
// Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase())
@@ -684,7 +685,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
CAmount nFees = 0;
if (!Consensus::CheckTxInputs(tx, state, m_view, GetSpendHeight(m_view), nFees)) {
- return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), state.ToString());
+ return false; // state filled in by CheckTxInputs
}
// Check for non-standard pay-to-script-hash in inputs
@@ -938,7 +939,7 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, Precompute
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
// Only the witness is missing, so the transaction itself may be fine.
- state.Invalid(TxValidationResult::TX_WITNESS_MUTATED,
+ state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
state.GetRejectReason(), state.GetDebugMessage());
}
return false; // state filled in by CheckInputScripts
@@ -3640,8 +3641,10 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS
return true;
}
- if (!CheckBlockHeader(block, state, chainparams.GetConsensus()))
- return error("%s: Consensus::CheckBlockHeader: %s, %s", __func__, hash.ToString(), state.ToString());
+ if (!CheckBlockHeader(block, state, chainparams.GetConsensus())) {
+ LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString());
+ return false;
+ }
// Get prev block index
CBlockIndex* pindexPrev = nullptr;
@@ -4707,7 +4710,6 @@ void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi
if (dbp)
dbp->nPos = nBlockPos;
blkdat.SetLimit(nBlockPos + nSize);
- blkdat.SetPos(nBlockPos);
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock& block = *pblock;
blkdat >> block;
@@ -5082,19 +5084,22 @@ bool LoadMempool(CTxMemPool& pool)
}
// TODO: remove this try except in v0.22
+ std::map<uint256, uint256> unbroadcast_txids;
try {
- std::set<uint256> unbroadcast_txids;
file >> unbroadcast_txids;
unbroadcast = unbroadcast_txids.size();
-
- for (const auto& txid : unbroadcast_txids) {
- pool.AddUnbroadcastTx(txid);
- }
} catch (const std::exception&) {
// mempool.dat files created prior to v0.21 will not have an
// unbroadcast set. No need to log a failure if parsing fails here.
}
-
+ for (const auto& elem : unbroadcast_txids) {
+ // Don't add unbroadcast transactions that didn't get back into the
+ // mempool.
+ const CTransactionRef& added_tx = pool.get(elem.first);
+ if (added_tx != nullptr) {
+ pool.AddUnbroadcastTx(elem.first, added_tx->GetWitnessHash());
+ }
+ }
} catch (const std::exception& e) {
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what());
return false;
@@ -5110,7 +5115,7 @@ bool DumpMempool(const CTxMemPool& pool)
std::map<uint256, CAmount> mapDeltas;
std::vector<TxMempoolInfo> vinfo;
- std::set<uint256> unbroadcast_txids;
+ std::map<uint256, uint256> unbroadcast_txids;
static Mutex dump_mutex;
LOCK(dump_mutex);
diff --git a/src/validation.h b/src/validation.h
index a148dacb7c..acadf151c5 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -74,7 +74,6 @@ static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60;
static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
static const bool DEFAULT_TXINDEX = false;
static const char* const DEFAULT_BLOCKFILTERINDEX = "0";
-static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
/** Default for -persistmempool */
static const bool DEFAULT_PERSIST_MEMPOOL = true;
/** Default for using fee filter */
@@ -845,7 +844,7 @@ public:
* validationinterface callback.
*
* @param[in] pblock The block we want to process.
- * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers.
+ * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources.
* @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call
* @returns If the block was processed, independently of block validity
*/
diff --git a/src/version.h b/src/version.h
index d932b512d4..b5f379e1b8 100644
--- a/src/version.h
+++ b/src/version.h
@@ -9,20 +9,13 @@
* network protocol versioning
*/
-static const int PROTOCOL_VERSION = 70015;
+static const int PROTOCOL_VERSION = 70016;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
-//! In this version, 'getheaders' was introduced.
-static const int GETHEADERS_VERSION = 31800;
-
//! disconnect from peers older than this proto version
-static const int MIN_PEER_PROTO_VERSION = GETHEADERS_VERSION;
-
-//! nTime field added to CAddress, starting with this version;
-//! if possible, avoid requesting addresses nodes older than this
-static const int CADDR_TIME_VERSION = 31402;
+static const int MIN_PEER_PROTO_VERSION = 31800;
//! BIP 0031, pong message, is enabled for all versions AFTER this one
static const int BIP0031_VERSION = 60000;
@@ -42,4 +35,7 @@ static const int SHORT_IDS_BLOCKS_VERSION = 70014;
//! not banning for invalid compact blocks starts with this version
static const int INVALID_CB_NO_BAN_VERSION = 70015;
+//! "wtxidrelay" command for wtxid-based relay starts with this version
+static const int WTXID_RELAY_VERSION = 70016;
+
#endif // BITCOIN_VERSION_H
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 5f823d5906..1953be2d54 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -139,7 +139,7 @@ BerkeleyEnvironment::~BerkeleyEnvironment()
Close();
}
-bool BerkeleyEnvironment::Open(bool retry)
+bool BerkeleyEnvironment::Open(bilingual_str& err)
{
if (fDbEnvInit) {
return true;
@@ -149,6 +149,7 @@ bool BerkeleyEnvironment::Open(bool retry)
TryCreateDirectories(pathIn);
if (!LockDirectory(pathIn, ".walletlock")) {
LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
+ err = strprintf(_("Error initializing wallet database environment %s!"), Directory());
return false;
}
@@ -188,23 +189,11 @@ bool BerkeleyEnvironment::Open(bool retry)
LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2));
}
Reset();
- if (retry) {
- // try moving the database env out of the way
- fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime());
- try {
- fs::rename(pathLogDir, pathDatabaseBak);
- LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
- } catch (const fs::filesystem_error&) {
- // failure is ok (well, not really, but it's not worse than what we started with)
- }
- // try opening it again one more time
- if (!Open(false /* retry */)) {
- // if it still fails, it probably means we can't even create the database env
- return false;
- }
- } else {
- return false;
+ err = strprintf(_("Error initializing wallet database environment %s!"), Directory());
+ if (ret == DB_RUNRECOVERY) {
+ err += Untranslated(" ") + _("This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet");
}
+ return false;
}
fDbEnvInit = true;
@@ -300,8 +289,7 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr)
LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion());
LogPrintf("Using wallet %s\n", file_path.string());
- if (!env->Open(true /* retry */)) {
- errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
+ if (!env->Open(errorStr)) {
return false;
}
@@ -324,8 +312,17 @@ void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
dbenv->lsn_reset(strFile.c_str(), 0);
}
+BerkeleyDatabase::~BerkeleyDatabase()
+{
+ if (env) {
+ LOCK(cs_db);
+ size_t erased = env->m_databases.erase(strFile);
+ assert(erased == 1);
+ env->m_fileids.erase(strFile);
+ }
+}
-BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_cursor(nullptr)
+BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr), m_cursor(nullptr), m_database(database)
{
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
@@ -342,7 +339,8 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
{
LOCK(cs_db);
- if (!env->Open(false /* retry */))
+ bilingual_str open_err;
+ if (!env->Open(open_err))
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
pdb = database.m_db.get();
@@ -399,11 +397,16 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
fReadOnly = fTmp;
}
}
- ++env->mapFileUseCount[strFilename];
+ database.AddRef();
strFile = strFilename;
}
}
+void BerkeleyDatabase::Open(const char* mode)
+{
+ throw std::logic_error("BerkeleyDatabase does not implement Open. This function should not be called.");
+}
+
void BerkeleyBatch::Flush()
{
if (activeTxn)
@@ -437,11 +440,7 @@ void BerkeleyBatch::Close()
if (fFlushOnClose)
Flush();
- {
- LOCK(cs_db);
- --env->mapFileUseCount[strFile];
- }
- env->m_db_in_use.notify_all();
+ m_database.RemoveRef();
}
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
@@ -482,7 +481,8 @@ void BerkeleyEnvironment::ReloadDbEnv()
// Reset the environment
Flush(true); // This will flush and close the environment
Reset();
- Open(true);
+ bilingual_str open_err;
+ Open(open_err);
}
bool BerkeleyDatabase::Rewrite(const char* pszSkip)
@@ -623,8 +623,8 @@ bool BerkeleyDatabase::PeriodicFlush()
if (!lockDb) return false;
// Don't flush if any databases are in use
- for (auto it = env->mapFileUseCount.begin() ; it != env->mapFileUseCount.end(); it++) {
- if ((*it).second > 0) return false;
+ for (const auto& use_count : env->mapFileUseCount) {
+ if (use_count.second > 0) return false;
}
// Don't flush if there haven't been any batch writes for this database.
@@ -685,22 +685,17 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) const
}
}
-void BerkeleyDatabase::Flush(bool shutdown)
+void BerkeleyDatabase::Flush()
{
if (!IsDummy()) {
- env->Flush(shutdown);
- if (shutdown) {
- LOCK(cs_db);
- g_dbenvs.erase(env->Directory().string());
- env = nullptr;
- } else {
- // TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the
- // first database shutdown when multiple databases are open in the same
- // environment, should replace raw database `env` pointers with shared or weak
- // pointers, or else separate the database and environment shutdowns so
- // environments can be shut down after databases.
- env->m_fileids.erase(strFile);
- }
+ env->Flush(false);
+ }
+}
+
+void BerkeleyDatabase::Close()
+{
+ if (!IsDummy()) {
+ env->Flush(true);
}
}
@@ -841,3 +836,23 @@ bool BerkeleyBatch::HasKey(CDataStream&& key)
int ret = pdb->exists(activeTxn, datKey, 0);
return ret == 0;
}
+
+void BerkeleyDatabase::AddRef()
+{
+ LOCK(cs_db);
+ ++env->mapFileUseCount[strFile];
+}
+
+void BerkeleyDatabase::RemoveRef()
+{
+ {
+ LOCK(cs_db);
+ --env->mapFileUseCount[strFile];
+ }
+ env->m_db_in_use.notify_all();
+}
+
+std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(const char* mode, bool flush_on_close)
+{
+ return MakeUnique<BerkeleyBatch>(*this, mode, flush_on_close);
+}
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index 599319482b..ef3b81d4d6 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -69,7 +69,7 @@ public:
bool Verify(const std::string& strFile);
- bool Open(bool retry);
+ bool Open(bilingual_str& error);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
@@ -90,62 +90,67 @@ public:
/** Get BerkeleyEnvironment and database filename given a wallet path. */
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
-/** Return wheter a BDB wallet database is currently loaded. */
+/** Return whether a BDB wallet database is currently loaded. */
bool IsBDBWalletLoaded(const fs::path& wallet_path);
+class BerkeleyBatch;
+
/** An instance of this class represents one database.
* For BerkeleyDB this is just a (env, strFile) tuple.
**/
-class BerkeleyDatabase
+class BerkeleyDatabase : public WalletDatabase
{
friend class BerkeleyBatch;
public:
/** Create dummy DB handle */
- BerkeleyDatabase() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr)
+ BerkeleyDatabase() : WalletDatabase(), env(nullptr)
{
}
/** Create DB handle to real database */
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
- nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename))
+ WalletDatabase(), env(std::move(env)), strFile(std::move(filename))
{
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
}
- ~BerkeleyDatabase() {
- if (env) {
- size_t erased = env->m_databases.erase(strFile);
- assert(erased == 1);
- }
- }
+ ~BerkeleyDatabase() override;
+
+ /** Open the database if it is not already opened.
+ * Dummy function, doesn't do anything right now, but is needed for class abstraction */
+ void Open(const char* mode) override;
/** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
*/
- bool Rewrite(const char* pszSkip=nullptr);
+ bool Rewrite(const char* pszSkip=nullptr) override;
+
+ /** Indicate the a new database user has began using the database. */
+ void AddRef() override;
+ /** Indicate that database user has stopped using the database and that it could be flushed or closed. */
+ void RemoveRef() override;
/** Back up the entire database to a file.
*/
- bool Backup(const std::string& strDest) const;
+ bool Backup(const std::string& strDest) const override;
- /** Make sure all changes are flushed to disk.
+ /** Make sure all changes are flushed to database file.
*/
- void Flush(bool shutdown);
+ void Flush() override;
+ /** Flush to the database file and close the database.
+ * Also close the environment if no other databases are open in it.
+ */
+ void Close() override;
/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
- bool PeriodicFlush();
-
- void IncrementUpdateCounter();
+ bool PeriodicFlush() override;
- void ReloadDbEnv();
+ void IncrementUpdateCounter() override;
- std::atomic<unsigned int> nUpdateCounter;
- unsigned int nLastSeen;
- unsigned int nLastFlushed;
- int64_t nLastWalletUpdate;
+ void ReloadDbEnv() override;
/** Verifies the environment and database file */
- bool Verify(bilingual_str& error);
+ bool Verify(bilingual_str& error) override;
/**
* Pointer to shared database environment.
@@ -161,6 +166,9 @@ public:
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
std::unique_ptr<Db> m_db;
+ /** Make a BerkeleyBatch connected to this database */
+ std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) override;
+
private:
std::string strFile;
@@ -172,7 +180,7 @@ private:
};
/** RAII class that provides access to a Berkeley database */
-class BerkeleyBatch
+class BerkeleyBatch : public DatabaseBatch
{
/** RAII class that automatically cleanses its data on destruction */
class SafeDbt final
@@ -195,10 +203,10 @@ class BerkeleyBatch
};
private:
- bool ReadKey(CDataStream&& key, CDataStream& value);
- bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true);
- bool EraseKey(CDataStream&& key);
- bool HasKey(CDataStream&& key);
+ bool ReadKey(CDataStream&& key, CDataStream& value) override;
+ bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite = true) override;
+ bool EraseKey(CDataStream&& key) override;
+ bool HasKey(CDataStream&& key) override;
protected:
Db* pdb;
@@ -208,74 +216,24 @@ protected:
bool fReadOnly;
bool fFlushOnClose;
BerkeleyEnvironment *env;
+ BerkeleyDatabase& m_database;
public:
explicit BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
- ~BerkeleyBatch() { Close(); }
+ ~BerkeleyBatch() override { Close(); }
BerkeleyBatch(const BerkeleyBatch&) = delete;
BerkeleyBatch& operator=(const BerkeleyBatch&) = delete;
- void Flush();
- void Close();
-
- template <typename K, typename T>
- bool Read(const K& key, T& value)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
-
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- if (!ReadKey(std::move(ssKey), ssValue)) return false;
- try {
- ssValue >> value;
- return true;
- } catch (const std::exception&) {
- return false;
- }
- }
-
- template <typename K, typename T>
- bool Write(const K& key, const T& value, bool fOverwrite = true)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
-
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- ssValue.reserve(10000);
- ssValue << value;
-
- return WriteKey(std::move(ssKey), std::move(ssValue), fOverwrite);
- }
-
- template <typename K>
- bool Erase(const K& key)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
-
- return EraseKey(std::move(ssKey));
- }
-
- template <typename K>
- bool Exists(const K& key)
- {
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- ssKey.reserve(1000);
- ssKey << key;
-
- return HasKey(std::move(ssKey));
- }
+ void Flush() override;
+ void Close() override;
- bool StartCursor();
- bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete);
- void CloseCursor();
- bool TxnBegin();
- bool TxnCommit();
- bool TxnAbort();
+ bool StartCursor() override;
+ bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override;
+ void CloseCursor() override;
+ bool TxnBegin() override;
+ bool TxnCommit() override;
+ bool TxnAbort() override;
};
std::string BerkeleyDatabaseVersion();
diff --git a/src/wallet/context.h b/src/wallet/context.h
index 3c8fdd1c59..a83591154f 100644
--- a/src/wallet/context.h
+++ b/src/wallet/context.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_WALLET_CONTEXT_H
#define BITCOIN_WALLET_CONTEXT_H
+class ArgsManager;
namespace interfaces {
class Chain;
} // namespace interfaces
@@ -21,6 +22,7 @@ class Chain;
//! behavior.
struct WalletContext {
interfaces::Chain* chain{nullptr};
+ ArgsManager* args{nullptr};
//! 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/db.h b/src/wallet/db.h
index 1322bf54fa..12dc1cc96b 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -6,12 +6,152 @@
#ifndef BITCOIN_WALLET_DB_H
#define BITCOIN_WALLET_DB_H
+#include <clientversion.h>
#include <fs.h>
+#include <streams.h>
+#include <atomic>
+#include <memory>
#include <string>
+struct bilingual_str;
+
/** Given a wallet directory path or legacy file path, return path to main data file in the wallet database. */
fs::path WalletDataFilePath(const fs::path& wallet_path);
void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename);
+/** RAII class that provides access to a WalletDatabase */
+class DatabaseBatch
+{
+private:
+ virtual bool ReadKey(CDataStream&& key, CDataStream& value) = 0;
+ virtual bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) = 0;
+ virtual bool EraseKey(CDataStream&& key) = 0;
+ virtual bool HasKey(CDataStream&& key) = 0;
+
+public:
+ explicit DatabaseBatch() {}
+ virtual ~DatabaseBatch() {}
+
+ DatabaseBatch(const DatabaseBatch&) = delete;
+ DatabaseBatch& operator=(const DatabaseBatch&) = delete;
+
+ virtual void Flush() = 0;
+ virtual void Close() = 0;
+
+ template <typename K, typename T>
+ bool Read(const K& key, T& value)
+ {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(1000);
+ ssKey << key;
+
+ CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+ if (!ReadKey(std::move(ssKey), ssValue)) return false;
+ try {
+ ssValue >> value;
+ return true;
+ } catch (const std::exception&) {
+ return false;
+ }
+ }
+
+ template <typename K, typename T>
+ bool Write(const K& key, const T& value, bool fOverwrite = true)
+ {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(1000);
+ ssKey << key;
+
+ CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+ ssValue.reserve(10000);
+ ssValue << value;
+
+ return WriteKey(std::move(ssKey), std::move(ssValue), fOverwrite);
+ }
+
+ template <typename K>
+ bool Erase(const K& key)
+ {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(1000);
+ ssKey << key;
+
+ return EraseKey(std::move(ssKey));
+ }
+
+ template <typename K>
+ bool Exists(const K& key)
+ {
+ CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+ ssKey.reserve(1000);
+ ssKey << key;
+
+ return HasKey(std::move(ssKey));
+ }
+
+ virtual bool StartCursor() = 0;
+ virtual bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) = 0;
+ virtual void CloseCursor() = 0;
+ virtual bool TxnBegin() = 0;
+ virtual bool TxnCommit() = 0;
+ virtual bool TxnAbort() = 0;
+};
+
+/** An instance of this class represents one database.
+ **/
+class WalletDatabase
+{
+public:
+ /** Create dummy DB handle */
+ WalletDatabase() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0) {}
+ virtual ~WalletDatabase() {};
+
+ /** Open the database if it is not already opened. */
+ virtual void Open(const char* mode) = 0;
+
+ //! Counts the number of active database users to be sure that the database is not closed while someone is using it
+ std::atomic<int> m_refcount{0};
+ /** Indicate the a new database user has began using the database. Increments m_refcount */
+ virtual void AddRef() = 0;
+ /** Indicate that database user has stopped using the database and that it could be flushed or closed. Decrement m_refcount */
+ virtual void RemoveRef() = 0;
+
+ /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
+ */
+ virtual bool Rewrite(const char* pszSkip=nullptr) = 0;
+
+ /** Back up the entire database to a file.
+ */
+ virtual bool Backup(const std::string& strDest) const = 0;
+
+ /** Make sure all changes are flushed to database file.
+ */
+ virtual void Flush() = 0;
+ /** Flush to the database file and close the database.
+ * Also close the environment if no other databases are open in it.
+ */
+ virtual void Close() = 0;
+ /* flush the wallet passively (TRY_LOCK)
+ ideal to be called periodically */
+ virtual bool PeriodicFlush() = 0;
+
+ virtual void IncrementUpdateCounter() = 0;
+
+ virtual void ReloadDbEnv() = 0;
+
+ std::atomic<unsigned int> nUpdateCounter;
+ unsigned int nLastSeen;
+ unsigned int nLastFlushed;
+ int64_t nLastWalletUpdate;
+
+ /** Verifies the environment and database file */
+ virtual bool Verify(bilingual_str& error) = 0;
+
+ std::string m_file_path;
+
+ /** Make a DatabaseBatch connected to this database */
+ virtual std::unique_ptr<DatabaseBatch> MakeBatch(const char* mode = "r+", bool flush_on_close = true) = 0;
+};
+
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index f173b5e62b..781920755c 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -9,6 +9,7 @@
#include <node/context.h>
#include <node/ui_interface.h>
#include <outputtype.h>
+#include <util/check.h>
#include <util/moneystr.h>
#include <util/system.h>
#include <util/translation.h>
@@ -16,9 +17,9 @@
#include <wallet/wallet.h>
#include <walletinitinterface.h>
-class WalletInit : public WalletInitInterface {
+class WalletInit : public WalletInitInterface
+{
public:
-
//! Was the wallet component compiled in.
bool HasWalletSupport() const override {return true;}
@@ -112,10 +113,11 @@ bool WalletInit::ParameterInteraction() const
void WalletInit::Construct(NodeContext& node) const
{
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ ArgsManager& args = *Assert(node.args);
+ if (args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
LogPrintf("Wallet disabled!\n");
return;
}
- gArgs.SoftSetArg("-wallet", "");
- node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, gArgs.GetArgs("-wallet")));
+ args.SoftSetArg("-wallet", "");
+ node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet")));
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 8df3e78215..2a81d30133 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -11,6 +11,7 @@
#include <util/system.h>
#include <util/translation.h>
#include <wallet/wallet.h>
+#include <wallet/walletdb.h>
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
{
@@ -82,28 +83,30 @@ bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& walle
}
}
-void StartWallets(CScheduler& scheduler)
+void StartWallets(CScheduler& scheduler, const ArgsManager& args)
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
pwallet->postInitProcess();
}
// Schedule periodic wallet flushes and tx rebroadcasts
- scheduler.scheduleEvery(MaybeCompactWalletDB, std::chrono::milliseconds{500});
+ if (args.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
+ scheduler.scheduleEvery(MaybeCompactWalletDB, std::chrono::milliseconds{500});
+ }
scheduler.scheduleEvery(MaybeResendWalletTxs, std::chrono::milliseconds{1000});
}
void FlushWallets()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
- pwallet->Flush(false);
+ pwallet->Flush();
}
}
void StopWallets()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
- pwallet->Flush(true);
+ pwallet->Close();
}
}
diff --git a/src/wallet/load.h b/src/wallet/load.h
index e24b1f2e69..ff4f5b4b23 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
+class ArgsManager;
class CScheduler;
namespace interfaces {
@@ -22,7 +23,7 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files);
//! Complete startup of wallets.
-void StartWallets(CScheduler& scheduler);
+void StartWallets(CScheduler& scheduler, const ArgsManager& args);
//! Flush all wallets in preparation for shutdown.
void FlushWallets();
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index c9ea6c2ad9..3b752ca936 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1547,7 +1547,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue&
if (!w_desc.descriptor->GetOutputType()) {
warnings.push_back("Unknown output type, cannot set descriptor to active.");
} else {
- pwallet->SetActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
+ pwallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
}
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 55114a17d7..9d334063c4 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -359,36 +359,54 @@ static UniValue setlabel(const JSONRPCRequest& request)
return NullUniValue;
}
+void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) {
+ std::set<CTxDestination> destinations;
+ int i = 0;
+ for (const std::string& address: address_amounts.getKeys()) {
+ CTxDestination dest = DecodeDestination(address);
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + address);
+ }
-static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
-{
- CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
+ if (destinations.count(dest)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address);
+ }
+ destinations.insert(dest);
- // Check amount
- if (nValue <= 0)
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
+ CScript script_pub_key = GetScriptForDestination(dest);
+ CAmount amount = AmountFromValue(address_amounts[i++]);
- if (nValue > curBalance)
- throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
+ bool subtract_fee = false;
+ for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
+ const UniValue& addr = subtract_fee_outputs[idx];
+ if (addr.get_str() == address) {
+ subtract_fee = true;
+ }
+ }
- // Parse Bitcoin address
- CScript scriptPubKey = GetScriptForDestination(address);
+ CRecipient recipient = {script_pub_key, amount, subtract_fee};
+ recipients.push_back(recipient);
+ }
+}
+
+UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value)
+{
+ EnsureWalletIsUnlocked(pwallet);
- // Create and send the transaction
+ // Shuffle recipient list
+ std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
+
+ // Send
CAmount nFeeRequired = 0;
- bilingual_str error;
- std::vector<CRecipient> vecSend;
int nChangePosRet = -1;
- CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
- vecSend.push_back(recipient);
+ bilingual_str error;
CTransactionRef tx;
- if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control)) {
- if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
- error = strprintf(Untranslated("Error: This transaction requires a transaction fee of at least %s"), FormatMoney(nFeeRequired));
- throw JSONRPCError(RPC_WALLET_ERROR, error.original);
+ bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ if (!fCreated) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
}
- pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
- return tx;
+ pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
+ return tx->GetHash().GetHex();
}
static UniValue sendtoaddress(const JSONRPCRequest& request)
@@ -436,16 +454,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
LOCK(pwallet->cs_wallet);
- CTxDestination dest = DecodeDestination(request.params[0].get_str());
- if (!IsValidDestination(dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
- }
-
- // Amount
- CAmount nAmount = AmountFromValue(request.params[1]);
- if (nAmount <= 0)
- throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
-
// Wallet comments
mapValue_t mapValue;
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
@@ -471,8 +479,18 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet);
- CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
- return tx->GetHash().GetHex();
+ UniValue address_amounts(UniValue::VOBJ);
+ const std::string address = request.params[0].get_str();
+ address_amounts.pushKV(address, request.params[1]);
+ UniValue subtractFeeFromAmount(UniValue::VARR);
+ if (fSubtractFeeFromAmount) {
+ subtractFeeFromAmount.push_back(address);
+ }
+
+ std::vector<CRecipient> recipients;
+ ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
+
+ return SendMoney(pwallet, coin_control, recipients, mapValue);
}
static UniValue listaddressgroupings(const JSONRPCRequest& request)
@@ -860,52 +878,10 @@ static UniValue sendmany(const JSONRPCRequest& request)
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
- std::set<CTxDestination> destinations;
- std::vector<CRecipient> vecSend;
+ std::vector<CRecipient> recipients;
+ ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
- std::vector<std::string> keys = sendTo.getKeys();
- for (const std::string& name_ : keys) {
- CTxDestination dest = DecodeDestination(name_);
- if (!IsValidDestination(dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
- }
-
- if (destinations.count(dest)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
- }
- destinations.insert(dest);
-
- CScript scriptPubKey = GetScriptForDestination(dest);
- CAmount nAmount = AmountFromValue(sendTo[name_]);
- if (nAmount <= 0)
- throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
-
- bool fSubtractFeeFromAmount = false;
- for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
- const UniValue& addr = subtractFeeFromAmount[idx];
- if (addr.get_str() == name_)
- fSubtractFeeFromAmount = true;
- }
-
- CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
- vecSend.push_back(recipient);
- }
-
- EnsureWalletIsUnlocked(pwallet);
-
- // Shuffle recipient list
- std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
-
- // Send
- CAmount nFeeRequired = 0;
- int nChangePosRet = -1;
- bilingual_str error;
- CTransactionRef tx;
- bool fCreated = pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control);
- if (!fCreated)
- throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
- pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
- return tx->GetHash().GetHex();
+ return SendMoney(pwallet, coin_control, recipients, std::move(mapValue));
}
static UniValue addmultisigaddress(const JSONRPCRequest& request)
@@ -3141,7 +3117,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
CAmount fee;
int change_position;
CCoinControl coin_control;
- // Automatically select (additional) coins. Can be overriden by options.add_inputs.
+ // Automatically select (additional) coins. Can be overridden by options.add_inputs.
coin_control.m_add_inputs = true;
FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control);
@@ -4075,7 +4051,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
CCoinControl coin_control;
// Automatically select coins, unless at least one is manually selected. Can
- // be overriden by options.add_inputs.
+ // be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control);
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index e6e62332c0..af57210f01 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -5,6 +5,7 @@
#include <fs.h>
#include <streams.h>
+#include <util/translation.h>
#include <wallet/salvage.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
@@ -20,8 +21,9 @@ bool RecoverDatabaseFile(const fs::path& file_path)
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
- if (!env->Open(true /* retry */)) {
- tfm::format(std::cerr, "Error initializing wallet database environment %s!", env->Directory());
+ bilingual_str open_err;
+ if (!env->Open(open_err)) {
+ tfm::format(std::cerr, "%s\n", open_err.original);
return false;
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 38d94335a3..51715462c5 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -905,20 +905,22 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim
return AddWatchOnly(dest);
}
-void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly)
+void LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain)
{
LOCK(cs_KeyStore);
- // memonly == true means we are loading the wallet file
- // memonly == false means that the chain is actually being changed
- if (!memonly) {
- // Store the new chain
- if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
- throw std::runtime_error(std::string(__func__) + ": writing chain failed");
- }
- // When there's an old chain, add it as an inactive chain as we are now rotating hd chains
- if (!m_hd_chain.seed_id.IsNull()) {
- AddInactiveHDChain(m_hd_chain);
- }
+ m_hd_chain = chain;
+}
+
+void LegacyScriptPubKeyMan::AddHDChain(const CHDChain& chain)
+{
+ LOCK(cs_KeyStore);
+ // Store the new chain
+ if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
+ throw std::runtime_error(std::string(__func__) + ": writing chain failed");
+ }
+ // When there's an old chain, add it as an inactive chain as we are now rotating hd chains
+ if (!m_hd_chain.seed_id.IsNull()) {
+ AddInactiveHDChain(m_hd_chain);
}
m_hd_chain = chain;
@@ -1172,7 +1174,7 @@ void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed)
CHDChain newHdChain;
newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
newHdChain.seed_id = seed.GetID();
- SetHDChain(newHdChain, false);
+ AddHDChain(newHdChain);
NotifyCanGetAddressesChanged();
WalletBatch batch(m_storage.GetDatabase());
m_storage.UnsetBlankWalletFlag(batch);
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 9fa2a68284..a96d971734 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -422,8 +422,10 @@ public:
//! Generate a new key
CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
- /* Set the HD chain model (chain child index counters) */
- void SetHDChain(const CHDChain& chain, bool memonly);
+ /* Set the HD chain model (chain child index counters) and writes it to the database */
+ void AddHDChain(const CHDChain& chain);
+ //! Load a HD chain model (used by LoadWallet)
+ void LoadHDChain(const CHDChain& chain);
const CHDChain& GetHDChain() const { return m_hd_chain; }
void AddInactiveHDChain(const CHDChain& chain);
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index 797a0d634f..35bd965673 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -3,13 +3,14 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <fs.h>
+#include <util/check.h>
#include <util/system.h>
#include <wallet/test/init_test_fixture.h>
-InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName): BasicTestingSetup(chainName)
+InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
{
- m_chain_client = MakeWalletClient(*m_chain, {});
+ m_chain_client = MakeWalletClient(*m_chain, *Assert(m_node.args), {});
std::string sep;
sep += fs::path::preferred_separator;
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index 6c32868b1e..99d7cfe921 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -10,17 +10,18 @@
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
#include <node/context.h>
+#include <util/check.h>
#include <wallet/wallet.h>
#include <memory>
/** Testing setup and teardown for wallet.
*/
-struct WalletTestingSetup: public TestingSetup {
+struct WalletTestingSetup : public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
- std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, {});
+ std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, *Assert(m_node.args), {});
CWallet m_wallet;
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
};
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 5c565a3d38..d2770a46f7 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -28,6 +28,11 @@ extern UniValue importmulti(const JSONRPCRequest& request);
extern UniValue dumpwallet(const JSONRPCRequest& request);
extern UniValue importwallet(const JSONRPCRequest& request);
+// 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");
+static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wallet incremental fee is smaller than default incremental relay fee");
+
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
@@ -791,4 +796,37 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
TestUnloadWallet(std::move(wallet));
}
+BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto wallet = TestLoadWallet(*chain);
+ CKey key;
+ key.MakeNewKey(true);
+ AddKey(*wallet, key);
+
+ std::string error;
+ m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
+ CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+
+ SyncWithValidationInterfaceQueue();
+
+ {
+ auto block_hash = block_tx.GetHash();
+ auto prev_hash = m_coinbase_txns[0]->GetHash();
+
+ LOCK(wallet->cs_wallet);
+ BOOST_CHECK(wallet->HasWalletSpend(prev_hash));
+ BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
+
+ std::vector<uint256> vHashIn{ block_hash }, vHashOut;
+ BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
+
+ BOOST_CHECK(!wallet->HasWalletSpend(prev_hash));
+ BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
+ }
+
+ TestUnloadWallet(std::move(wallet));
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 29ff7bbef1..cee2f2214c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -439,9 +439,14 @@ bool CWallet::HasWalletSpend(const uint256& txid) const
return (iter != mapTxSpends.end() && iter->first.hash == txid);
}
-void CWallet::Flush(bool shutdown)
+void CWallet::Flush()
{
- database->Flush(shutdown);
+ database->Flush();
+}
+
+void CWallet::Close()
+{
+ database->Close();
}
void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> range)
@@ -1422,19 +1427,28 @@ bool CWallet::IsWalletFlagSet(uint64_t flag) const
return (m_wallet_flags & flag);
}
-bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
+bool CWallet::LoadWalletFlags(uint64_t flags)
{
LOCK(cs_wallet);
- m_wallet_flags = overwriteFlags;
- if (((overwriteFlags & KNOWN_WALLET_FLAGS) >> 32) ^ (overwriteFlags >> 32)) {
+ if (((flags & KNOWN_WALLET_FLAGS) >> 32) ^ (flags >> 32)) {
// contains unknown non-tolerable wallet flags
return false;
}
- if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
+ m_wallet_flags = flags;
+
+ return true;
+}
+
+bool CWallet::AddWalletFlags(uint64_t flags)
+{
+ LOCK(cs_wallet);
+ // We should never be writing unknown non-tolerable wallet flags
+ assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
+ if (!WalletBatch(*database).WriteWalletFlags(flags)) {
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
- return true;
+ return LoadWalletFlags(flags);
}
int64_t CWalletTx::GetTxTime() const
@@ -3120,9 +3134,11 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
{
AssertLockHeld(cs_wallet);
DBErrors nZapSelectTxRet = WalletBatch(*database, "cr+").ZapSelectTx(vHashIn, vHashOut);
- for (uint256 hash : vHashOut) {
+ for (const uint256& hash : vHashOut) {
const auto& it = mapWallet.find(hash);
wtxOrdered.erase(it->second.m_it_wtxOrdered);
+ for (const auto& txin : it->second.tx->vin)
+ mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
NotifyTransactionChanged(this, hash, CT_DELETED);
}
@@ -3796,7 +3812,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
walletInstance->SetMinVersion(FEATURE_LATEST);
- walletInstance->SetWalletFlags(wallet_creation_flags, false);
+ walletInstance->AddWalletFlags(wallet_creation_flags);
// Only create LegacyScriptPubKeyMan when not descriptor wallet
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
@@ -4417,12 +4433,21 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
spk_manager->SetupDescriptorGeneration(master_key, t);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
- SetActiveScriptPubKeyMan(id, t, internal);
+ AddActiveScriptPubKeyMan(id, t, internal);
}
}
}
-void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly)
+void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
+{
+ WalletBatch batch(*database);
+ if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
+ throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
+ }
+ LoadActiveScriptPubKeyMan(id, type, internal);
+}
+
+void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
@@ -4430,12 +4455,6 @@ void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
- if (!memonly) {
- WalletBatch batch(*database);
- if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
- throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
- }
- }
NotifyCanGetAddressesChanged();
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 32d8481cd8..a761caf38c 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1087,7 +1087,10 @@ public:
bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Flush wallet (bitdb flush)
- void Flush(bool shutdown=false);
+ void Flush();
+
+ //! Close wallet database
+ void Close();
/** Wallet is about to be unloaded */
boost::signals2::signal<void ()> NotifyUnload;
@@ -1176,7 +1179,9 @@ public:
/** overwrite all flags by the given uint64_t
returns false if unknown, non-tolerable flags are present */
- bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
+ bool AddWalletFlags(uint64_t flags);
+ /** Loads the flags into the wallet. (used by LoadWallet) */
+ bool LoadWalletFlags(uint64_t flags);
/** Determine if we are a legacy wallet */
bool IsLegacy() const;
@@ -1254,12 +1259,17 @@ public:
//! Instantiate a descriptor ScriptPubKeyMan from the WalletDescriptor and load it
void LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc);
- //! Sets the active ScriptPubKeyMan for the specified type and internal
+ //! Adds the active ScriptPubKeyMan for the specified type and internal. Writes it to the wallet file
+ //! @param[in] id The unique id for the ScriptPubKeyMan
+ //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
+ //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
+ void AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+
+ //! Loads an active ScriptPubKeyMan for the specified type and internal. (used by LoadWallet)
//! @param[in] id The unique id for the ScriptPubKeyMan
//! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
- //! @param[in] memonly Whether to record this update to the database. Set to true for wallet loading, normally false when actually updating the wallet.
- void SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly = false);
+ void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans();
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 7da477d5b7..8c409b40cd 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -121,7 +121,7 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
if (!WriteIC(key, std::make_pair(vchCryptedSecret, checksum), false)) {
// It may already exist, so try writing just the checksum
std::vector<unsigned char> val;
- if (!m_batch.Read(key, val)) {
+ if (!m_batch->Read(key, val)) {
return false;
}
if (!WriteIC(key, std::make_pair(val, checksum), true)) {
@@ -166,8 +166,8 @@ bool WalletBatch::WriteBestBlock(const CBlockLocator& locator)
bool WalletBatch::ReadBestBlock(CBlockLocator& locator)
{
- if (m_batch.Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true;
- return m_batch.Read(DBKeys::BESTBLOCK_NOMERKLE, locator);
+ if (m_batch->Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true;
+ return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, locator);
}
bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext)
@@ -177,7 +177,7 @@ bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext)
bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool)
{
- return m_batch.Read(std::make_pair(DBKeys::POOL, nPool), keypool);
+ return m_batch->Read(std::make_pair(DBKeys::POOL, nPool), keypool);
}
bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool)
@@ -539,11 +539,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} else if (strType == DBKeys::HDCHAIN) {
CHDChain chain;
ssValue >> chain;
- pwallet->GetOrCreateLegacyScriptPubKeyMan()->SetHDChain(chain, true);
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
} else if (strType == DBKeys::FLAGS) {
uint64_t flags;
ssValue >> flags;
- if (!pwallet->SetWalletFlags(flags, true)) {
+ if (!pwallet->LoadWalletFlags(flags)) {
strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
return false;
}
@@ -592,9 +592,6 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssValue >> ser_xpub;
CExtPubKey xpub;
xpub.Decode(ser_xpub.data());
- if (wss.m_descriptor_caches.count(desc_id)) {
- wss.m_descriptor_caches[desc_id] = DescriptorCache();
- }
if (parent) {
wss.m_descriptor_caches[desc_id].CacheParentExtPubKey(key_exp_index, xpub);
} else {
@@ -693,14 +690,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
LOCK(pwallet->cs_wallet);
try {
int nMinVersion = 0;
- if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
+ if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
if (nMinVersion > FEATURE_LATEST)
return DBErrors::TOO_NEW;
pwallet->LoadMinVersion(nMinVersion);
}
// Get cursor
- if (!m_batch.StartCursor())
+ if (!m_batch->StartCursor())
{
pwallet->WalletLogPrintf("Error getting wallet database cursor\n");
return DBErrors::CORRUPT;
@@ -712,13 +709,13 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
bool complete;
- bool ret = m_batch.ReadAtCursor(ssKey, ssValue, complete);
+ bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);
if (complete) {
break;
}
else if (!ret)
{
- m_batch.CloseCursor();
+ m_batch->CloseCursor();
pwallet->WalletLogPrintf("Error reading next record from wallet database\n");
return DBErrors::CORRUPT;
}
@@ -748,14 +745,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
} catch (...) {
result = DBErrors::CORRUPT;
}
- m_batch.CloseCursor();
+ m_batch->CloseCursor();
// Set the active ScriptPubKeyMans
for (auto spk_man_pair : wss.m_active_external_spks) {
- pwallet->SetActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false, /* memonly */ true);
+ pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false);
}
for (auto spk_man_pair : wss.m_active_internal_spks) {
- pwallet->SetActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true, /* memonly */ true);
+ pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true);
}
// Set the descriptor caches
@@ -785,7 +782,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
// Last client version to open this wallet, was previously the file version number
int last_client = CLIENT_VERSION;
- m_batch.Read(DBKeys::VERSION, last_client);
+ m_batch->Read(DBKeys::VERSION, last_client);
int wallet_version = pwallet->GetVersion();
pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client);
@@ -810,7 +807,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
return DBErrors::NEED_REWRITE;
if (last_client < CLIENT_VERSION) // Update
- m_batch.Write(DBKeys::VERSION, CLIENT_VERSION);
+ m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);
if (wss.fAnyUnordered)
result = pwallet->ReorderTransactions();
@@ -846,13 +843,13 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
try {
int nMinVersion = 0;
- if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
+ if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
if (nMinVersion > FEATURE_LATEST)
return DBErrors::TOO_NEW;
}
// Get cursor
- if (!m_batch.StartCursor())
+ if (!m_batch->StartCursor())
{
LogPrintf("Error getting wallet database cursor\n");
return DBErrors::CORRUPT;
@@ -864,11 +861,11 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
bool complete;
- bool ret = m_batch.ReadAtCursor(ssKey, ssValue, complete);
+ bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);
if (complete) {
break;
} else if (!ret) {
- m_batch.CloseCursor();
+ m_batch->CloseCursor();
LogPrintf("Error reading next record from wallet database\n");
return DBErrors::CORRUPT;
}
@@ -886,7 +883,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
} catch (...) {
result = DBErrors::CORRUPT;
}
- m_batch.CloseCursor();
+ m_batch->CloseCursor();
return result;
}
@@ -952,9 +949,6 @@ void MaybeCompactWalletDB()
if (fOneThread.exchange(true)) {
return;
}
- if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
- return;
- }
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
WalletDatabase& dbh = pwallet->GetDBHandle();
@@ -999,17 +993,17 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags)
bool WalletBatch::TxnBegin()
{
- return m_batch.TxnBegin();
+ return m_batch->TxnBegin();
}
bool WalletBatch::TxnCommit()
{
- return m_batch.TxnCommit();
+ return m_batch->TxnCommit();
}
bool WalletBatch::TxnAbort()
{
- return m_batch.TxnAbort();
+ return m_batch->TxnAbort();
}
bool IsWalletLoaded(const fs::path& wallet_path)
@@ -1018,20 +1012,20 @@ bool IsWalletLoaded(const fs::path& wallet_path)
}
/** Return object for accessing database at specified path. */
-std::unique_ptr<BerkeleyDatabase> CreateWalletDatabase(const fs::path& path)
+std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path)
{
std::string filename;
return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
}
/** Return object for accessing dummy database with no read/write capabilities. */
-std::unique_ptr<BerkeleyDatabase> CreateDummyWalletDatabase()
+std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase()
{
return MakeUnique<BerkeleyDatabase>();
}
/** Return object for accessing temporary in-memory database. */
-std::unique_ptr<BerkeleyDatabase> CreateMockWalletDatabase()
+std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
{
return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 61e0f19e56..7c5bf7652b 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -40,9 +40,6 @@ class CWalletTx;
class uint160;
class uint256;
-/** Backend-agnostic database type. */
-using WalletDatabase = BerkeleyDatabase;
-
/** Error statuses for the wallet database */
enum class DBErrors
{
@@ -183,12 +180,12 @@ private:
template <typename K, typename T>
bool WriteIC(const K& key, const T& value, bool fOverwrite = true)
{
- if (!m_batch.Write(key, value, fOverwrite)) {
+ if (!m_batch->Write(key, value, fOverwrite)) {
return false;
}
m_database.IncrementUpdateCounter();
if (m_database.nUpdateCounter % 1000 == 0) {
- m_batch.Flush();
+ m_batch->Flush();
}
return true;
}
@@ -196,19 +193,19 @@ private:
template <typename K>
bool EraseIC(const K& key)
{
- if (!m_batch.Erase(key)) {
+ if (!m_batch->Erase(key)) {
return false;
}
m_database.IncrementUpdateCounter();
if (m_database.nUpdateCounter % 1000 == 0) {
- m_batch.Flush();
+ m_batch->Flush();
}
return true;
}
public:
explicit WalletBatch(WalletDatabase& database, const char* pszMode = "r+", bool _fFlushOnClose = true) :
- m_batch(database, pszMode, _fFlushOnClose),
+ m_batch(database.MakeBatch(pszMode, _fFlushOnClose)),
m_database(database)
{
}
@@ -280,7 +277,7 @@ public:
//! Abort current transaction
bool TxnAbort();
private:
- BerkeleyBatch m_batch;
+ std::unique_ptr<DatabaseBatch> m_batch;
WalletDatabase& m_database;
};
@@ -294,12 +291,12 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st
bool IsWalletLoaded(const fs::path& wallet_path);
/** Return object for accessing database at specified path. */
-std::unique_ptr<BerkeleyDatabase> CreateWalletDatabase(const fs::path& path);
+std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path& path);
/** Return object for accessing dummy database with no read/write capabilities. */
-std::unique_ptr<BerkeleyDatabase> CreateDummyWalletDatabase();
+std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase();
/** Return object for accessing temporary in-memory database. */
-std::unique_ptr<BerkeleyDatabase> CreateMockWalletDatabase();
+std::unique_ptr<WalletDatabase> CreateMockWalletDatabase();
#endif // BITCOIN_WALLET_WALLETDB_H
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 8a45d81456..9f25b1ae7d 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -17,7 +17,7 @@ namespace WalletTool {
static void WalletToolReleaseWallet(CWallet* wallet)
{
wallet->WalletLogPrintf("Releasing wallet\n");
- wallet->Flush(true);
+ wallet->Close();
delete wallet;
}
@@ -133,7 +133,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
std::shared_ptr<CWallet> wallet_instance = CreateWallet(name, path);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
- wallet_instance->Flush(true);
+ wallet_instance->Close();
}
} else if (command == "info" || command == "salvage") {
if (!fs::exists(path)) {
@@ -145,7 +145,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
if (!wallet_instance) return false;
WalletShowInfo(wallet_instance.get());
- wallet_instance->Flush(true);
+ wallet_instance->Close();
} else if (command == "salvage") {
return SalvageWallet(path);
}
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index 5cddd6527e..07dd0f8f82 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -6,7 +6,7 @@
Test various backwards compatibility scenarios. Download the previous node binaries:
-contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py.
Due to a hardfork in regtest, it can't be used to sync nodes.
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index a4dc455d57..34e856c1ba 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -112,11 +112,38 @@ class ConfArgsTest(BitcoinTestFramework):
])
self.stop_node(0)
+ def test_networkactive(self):
+ self.log.info('Test -networkactive option')
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0, extra_args=['-networkactive'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.start_node(0, extra_args=['-networkactive=1'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-networkactive=0'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-nonetworkactive'])
+ self.stop_node(0)
+
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.start_node(0, extra_args=['-nonetworkactive=1'])
+ self.stop_node(0)
+
def run_test(self):
self.stop_node(0)
self.test_log_buffer()
self.test_args_log()
+ self.test_networkactive()
self.test_config_file_parser()
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
new file mode 100755
index 0000000000..c565854bb0
--- /dev/null
+++ b/test/functional/feature_settings.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-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.
+"""Test various command line arguments and configuration file parameters."""
+
+import json
+
+from pathlib import Path
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.test_node import ErrorMatch
+from test_framework.util import assert_equal
+
+
+class SettingsTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def run_test(self):
+ node, = self.nodes
+ settings = Path(node.datadir, self.chain, "settings.json")
+ conf = Path(node.datadir, "bitcoin.conf")
+
+ # Assert empty settings file was created
+ self.stop_node(0)
+ with settings.open() as fp:
+ assert_equal(json.load(fp), {})
+
+ # Assert settings are parsed and logged
+ with settings.open("w") as fp:
+ json.dump({"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]}, fp)
+ with node.assert_debug_log(expected_msgs=[
+ 'Setting file arg: string = "string"',
+ 'Setting file arg: num = 5',
+ 'Setting file arg: bool = true',
+ 'Setting file arg: null = null',
+ 'Setting file arg: list = [6,7]']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Assert settings are unchanged after shutdown
+ with settings.open() as fp:
+ assert_equal(json.load(fp), {"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]})
+
+ # Test invalid json
+ with settings.open("w") as fp:
+ fp.write("invalid json")
+ node.assert_start_raises_init_error(expected_msg='Unable to parse settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid json object
+ with settings.open("w") as fp:
+ fp.write('"string"')
+ node.assert_start_raises_init_error(expected_msg='Found non-object value "string" in settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid settings file containing duplicate keys
+ with settings.open("w") as fp:
+ fp.write('{"key": 1, "key": 2}')
+ node.assert_start_raises_init_error(expected_msg='Found duplicate key key in settings file', match=ErrorMatch.PARTIAL_REGEX)
+
+ # Test invalid settings file is ignored with command line -nosettings
+ with node.assert_debug_log(expected_msgs=['Command-line arg: settings=false']):
+ self.start_node(0, extra_args=["-nosettings"])
+ self.stop_node(0)
+
+ # Test invalid settings file is ignored with config file -nosettings
+ with conf.open('a') as conf:
+ conf.write('nosettings=1\n')
+ with node.assert_debug_log(expected_msgs=['Config file arg: [regtest] settings=false']):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Test alternate settings path
+ altsettings = Path(node.datadir, "altsettings.json")
+ 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.stop_node(0)
+
+
+if __name__ == '__main__':
+ SettingsTest().main()
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index 999399dec0..31fb751904 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -8,7 +8,7 @@ NOTE: The test is designed to prevent cases when compatibility is broken acciden
In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
Download node binaries:
-contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
Only v0.15.2 is required by this test. The rest is used in other backwards compatibility tests.
"""
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index 5b7216b253..542d24f4be 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -69,14 +69,19 @@ class MempoolPackagesTest(BitcoinTestFramework):
fee = Decimal("0.0001")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
+ witness_chain = []
for i in range(MAX_ANCESTORS):
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1)
value = sent_value
chain.append(txid)
+ # We need the wtxids to check P2P announcements
+ fulltx = self.nodes[0].getrawtransaction(txid)
+ witnesstx = self.nodes[0].decoderawtransaction(fulltx, True)
+ witness_chain.append(witnesstx['hash'])
# Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
- self.nodes[0].p2p.wait_for_broadcast(chain)
+ self.nodes[0].p2p.wait_for_broadcast(witness_chain)
# Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
# count and fees should look correct
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index 0865c232e8..27e6b669f6 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -52,15 +52,15 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.log.info('Check that txs from rpc are not rejected and relayed to other peers')
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True)
txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid']
- with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=1'.format(txid)]):
+ with self.nodes[0].assert_debug_log(['received getdata for: wtx {} peer=1'.format(txid)]):
self.nodes[0].sendrawtransaction(sigtx)
self.nodes[0].p2p.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
- self.log.info('Check that txs from whitelisted peers are not rejected and relayed to others')
- self.log.info("Restarting node 0 with whitelist permission and blocksonly")
+ self.log.info('Check that txs from forcerelay peers are not rejected and relayed to others')
+ self.log.info("Restarting node 0 with forcerelay permission and blocksonly")
self.restart_node(0, ["-persistmempool=0", "-whitelist=127.0.0.1", "-whitelistforcerelay", "-blocksonly"])
- assert_equal(self.nodes[0].getrawmempool(),[])
+ assert_equal(self.nodes[0].getrawmempool(), [])
first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
peer_1_info = self.nodes[0].getpeerinfo()[0]
@@ -72,14 +72,15 @@ class P2PBlocksOnly(BitcoinTestFramework):
assert_equal(self.nodes[0].testmempoolaccept([sigtx])[0]['allowed'], True)
txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid']
- self.log.info('Check that the tx from whitelisted first_peer is relayed to others (ie.second_peer)')
+ self.log.info('Check that the tx from forcerelay first_peer is relayed to others (ie.second_peer)')
with self.nodes[0].assert_debug_log(["received getdata"]):
first_peer.send_message(msg_tx(FromHex(CTransaction(), sigtx)))
- self.log.info('Check that the whitelisted peer is still connected after sending the transaction')
+ self.log.info('Check that the forcerelay peer is still connected after sending the transaction')
assert_equal(first_peer.is_connected, True)
second_peer.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
- self.log.info("Whitelisted peer's transaction is accepted and relayed")
+ self.log.info("Forcerelay peer's transaction is accepted and relayed")
+
if __name__ == '__main__':
P2PBlocksOnly().main()
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index f939ea965c..73afe9adc4 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -7,7 +7,7 @@
from decimal import Decimal
import time
-from test_framework.messages import MSG_TX, msg_feefilter
+from test_framework.messages import MSG_TX, MSG_WTX, msg_feefilter
from test_framework.mininode import mininode_lock, P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
@@ -45,7 +45,7 @@ class TestP2PConn(P2PInterface):
def on_inv(self, message):
for i in message.inv:
- if (i.type == MSG_TX):
+ if (i.type == MSG_TX) or (i.type == MSG_WTX):
self.txinvs.append(hashToHex(i.hash))
def clear_invs(self):
diff --git a/test/functional/p2p_ibd_txrelay.py b/test/functional/p2p_ibd_txrelay.py
new file mode 100755
index 0000000000..c3e758b021
--- /dev/null
+++ b/test/functional/p2p_ibd_txrelay.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# 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.
+"""Test fee filters during and after IBD."""
+
+from decimal import Decimal
+
+from test_framework.messages import COIN
+from test_framework.test_framework import BitcoinTestFramework
+
+MAX_FEE_FILTER = Decimal(9170997) / COIN
+NORMAL_FEE_FILTER = Decimal(100) / COIN
+
+
+class P2PIBDTxRelayTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.extra_args = [
+ ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
+ ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
+ ]
+
+ def run_test(self):
+ self.log.info("Check that nodes set minfilter to MAX_MONEY while still in IBD")
+ for node in self.nodes:
+ assert node.getblockchaininfo()['initialblockdownload']
+ self.wait_until(lambda: all(peer['minfeefilter'] == MAX_FEE_FILTER for peer in node.getpeerinfo()))
+
+ # Come out of IBD by generating a block
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ self.log.info("Check that nodes reset minfilter after coming out of IBD")
+ for node in self.nodes:
+ assert not node.getblockchaininfo()['initialblockdownload']
+ self.wait_until(lambda: all(peer['minfeefilter'] == NORMAL_FEE_FILTER for peer in node.getpeerinfo()))
+
+
+if __name__ == '__main__':
+ P2PIBDTxRelayTest().main()
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index 3b3dbd08f2..fe6e236fc4 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -26,7 +26,7 @@ from test_framework.util import (
wait_until,
)
-banscore = 10
+DISCOURAGEMENT_THRESHOLD = 100
class CLazyNode(P2PInterface):
@@ -65,12 +65,13 @@ class CLazyNode(P2PInterface):
# Node that never sends a version. We'll use this to send a bunch of messages
# anyway, and eventually get disconnected.
-class CNodeNoVersionBan(CLazyNode):
- # send a bunch of veracks without sending a message. This should get us disconnected.
- # NOTE: implementation-specific check here. Remove if bitcoind ban behavior changes
+class CNodeNoVersionMisbehavior(CLazyNode):
+ # Send enough veracks without a message to reach the peer discouragement
+ # threshold. This should get us disconnected. NOTE: implementation-specific
+ # test; update if our discouragement policy for peer misbehavior changes.
def on_open(self):
super().on_open()
- for i in range(banscore):
+ for _ in range(DISCOURAGEMENT_THRESHOLD):
self.send_message(msg_verack())
# Node that never sends a version. This one just sits idle and hopes to receive
@@ -106,10 +107,10 @@ class P2PVersionStore(P2PInterface):
class P2PLeakTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [['-banscore=' + str(banscore)]]
def run_test(self):
- no_version_bannode = self.nodes[0].add_p2p_connection(CNodeNoVersionBan(), send_version=False, wait_for_verack=False)
+ no_version_disconnect_node = self.nodes[0].add_p2p_connection(
+ CNodeNoVersionMisbehavior(), send_version=False, wait_for_verack=False)
no_version_idlenode = self.nodes[0].add_p2p_connection(CNodeNoVersionIdle(), send_version=False, wait_for_verack=False)
no_verack_idlenode = self.nodes[0].add_p2p_connection(CNodeNoVerackIdle(), wait_for_verack=False)
@@ -117,7 +118,7 @@ class P2PLeakTest(BitcoinTestFramework):
# verack, since we never sent one
no_verack_idlenode.wait_for_verack()
- wait_until(lambda: no_version_bannode.ever_connected, timeout=10, lock=mininode_lock)
+ wait_until(lambda: no_version_disconnect_node.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_version_idlenode.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_verack_idlenode.version_received, timeout=10, lock=mininode_lock)
@@ -127,13 +128,13 @@ class P2PLeakTest(BitcoinTestFramework):
#Give the node enough time to possibly leak out a message
time.sleep(5)
- #This node should have been banned
- assert not no_version_bannode.is_connected
+ # Expect this node to be disconnected for misbehavior
+ assert not no_version_disconnect_node.is_connected
self.nodes[0].disconnect_p2ps()
# Make sure no unexpected messages came in
- assert no_version_bannode.unexpected_msg == False
+ assert no_version_disconnect_node.unexpected_msg == False
assert no_version_idlenode.unexpected_msg == False
assert no_verack_idlenode.unexpected_msg == False
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index d0656cdb49..32a795e345 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -108,9 +108,9 @@ class P2PPermissionsTests(BitcoinTestFramework):
block_op_true = self.nodes[0].getblock(self.nodes[0].generatetoaddress(100, ADDRESS_BCRT1_P2WSH_OP_TRUE)[0])
self.sync_all()
- self.log.debug("Create a connection from a whitelisted wallet that rebroadcasts raw txs")
+ self.log.debug("Create a connection from a forcerelay peer that rebroadcasts raw txs")
# A python mininode is needed to send the raw transaction directly. If a full node was used, it could only
- # rebroadcast via the inv-getdata mechanism. However, even for whitelisted connections, a full node would
+ # rebroadcast via the inv-getdata mechanism. However, even for forcerelay connections, a full node would
# currently not request a txid that is already in the mempool.
self.restart_node(1, extra_args=["-whitelist=forcerelay@127.0.0.1"])
p2p_rebroadcast_wallet = self.nodes[1].add_p2p_connection(P2PDataStore())
@@ -135,7 +135,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.log.debug("Check that node[1] will send the tx to node[0] even though it is already in the mempool")
connect_nodes(self.nodes[1], 0)
- with self.nodes[1].assert_debug_log(["Force relaying tx {} from whitelisted peer=0".format(txid)]):
+ with self.nodes[1].assert_debug_log(["Force relaying tx {} from peer=0".format(txid)]):
p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1])
wait_until(lambda: txid in self.nodes[0].getrawmempool())
@@ -146,7 +146,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
[tx],
self.nodes[1],
success=False,
- reject_reason='Not relaying non-mempool transaction {} from whitelisted peer=0'.format(txid),
+ reject_reason='Not relaying non-mempool transaction {} from forcerelay peer=0'.format(txid),
)
def checkpermission(self, args, expectedPermissions, whitelisted):
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 25dd765442..9915b844d1 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -25,6 +25,7 @@ from test_framework.messages import (
MSG_BLOCK,
MSG_TX,
MSG_WITNESS_FLAG,
+ MSG_WTX,
NODE_NETWORK,
NODE_WITNESS,
msg_no_witness_block,
@@ -34,6 +35,7 @@ from test_framework.messages import (
msg_tx,
msg_block,
msg_no_witness_tx,
+ msg_verack,
ser_uint256,
ser_vector,
sha256,
@@ -81,6 +83,7 @@ from test_framework.util import (
softfork_active,
hex_str_to_bytes,
assert_raises_rpc_error,
+ wait_until,
)
# The versionbit bit used to signal activation of SegWit
@@ -143,25 +146,47 @@ def test_witness_block(node, p2p, block, accepted, with_witness=True, reason=Non
class TestP2PConn(P2PInterface):
- def __init__(self):
+ def __init__(self, wtxidrelay=False):
super().__init__()
self.getdataset = set()
+ self.last_wtxidrelay = []
+ self.lastgetdata = []
+ self.wtxidrelay = wtxidrelay
# Avoid sending out msg_getdata in the mininode thread as a reply to invs.
# They are not needed and would only lead to races because we send msg_getdata out in the test thread
def on_inv(self, message):
pass
+ def on_version(self, message):
+ if self.wtxidrelay:
+ super().on_version(message)
+ else:
+ self.send_message(msg_verack())
+ self.nServices = message.nServices
+
def on_getdata(self, message):
+ self.lastgetdata = message.inv
for inv in message.inv:
self.getdataset.add(inv.hash)
- def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True):
+ def on_wtxidrelay(self, message):
+ self.last_wtxidrelay.append(message)
+
+ def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True, use_wtxid=False):
with mininode_lock:
self.last_message.pop("getdata", None)
- self.send_message(msg_inv(inv=[CInv(MSG_TX, tx.sha256)]))
+ if use_wtxid:
+ wtxid = tx.calc_sha256(True)
+ self.send_message(msg_inv(inv=[CInv(MSG_WTX, wtxid)]))
+ else:
+ self.send_message(msg_inv(inv=[CInv(MSG_TX, tx.sha256)]))
+
if success:
- self.wait_for_getdata([tx.sha256], timeout)
+ if use_wtxid:
+ self.wait_for_getdata([wtxid], timeout)
+ else:
+ self.wait_for_getdata([tx.sha256], timeout)
else:
time.sleep(timeout)
assert not self.last_message.get("getdata")
@@ -277,6 +302,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_upgrade_after_activation()
self.test_witness_sigops()
self.test_superfluous_witness()
+ self.test_wtxid_relay()
# Individual tests
@@ -1270,7 +1296,6 @@ class SegWitTest(BitcoinTestFramework):
test_transaction_acceptance(self.nodes[0], self.test_node, tx, with_witness=True, accepted=False)
# Verify that removing the witness succeeds.
- self.test_node.announce_tx_and_wait_for_getdata(tx)
test_transaction_acceptance(self.nodes[0], self.test_node, tx, with_witness=False, accepted=True)
# Now try to add extra witness data to a valid witness tx.
@@ -1297,8 +1322,6 @@ class SegWitTest(BitcoinTestFramework):
# Node will not be blinded to the transaction
self.std_node.announce_tx_and_wait_for_getdata(tx3)
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
- self.std_node.announce_tx_and_wait_for_getdata(tx3)
- test_transaction_acceptance(self.nodes[1], self.std_node, tx3, True, False, 'tx-size')
# Remove witness stuffing, instead add extra witness push on stack
tx3.vout[0] = CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))
@@ -2016,6 +2039,11 @@ class SegWitTest(BitcoinTestFramework):
# TODO: test p2sh sigop counting
+ # Cleanup and prep for next test
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+ @subtest # type: ignore
def test_superfluous_witness(self):
# Serialization of tx that puts witness flag to 3 always
def serialize_with_bogus_witness(tx):
@@ -2059,6 +2087,67 @@ class SegWitTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(['Unknown transaction optional data']):
self.nodes[0].p2p.send_and_ping(msg_bogus_tx(tx))
+ @subtest # type: ignore
+ def test_wtxid_relay(self):
+ # Use brand new nodes to avoid contamination from earlier tests
+ self.wtx_node = self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=True), services=NODE_NETWORK | NODE_WITNESS)
+ self.tx_node = self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=False), services=NODE_NETWORK | NODE_WITNESS)
+
+ # Check wtxidrelay feature negotiation message through connecting a new peer
+ def received_wtxidrelay():
+ return (len(self.wtx_node.last_wtxidrelay) > 0)
+ wait_until(received_wtxidrelay, timeout=60, lock=mininode_lock)
+
+ # Create a Segwit output from the latest UTXO
+ # and announce it to the network
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ script_pubkey = CScript([OP_0, witness_hash])
+
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey))
+ tx.rehash()
+
+ # Create a Segwit transaction
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_pubkey))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
+ tx2.rehash()
+
+ # Announce Segwit transaction with wtxid
+ # and wait for getdata
+ self.wtx_node.announce_tx_and_wait_for_getdata(tx2, use_wtxid=True)
+ with mininode_lock:
+ lgd = self.wtx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_WTX, tx2.calc_sha256(True))])
+
+ # Announce Segwit transaction from non wtxidrelay peer
+ # and wait for getdata
+ self.tx_node.announce_tx_and_wait_for_getdata(tx2, use_wtxid=False)
+ with mininode_lock:
+ lgd = self.tx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_TX|MSG_WITNESS_FLAG, tx2.sha256)])
+
+ # Send tx2 through; it's an orphan so won't be accepted
+ with mininode_lock:
+ self.tx_node.last_message.pop("getdata", None)
+ test_transaction_acceptance(self.nodes[0], self.tx_node, tx2, with_witness=True, accepted=False)
+
+ # Expect a request for parent (tx) due to use of non-WTX peer
+ self.tx_node.wait_for_getdata([tx.sha256], 60)
+ with mininode_lock:
+ lgd = self.tx_node.lastgetdata[:]
+ assert_equal(lgd, [CInv(MSG_TX|MSG_WITNESS_FLAG, tx.sha256)])
+
+ # Send tx through
+ test_transaction_acceptance(self.nodes[0], self.tx_node, tx, with_witness=False, accepted=True)
+
+ # Check tx2 is there now
+ assert_equal(tx2.hash in self.nodes[0].getrawmempool(), True)
+
if __name__ == '__main__':
SegWitTest().main()
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 10f5eea0e5..2527edc135 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -12,6 +12,7 @@ from test_framework.messages import (
FromHex,
MSG_TX,
MSG_TYPE_MASK,
+ MSG_WTX,
msg_inv,
msg_notfound,
)
@@ -36,7 +37,7 @@ class TestP2PConn(P2PInterface):
def on_getdata(self, message):
for i in message.inv:
- if i.type & MSG_TYPE_MASK == MSG_TX:
+ if i.type & MSG_TYPE_MASK == MSG_TX or i.type & MSG_TYPE_MASK == MSG_WTX:
self.tx_getdata_count += 1
@@ -44,12 +45,13 @@ class TestP2PConn(P2PInterface):
GETDATA_TX_INTERVAL = 60 # seconds
MAX_GETDATA_RANDOM_DELAY = 2 # seconds
INBOUND_PEER_TX_DELAY = 2 # seconds
+TXID_RELAY_DELAY = 2 # seconds
MAX_GETDATA_IN_FLIGHT = 100
TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10
# Python test constants
NUM_INBOUND = 10
-MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY
+MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
class TxDownloadTest(BitcoinTestFramework):
@@ -63,7 +65,7 @@ class TxDownloadTest(BitcoinTestFramework):
txid = 0xdeadbeef
self.log.info("Announce the txid from each incoming peer to node 0")
- msg = msg_inv([CInv(t=MSG_TX, h=txid)])
+ msg = msg_inv([CInv(t=MSG_WTX, h=txid)])
for p in self.nodes[0].p2ps:
p.send_and_ping(msg)
@@ -135,13 +137,13 @@ class TxDownloadTest(BitcoinTestFramework):
with mininode_lock:
p.tx_getdata_count = 0
- p.send_message(msg_inv([CInv(t=MSG_TX, h=i) for i in txids]))
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=i) for i in txids]))
wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT, lock=mininode_lock)
with mininode_lock:
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT)
self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request")
- p.send_message(msg_notfound(vec=[CInv(t=MSG_TX, h=txids[0])]))
+ p.send_message(msg_notfound(vec=[CInv(t=MSG_WTX, h=txids[0])]))
wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10, lock=mininode_lock)
with mininode_lock:
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1)
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index c323168848..71b0b0f63a 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test processing of unrequested blocks.
-Setup: two nodes, node0+node1, not connected to each other. Node1 will have
+Setup: two nodes, node0 + node1, not connected to each other. Node1 will have
nMinimumChainWork set to 0x10, so it won't process low-work unrequested blocks.
We have one P2PInterface connection to node0 called test_node, and one to node1
@@ -71,18 +71,10 @@ class AcceptBlockTest(BitcoinTestFramework):
self.extra_args = [[], ["-minimumchainwork=0x10"]]
def setup_network(self):
- # Node0 will be used to test behavior of processing unrequested blocks
- # from peers which are not whitelisted, while Node1 will be used for
- # the whitelisted case.
- # Node2 will be used for non-whitelisted peers to test the interaction
- # with nMinimumChainWork.
self.setup_nodes()
def run_test(self):
- # Setup the p2p connections
- # test_node connects to node0 (not whitelisted)
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
- # min_work_node connects to node1 (whitelisted)
min_work_node = self.nodes[1].add_p2p_connection(P2PInterface())
# 1. Have nodes mine a block (leave IBD)
@@ -226,7 +218,7 @@ class AcceptBlockTest(BitcoinTestFramework):
self.nodes[0].getblock(all_blocks[286].hash)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
- self.log.info("Successfully reorged to longer chain from non-whitelisted peer")
+ self.log.info("Successfully reorged to longer chain")
# 8. Create a chain which is invalid at a height longer than the
# current chain, but which has more blocks on top of that
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index ca26152e7e..3336246c8b 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -102,12 +102,14 @@ class NetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
- self.nodes[0].setnetworkactive(state=False)
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
+ self.nodes[0].setnetworkactive(state=False)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False)
# Wait a bit for all sockets to close
wait_until(lambda: self.nodes[0].getnetworkinfo()['connections'] == 0, timeout=3)
- self.nodes[0].setnetworkactive(state=True)
+ with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
+ self.nodes[0].setnetworkactive(state=True)
self.log.info('Connect nodes both way')
connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[1], 0)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 14cad3d1b8..23b5e647d6 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -424,11 +424,12 @@ class RawTransactionsTest(BitcoinTestFramework):
####################################
# 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 = ToHex(tx)
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
- assert_equal(decrawtx['version'], -0x80000000)
+ assert_equal(decrawtx['version'], 0x80000000)
# Test the maximum transaction version number that fits in a signed 32-bit integer.
tx = CTransaction()
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index eb1244035f..2462a9a6db 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -31,7 +31,7 @@ from test_framework.siphash import siphash256
from test_framework.util import hex_str_to_bytes, assert_equal
MIN_VERSION_SUPPORTED = 60001
-MY_VERSION = 70014 # past bip-31 for ping/pong
+MY_VERSION = 70016 # past wtxid relay
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
@@ -59,6 +59,7 @@ MSG_TX = 1
MSG_BLOCK = 2
MSG_FILTERED_BLOCK = 3
MSG_CMPCT_BLOCK = 4
+MSG_WTX = 5
MSG_WITNESS_FLAG = 1 << 30
MSG_TYPE_MASK = 0xffffffff >> 2
@@ -207,17 +208,19 @@ class CAddress:
self.ip = "0.0.0.0"
self.port = 0
- def deserialize(self, f, with_time=True):
+ def deserialize(self, f, *, with_time=True):
if with_time:
+ # VERSION messages serialize CAddress objects without time
self.time = struct.unpack("<i", f.read(4))[0]
self.nServices = struct.unpack("<Q", f.read(8))[0]
self.pchReserved = f.read(12)
self.ip = socket.inet_ntoa(f.read(4))
self.port = struct.unpack(">H", f.read(2))[0]
- def serialize(self, with_time=True):
+ def serialize(self, *, with_time=True):
r = b""
if with_time:
+ # VERSION messages serialize CAddress objects without time
r += struct.pack("<i", self.time)
r += struct.pack("<Q", self.nServices)
r += self.pchReserved
@@ -240,7 +243,8 @@ class CInv:
MSG_TX | MSG_WITNESS_FLAG: "WitnessTx",
MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock",
MSG_FILTERED_BLOCK: "filtered Block",
- 4: "CompactBlock"
+ 4: "CompactBlock",
+ 5: "WTX",
}
def __init__(self, t=0, h=0):
@@ -261,6 +265,9 @@ class CInv:
return "CInv(type=%s hash=%064x)" \
% (self.typemap[self.type], self.hash)
+ def __eq__(self, other):
+ return isinstance(other, CInv) and self.hash == other.hash and self.type == other.type
+
class CBlockLocator:
__slots__ = ("nVersion", "vHave")
@@ -973,10 +980,10 @@ class msg_version:
self.nServices = struct.unpack("<Q", f.read(8))[0]
self.nTime = struct.unpack("<q", f.read(8))[0]
self.addrTo = CAddress()
- self.addrTo.deserialize(f, False)
+ self.addrTo.deserialize(f, with_time=False)
self.addrFrom = CAddress()
- self.addrFrom.deserialize(f, False)
+ self.addrFrom.deserialize(f, with_time=False)
self.nNonce = struct.unpack("<Q", f.read(8))[0]
self.strSubVer = deser_string(f)
@@ -996,8 +1003,8 @@ class msg_version:
r += struct.pack("<i", self.nVersion)
r += struct.pack("<Q", self.nServices)
r += struct.pack("<q", self.nTime)
- r += self.addrTo.serialize(False)
- r += self.addrFrom.serialize(False)
+ r += self.addrTo.serialize(with_time=False)
+ r += self.addrFrom.serialize(with_time=False)
r += struct.pack("<Q", self.nNonce)
r += ser_string(self.strSubVer)
r += struct.pack("<i", self.nStartingHeight)
@@ -1122,6 +1129,22 @@ class msg_tx:
def __repr__(self):
return "msg_tx(tx=%s)" % (repr(self.tx))
+class msg_wtxidrelay:
+ __slots__ = ()
+ msgtype = b"wtxidrelay"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_wtxidrelay()"
+
class msg_no_witness_tx(msg_tx):
__slots__ = ()
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index e6da33763d..f68c1a9ddd 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -59,6 +59,8 @@ from test_framework.messages import (
MSG_TYPE_MASK,
msg_verack,
msg_version,
+ MSG_WTX,
+ msg_wtxidrelay,
NODE_NETWORK,
NODE_WITNESS,
sha256,
@@ -96,6 +98,7 @@ MESSAGEMAP = {
b"tx": msg_tx,
b"verack": msg_verack,
b"version": msg_version,
+ b"wtxidrelay": msg_wtxidrelay,
}
MAGIC_BYTES = {
@@ -356,6 +359,7 @@ class P2PInterface(P2PConnection):
def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass
def on_tx(self, message): pass
+ def on_wtxidrelay(self, message): pass
def on_inv(self, message):
want = msg_getdata()
@@ -373,6 +377,8 @@ class P2PInterface(P2PConnection):
def on_version(self, message):
assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED)
+ if message.nVersion >= 70016:
+ self.send_message(msg_wtxidrelay())
self.send_message(msg_verack())
self.nServices = message.nServices
@@ -654,7 +660,7 @@ class P2PTxInvStore(P2PInterface):
super().on_inv(message) # Send getdata in response.
# Store how many times invs have been received for each tx.
for i in message.inv:
- if i.type == MSG_TX:
+ if (i.type == MSG_TX) or (i.type == MSG_WTX):
# save txid
self.tx_invs_received[i.hash] += 1
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 9d9e065158..8d402d4888 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -31,6 +31,7 @@ from .util import (
disconnect_nodes,
get_datadir_path,
initialize_datadir,
+ wait_until,
)
@@ -602,6 +603,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.sync_blocks(nodes)
self.sync_mempools(nodes)
+ def wait_until(self, test_function, timeout=60, lock=None):
+ return wait_until(test_function, timeout=timeout, lock=lock, timeout_factor=self.options.timeout_factor)
+
# Private helper methods. These should not be accessed by the subclass test scripts.
def _start_logging(self):
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 867a9909a8..95c2b7c5ec 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -243,11 +243,13 @@ BASE_SCRIPTS = [
'p2p_permissions.py',
'feature_blocksdir.py',
'feature_config_args.py',
+ 'feature_settings.py',
'rpc_getdescriptorinfo.py',
'rpc_getpeerinfo_banscore_deprecation.py',
'rpc_help.py',
'feature_help.py',
'feature_shutdown.py',
+ 'p2p_ibd_txrelay.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 8962362276..81382d94ad 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -119,7 +119,7 @@ class WalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
self.nodes[2].lockunspent(False, [unspent_0])
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
+ assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
assert_equal([unspent_0], self.nodes[2].listlockunspent())
self.nodes[2].lockunspent(True, [unspent_0])
assert_equal(len(self.nodes[2].listlockunspent()), 0)
@@ -363,6 +363,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
# General checks for errors from incorrect inputs
+ # This will raise an exception because the amount is negative
+ assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
+
# This will raise an exception because the amount type is wrong
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
@@ -590,7 +593,7 @@ class WalletTest(BitcoinTestFramework):
node0_balance = self.nodes[0].getbalance()
# With walletrejectlongchains we will not create the tx and store it in our wallet.
- assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
+ assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
# Verify nothing new in wallet
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py
index ba1e494d9a..6bfb468823 100755
--- a/test/functional/wallet_dump.py
+++ b/test/functional/wallet_dump.py
@@ -202,5 +202,10 @@ class WalletDumpTest(BitcoinTestFramework):
result = self.nodes[0].getaddressinfo(multisig_addr)
assert result['ismine']
+ self.log.info('Check that wallet is flushed')
+ with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20):
+ self.nodes[0].getnewaddress()
+
+
if __name__ == '__main__':
WalletDumpTest().main()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index 6cd82ad250..4509c1e0b2 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -13,6 +13,7 @@ from test_framework.util import (
assert_greater_than_or_equal,
)
+
class WalletEncryptionTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -72,20 +73,25 @@ class WalletEncryptionTest(BitcoinTestFramework):
# Test timeout bounds
assert_raises_rpc_error(-8, "Timeout cannot be negative.", self.nodes[0].walletpassphrase, passphrase2, -10)
- # Check the timeout
- # Check a time less than the limit
+
+ self.log.info('Check a timeout less than the limit')
MAX_VALUE = 100000000
expected_time = int(time.time()) + MAX_VALUE - 600
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600)
+ # give buffer for walletpassphrase, since it iterates over all crypted keys
+ expected_time_with_buffer = time.time() + MAX_VALUE - 600
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
- # Check a time greater than the limit
+ assert_greater_than(expected_time_with_buffer, actual_time)
+
+ self.log.info('Check a timeout greater than the limit')
expected_time = int(time.time()) + MAX_VALUE - 1
self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000)
+ expected_time_with_buffer = time.time() + MAX_VALUE
actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
assert_greater_than_or_equal(actual_time, expected_time)
- assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
+ assert_greater_than(expected_time_with_buffer, actual_time)
+
if __name__ == '__main__':
WalletEncryptionTest().main()
diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py
index 0c67982bbe..dbf853b35c 100755
--- a/test/functional/wallet_fallbackfee.py
+++ b/test/functional/wallet_fallbackfee.py
@@ -22,7 +22,7 @@ class WalletRBFTest(BitcoinTestFramework):
# test sending a tx with disabled fallback fee (must fail)
self.restart_node(0, extra_args=["-fallbackfee=0"])
- assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
+ assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})))
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index fc5d653a91..2d982edef8 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -146,6 +146,14 @@ class ImportDescriptorsTest(BitcoinTestFramework):
ismine=True,
solvable=True)
+ # Check persistence of data and that loading works correctly
+ w1.unloadwallet()
+ self.nodes[1].loadwallet('w1')
+ test_address(w1,
+ key.p2sh_p2wpkh_addr,
+ ismine=True,
+ solvable=True)
+
# # Test importing of a multisig descriptor
key1 = get_generate_key()
key2 = get_generate_key()
@@ -370,6 +378,10 @@ class ImportDescriptorsTest(BitcoinTestFramework):
self.sync_all()
assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
+ # Make sure that descriptor wallets containing multiple xpubs in a single descriptor load correctly
+ wmulti_pub.unloadwallet()
+ self.nodes[1].loadwallet('wmulti_pub')
+
self.log.info("Multisig with distributed keys")
self.nodes[1].createwallet(wallet_name="wmulti_priv1", descriptors=True)
wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index cc2139a027..1a76f65215 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -6,7 +6,7 @@
Test upgradewallet RPC. Download node binaries:
-contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+contrib/devtools/previous_release.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py
"""
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
index cb33dd6232..3d0ac7f995 100644
--- a/test/sanitizer_suppressions/tsan
+++ b/test/sanitizer_suppressions/tsan
@@ -23,6 +23,7 @@ race:LoadWallet
race:WalletBatch::WriteHDChain
race:BerkeleyBatch
race:BerkeleyDatabase
+race:DatabaseBatch
race:zmq::*
race:bitcoin-qt
# deadlock (TODO fix)