aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml35
-rwxr-xr-x.travis/lint_06_script.sh2
-rw-r--r--depends/Makefile2
-rw-r--r--depends/funcs.mk12
-rw-r--r--doc/README.md1
-rw-r--r--doc/bips.md8
-rw-r--r--doc/descriptors.md19
-rw-r--r--doc/psbt.md10
-rw-r--r--doc/release-notes.md7
-rw-r--r--doc/shared-libraries.md4
-rw-r--r--doc/travis-ci.md42
-rw-r--r--src/Makefile.am2
-rw-r--r--src/core_io.h6
-rw-r--r--src/core_read.cpp28
-rw-r--r--src/init.cpp26
-rw-r--r--src/interfaces/chain.cpp13
-rw-r--r--src/interfaces/chain.h5
-rw-r--r--src/interfaces/wallet.cpp15
-rw-r--r--src/net_processing.cpp12
-rw-r--r--src/psbt.cpp166
-rw-r--r--src/psbt.h51
-rw-r--r--src/qt/forms/receivecoinsdialog.ui8
-rw-r--r--src/qt/receivecoinsdialog.cpp6
-rw-r--r--src/rpc/rawtransaction.cpp169
-rw-r--r--src/rpc/util.cpp29
-rw-r--r--src/script/descriptor.cpp2
-rw-r--r--src/script/sign.cpp8
-rw-r--r--src/script/sign.h2
-rw-r--r--src/test/descriptor_tests.cpp4
-rw-r--r--src/validation.cpp2
-rw-r--r--src/validationinterface.cpp7
-rw-r--r--src/validationinterface.h3
-rw-r--r--src/wallet/init.cpp3
-rw-r--r--src/wallet/rpcdump.cpp14
-rw-r--r--src/wallet/rpcwallet.cpp19
-rw-r--r--src/wallet/test/wallet_tests.cpp8
-rw-r--r--src/wallet/wallet.cpp121
-rw-r--r--src/wallet/wallet.h25
-rwxr-xr-xtest/functional/feature_config_args.py4
-rwxr-xr-xtest/functional/interface_rpc.py17
-rwxr-xr-xtest/functional/p2p_compactblocks.py207
-rw-r--r--test/functional/test_framework/authproxy.py33
-rwxr-xr-xtest/functional/test_framework/test_node.py2
-rw-r--r--test/functional/test_framework/util.py4
-rwxr-xr-xtest/functional/wallet_importmulti.py5
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py6
46 files changed, 624 insertions, 550 deletions
diff --git a/.travis.yml b/.travis.yml
index ef8da16936..21ba5461dd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,30 @@
+# The test build matrix (stage: test) is constructed to test a wide range of
+# configurations, rather than a single pass/fail. This helps to catch build
+# failures and logic errors that present on platforms other than the ones the
+# author has tested.
+#
+# Some builders use the dependency-generator in `./depends`, rather than using
+# apt-get to install build dependencies. This guarantees that the tester is
+# using the same versions as Gitian, so the build results are nearly identical
+# to what would be found in a final release.
+#
+# In order to avoid rebuilding all dependencies for each build, the binaries
+# are cached and re-used when possible. Changes in the dependency-generator
+# will trigger cache-invalidation and rebuilds as necessary.
+#
+# These caches can be manually removed if necessary. This is one of the very
+# few manual operations that is possible with Travis, and it can be done by a
+# Bitcoin Core GitHub member via the Travis web interface [0].
+#
+# Travis CI uploads the cache after the script phase of the build [1].
+# However, the build is terminated without saving the chache if it takes over
+# 50 minutes [2]. Thus, if we spent too much time in early build stages, fail
+# with an error and save the cache.
+#
+# [0] https://travis-ci.org/bitcoin/bitcoin/caches
+# [1] https://docs.travis-ci.com/user/caching/#build-phases
+# [2] https://docs.travis-ci.com/user/customizing-the-build#build-timeouts
+
dist: xenial
os: linux
language: minimal
@@ -26,6 +53,7 @@ env:
- SDK_URL=https://bitcoincore.org/depends-sources/sdks
- WINEDEBUG=fixme-all
- DOCKER_PACKAGES="build-essential libtool autotools-dev automake pkg-config bsdmainutils curl git ca-certificates ccache"
+ - CACHE_ERR_MSG="Error! Initial build successful, but not enough time remains to run later build stages and tests. Please manually re-run this job by using the travis restart button or asking a bitcoin maintainer to restart. The next run should not time out because the build cache has been saved."
before_install:
- set -o errexit; source .travis/test_03_before_install.sh
install:
@@ -33,8 +61,11 @@ install:
before_script:
- set -o errexit; source .travis/test_05_before_script.sh
script:
- - if [ $SECONDS -gt 1200 ]; then set +o errexit; echo "Travis early exit to cache current state"; false; else set -o errexit; source .travis/test_06_script_a.sh; fi
- - if [ $SECONDS -gt 1800 ]; then set +o errexit; echo "Travis early exit to cache current state"; false; else set -o errexit; source .travis/test_06_script_b.sh; fi
+ - export CONTINUE=1
+ - if [ $SECONDS -gt 1200 ]; then export CONTINUE=0; fi # Likely the depends build took very long
+ - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_a.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi
+ - if [ $SECONDS -gt 1800 ]; then export CONTINUE=0; fi # Likely the build took very long
+ - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_b.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi
after_script:
- echo $TRAVIS_COMMIT_RANGE
- echo $TRAVIS_COMMIT_LOG
diff --git a/.travis/lint_06_script.sh b/.travis/lint_06_script.sh
index 1a1693a8ce..eeebc80ec0 100755
--- a/.travis/lint_06_script.sh
+++ b/.travis/lint_06_script.sh
@@ -20,6 +20,6 @@ test/lint/lint-all.sh
if [ "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_EVENT_TYPE" = "cron" ]; then
git log --merges --before="2 days ago" -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit
- while read -r LINE; do travis_retry gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys &&
+ travis_retry gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys $(<contrib/verify-commits/trusted-keys) &&
./contrib/verify-commits/verify-commits.py --clean-merge=2;
fi
diff --git a/depends/Makefile b/depends/Makefile
index 50cc77ddeb..dc2a1e626c 100644
--- a/depends/Makefile
+++ b/depends/Makefile
@@ -192,4 +192,6 @@ download-win:
@$(MAKE) -s HOST=x86_64-w64-mingw32 download-one
download: download-osx download-linux download-win
+$(foreach package,$(all_packages),$(eval $(call ext_add_stages,$(package))))
+
.PHONY: install cached clean clean-all download-one download-osx download-linux download-win download check-packages check-sources
diff --git a/depends/funcs.mk b/depends/funcs.mk
index 9ff68f6f3c..8f03c5f37a 100644
--- a/depends/funcs.mk
+++ b/depends/funcs.mk
@@ -171,13 +171,13 @@ $($(1)_extracted): | $($(1)_fetched)
$(AT)mkdir -p $$(@D)
$(AT)cd $$(@D); $(call $(1)_extract_cmds,$(1))
$(AT)touch $$@
-$($(1)_preprocessed): | $($(1)_dependencies) $($(1)_extracted)
+$($(1)_preprocessed): | $($(1)_extracted)
$(AT)echo Preprocessing $(1)...
$(AT)mkdir -p $$(@D) $($(1)_patch_dir)
$(AT)$(foreach patch,$($(1)_patches),cd $(PATCHES_PATH)/$(1); cp $(patch) $($(1)_patch_dir) ;)
$(AT)cd $$(@D); $(call $(1)_preprocess_cmds, $(1))
$(AT)touch $$@
-$($(1)_configured): | $($(1)_preprocessed)
+$($(1)_configured): | $($(1)_dependencies) $($(1)_preprocessed)
$(AT)echo Configuring $(1)...
$(AT)rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), tar --no-same-owner -xf $($(package)_cached); )
$(AT)mkdir -p $$(@D)
@@ -214,6 +214,14 @@ $(1): | $($(1)_cached_checksum)
endef
+stages = fetched extracted preprocessed configured built staged postprocessed cached cached_checksum
+
+define ext_add_stages
+$(foreach stage,$(stages),
+ $(1)_$(stage): $($(1)_$(stage))
+ .PHONY: $(1)_$(stage))
+endef
+
# These functions create the build targets for each package. They must be
# broken down into small steps so that each part is done for all packages
# before moving on to the next step. Otherwise, a package's info
diff --git a/doc/README.md b/doc/README.md
index 8876ffdd72..b4fa933c8e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -57,7 +57,6 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th
- [Source Code Documentation (External Link)](https://dev.visucore.com/bitcoin/doxygen/)
- [Translation Process](translation_process.md)
- [Translation Strings Policy](translation_strings_policy.md)
-- [Travis CI](travis-ci.md)
- [JSON-RPC Interface](JSON-RPC-interface.md)
- [Unauthenticated REST Interface](REST-interface.md)
- [Shared Libraries](shared-libraries.md)
diff --git a/doc/bips.md b/doc/bips.md
index 76edc94c29..eb24ce6f66 100644
--- a/doc/bips.md
+++ b/doc/bips.md
@@ -1,4 +1,4 @@
-BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.17.0**):
+BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.18.0**):
* [`BIP 9`](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki): The changes allowing multiple soft-forks to be deployed in parallel have been implemented since **v0.12.1** ([PR #7575](https://github.com/bitcoin/bitcoin/pull/7575))
* [`BIP 11`](https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki): Multisig outputs are standard since **v0.6.0** ([PR #669](https://github.com/bitcoin/bitcoin/pull/669)).
@@ -15,11 +15,11 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.17.0**):
* [`BIP 35`](https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki): The 'mempool' protocol message (and the protocol version bump to 60002) has been implemented since **v0.7.0** ([PR #1641](https://github.com/bitcoin/bitcoin/pull/1641)).
* [`BIP 37`](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki): The bloom filtering for transaction relaying, partial Merkle trees for blocks, and the protocol version bump to 70001 (enabling low-bandwidth SPV clients) has been implemented since **v0.8.0** ([PR #1795](https://github.com/bitcoin/bitcoin/pull/1795)).
* [`BIP 42`](https://github.com/bitcoin/bips/blob/master/bip-0042.mediawiki): The bug that would have caused the subsidy schedule to resume after block 13440000 was fixed in **v0.9.2** ([PR #3842](https://github.com/bitcoin/bitcoin/pull/3842)).
-* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting *v0.17.0*, whether to send reject messages can be configured with the `-enablebip61` option.
+* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting **v0.17.0**, whether to send reject messages can be configured with the `-enablebip61` option, and support is deprecated as of **v0.18.0**.
* [`BIP 65`](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki): The CHECKLOCKTIMEVERIFY softfork was merged in **v0.12.0** ([PR #6351](https://github.com/bitcoin/bitcoin/pull/6351)), and backported to **v0.11.2** and **v0.10.4**. Mempool-only CLTV was added in [PR #6124](https://github.com/bitcoin/bitcoin/pull/6124).
* [`BIP 66`](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki): The strict DER rules and associated version 3 blocks have been implemented since **v0.10.0** ([PR #5713](https://github.com/bitcoin/bitcoin/pull/5713)).
* [`BIP 68`](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki): Sequence locks have been implemented as of **v0.12.1** ([PR #7184](https://github.com/bitcoin/bitcoin/pull/7184)), and have been activated since *block 419328*.
-* [`BIP 70`](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki) [`71`](https://github.com/bitcoin/bips/blob/master/bip-0071.mediawiki) [`72`](https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki): Payment Protocol support has been available in Bitcoin Core GUI since **v0.9.0** ([PR #5216](https://github.com/bitcoin/bitcoin/pull/5216)).
+* [`BIP 70`](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki) [`71`](https://github.com/bitcoin/bips/blob/master/bip-0071.mediawiki) [`72`](https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki): Payment Protocol support has been available in Bitcoin Core GUI since **v0.9.0** ([PR #5216](https://github.com/bitcoin/bitcoin/pull/5216)). Support can be optionally disabled at build time since **v0.18.0** ([PR 14451](https://github.com/bitcoin/bitcoin/pull/14451)).
* [`BIP 90`](https://github.com/bitcoin/bips/blob/master/bip-0090.mediawiki): Trigger mechanism for activation of BIPs 34, 65, and 66 has been simplified to block height checks since **v0.14.0** ([PR #8391](https://github.com/bitcoin/bitcoin/pull/8391)).
* [`BIP 111`](https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki): `NODE_BLOOM` service bit added, and enforced for all peer versions as of **v0.13.0** ([PR #6579](https://github.com/bitcoin/bitcoin/pull/6579) and [PR #6641](https://github.com/bitcoin/bitcoin/pull/6641)).
* [`BIP 112`](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki): The CHECKSEQUENCEVERIFY opcode has been implemented since **v0.12.1** ([PR #7524](https://github.com/bitcoin/bitcoin/pull/7524)) and has been activated since *block 419328*.
@@ -33,7 +33,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.17.0**):
* [`BIP 145`](https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki): getblocktemplate updates for Segregated Witness as of **v0.13.0** ([PR 8149](https://github.com/bitcoin/bitcoin/pull/8149)).
* [`BIP 147`](https://github.com/bitcoin/bips/blob/master/bip-0147.mediawiki): NULLDUMMY softfork as of **v0.13.1** ([PR 8636](https://github.com/bitcoin/bitcoin/pull/8636) and [PR 8937](https://github.com/bitcoin/bitcoin/pull/8937)).
* [`BIP 152`](https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki): Compact block transfer and related optimizations are used as of **v0.13.0** ([PR 8068](https://github.com/bitcoin/bitcoin/pull/8068)).
-* [`BIP 159`](https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki): NODE_NETWORK_LIMITED service bit [signaling only] is supported as of **v0.16.0** ([PR 11740](https://github.com/bitcoin/bitcoin/pull/11740)).
+* [`BIP 159`](https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki): The NODE_NETWORK_LIMITED service bit is signalled as of **v0.16.0** ([PR 11740](https://github.com/bitcoin/bitcoin/pull/11740)), and such nodes are connected to as of **v0.17.0** ([PR 10387](https://github.com/bitcoin/bitcoin/pull/10387)).
* [`BIP 173`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki): Bech32 addresses for native Segregated Witness outputs are supported as of **v0.16.0** ([PR 11167](https://github.com/bitcoin/bitcoin/pull/11167)).
* [`BIP 174`](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki): RPCs to operate on Partially Signed Bitcoin Transactions (PSBT) are present as of **v0.17.0** ([PR 13557](https://github.com/bitcoin/bitcoin/pull/13557)).
* [`BIP 176`](https://github.com/bitcoin/bips/blob/master/bip-0176.mediawiki): Bits Denomination [QT only] is supported as of **v0.16.0** ([PR 12035](https://github.com/bitcoin/bitcoin/pull/12035)).
diff --git a/doc/descriptors.md b/doc/descriptors.md
index 5dbcd95e1d..dbdac2c5b6 100644
--- a/doc/descriptors.md
+++ b/doc/descriptors.md
@@ -1,11 +1,18 @@
# Support for Output Descriptors in Bitcoin Core
-Since Bitcoin Core v0.17, there is support for Output Descriptors in the
-`scantxoutset` RPC call. This is a simple language which can be used to
-describe collections of output scripts.
-
-This document describes the language. For the specifics on usage for scanning
-the UTXO set, see the `scantxoutset` RPC help.
+Since Bitcoin Core v0.17, there is support for Output Descriptors. This is a
+simple language which can be used to describe collections of output scripts.
+Supporting RPCs are:
+- `scantxoutset` takes as input descriptors to scan for, and also reports
+ specialized descriptors for the matching UTXOs.
+- `getdescriptorinfo` analyzes a descriptor, and reports a canonicalized version
+ with checksum added.
+- `deriveaddresses` takes as input a descriptor and computes the corresponding
+ addresses.
+- `listunspent` outputs a specialized descriptor for the reported unspent outputs.
+
+This document describes the language. For the specifics on usage, see the RPC
+documentation for the functions mentioned above.
## Features
diff --git a/doc/psbt.md b/doc/psbt.md
index 560b45ef31..9d85af0348 100644
--- a/doc/psbt.md
+++ b/doc/psbt.md
@@ -67,6 +67,9 @@ hardware implementations will typically implement multiple roles simultaneously.
input a PSBT, adds UTXO, key, and script data to inputs and outputs that miss
it, and optionally signs inputs. Where possible it also finalizes the partial
signatures.
+- **`utxoupdatepsbt` (Updater)** is a node RPC that takes a PSBT and updates it
+ to include information available from the UTXO set (works only for SegWit
+ inputs).
- **`finalizepsbt` (Finalizer, Extractor)** is a utility RPC that finalizes any
partial signatures, and if all inputs are finalized, converts the result to a
fully signed transaction which can be broadcast with `sendrawtransaction`.
@@ -74,8 +77,15 @@ hardware implementations will typically implement multiple roles simultaneously.
can be used at any point in the workflow to merge information added to
different versions of the same PSBT. In particular it is useful to combine the
output of multiple Updaters or Signers.
+- **`joinpsbts`** (Creator) is a utility RPC that joins multiple PSBTs together,
+ concatenating the inputs and outputs. This can be used to construct CoinJoin
+ transactions.
- **`decodepsbt`** is a diagnostic utility RPC which will show all information in
a PSBT in human-readable form, as well as compute its eventual fee if known.
+- **`analyzepsbt`** is a utility RPC that examines an RPC and reports the
+ next steps in the workflow if known, computes the fee of the resulting
+ transaction, and estimates the weight and feerate if possible.
+
### Workflows
diff --git a/doc/release-notes.md b/doc/release-notes.md
index a876b70ba7..d4acbb9c88 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -85,9 +85,14 @@ Low-level Changes section below.
Low-level changes
=================
-Example item
+Configuration
------------
+* An error is issued where previously a warning was issued when a setting in
+ the config file was specified in the default section, but not overridden for
+ the selected network. This change takes only effect if the selected network
+ is not mainnet.
+
Credits
=======
diff --git a/doc/shared-libraries.md b/doc/shared-libraries.md
index dc363582cc..e960863a80 100644
--- a/doc/shared-libraries.md
+++ b/doc/shared-libraries.md
@@ -7,11 +7,11 @@ The purpose of this library is to make the verification functionality that is cr
### API
-The interface is defined in the C header `bitcoinconsensus.h` located in `src/script/bitcoinconsensus.h`.
+The interface is defined in the C header `bitcoinconsensus.h` located in `src/script/bitcoinconsensus.h`.
#### Version
-`bitcoinconsensus_version` returns an `unsigned int` with the API version *(currently at an experimental `0`)*.
+`bitcoinconsensus_version` returns an `unsigned int` with the API version *(currently `1`)*.
#### Script Validation
diff --git a/doc/travis-ci.md b/doc/travis-ci.md
deleted file mode 100644
index 38085cec35..0000000000
--- a/doc/travis-ci.md
+++ /dev/null
@@ -1,42 +0,0 @@
-Travis CI
-=========
-
-Support for using travis-ci has been added in order to automate pull-testing.
-See [travis-ci.org](https://travis-ci.org/) for more info
-
-This procedure is different than the pull-tester that came before it in a few
-ways.
-
-There is nothing to administer. This is a major feature as it means
-that builds have no local state. Because there is no ability to login to the
-builders to install packages (tools, dependencies, etc), the entire build
-procedure must instead be controlled by a declarative script `.travis.yml`.
-This script declares each build configuration, creates virtual machines as
-necessary, builds, then discards the virtual machines.
-
-A build matrix is constructed to test a wide range of configurations, rather
-than a single pass/fail. This helps to catch build failures and logic errors
-that present on platforms other than the ones the author has tested. This
-matrix is defined in the build script and can be changed at any time.
-
-All builders use the dependency-generator in the [depends dir](/depends), rather than
-using apt-get to install build dependencies. This guarantees that the tester
-is using the same versions as Gitian, so the build results are nearly identical
-to what would be found in a final release. However, this also means that builds
-will fail if new dependencies are introduced without being added to the
-dependency generator.
-
-In order to avoid rebuilding all dependencies for each build, the binaries are
-cached and re-used when possible. Changes in the dependency-generator will
-trigger cache-invalidation and rebuilds as necessary.
-
-These caches can be manually removed if necessary. This is one of the very few
-manual operations that is possible with Travis, and it can be done by the
-Bitcoin Core committer via the Travis web interface.
-
-In some cases, secure strings may be needed for hiding sensitive info such as
-private keys or URLs. The travis client may be used to create these strings:
-http://docs.travis-ci.com/user/encryption-keys/
-
-For the details of the build descriptor, see the official docs:
-http://docs.travis-ci.com/user/build-configuration/
diff --git a/src/Makefile.am b/src/Makefile.am
index 8f0110b43e..0385c825ea 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -434,8 +434,8 @@ libbitcoin_common_a_SOURCES = \
netaddress.cpp \
netbase.cpp \
policy/feerate.cpp \
- psbt.cpp \
protocol.cpp \
+ psbt.cpp \
scheduler.cpp \
script/descriptor.cpp \
script/ismine.cpp \
diff --git a/src/core_io.h b/src/core_io.h
index ae377eb6e8..19fb7b29f6 100644
--- a/src/core_io.h
+++ b/src/core_io.h
@@ -16,7 +16,6 @@ class CBlockHeader;
class CScript;
class CTransaction;
struct CMutableTransaction;
-struct PartiallySignedTransaction;
class uint256;
class UniValue;
@@ -37,11 +36,6 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
*/
bool ParseHashStr(const std::string& strHex, uint256& result);
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
-
-//! Decode a base64ed PSBT into a PartiallySignedTransaction
-NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
-//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
-NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
int ParseSighashString(const UniValue& sighash);
// core_write.cpp
diff --git a/src/core_read.cpp b/src/core_read.cpp
index 536a7f4f17..a879a375ce 100644
--- a/src/core_read.cpp
+++ b/src/core_read.cpp
@@ -4,7 +4,6 @@
#include <core_io.h>
-#include <psbt.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <script/script.h>
@@ -177,33 +176,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
return true;
}
-bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
-{
- bool invalid;
- std::string tx_data = DecodeBase64(base64_tx, &invalid);
- if (invalid) {
- error = "invalid base64";
- return false;
- }
- return DecodeRawPSBT(psbt, tx_data, error);
-}
-
-bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
-{
- CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
- try {
- ss_data >> psbt;
- if (!ss_data.empty()) {
- error = "extra data after PSBT";
- return false;
- }
- } catch (const std::exception& e) {
- error = e.what();
- return false;
- }
- return true;
-}
-
bool ParseHashStr(const std::string& strHex, uint256& result)
{
if ((strHex.size() != 64) || !IsHex(strHex))
diff --git a/src/init.cpp b/src/init.cpp
index 6564ce5b9a..ec8800ef07 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -828,19 +828,6 @@ void InitParameterInteraction()
if (gArgs.SoftSetBoolArg("-whitelistrelay", true))
LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n", __func__);
}
-
- // Warn if network-specific options (-addnode, -connect, etc) are
- // specified in default section of config file, but not overridden
- // on the command line or in this network's section of the config file.
- std::string network = gArgs.GetChainName();
- for (const auto& arg : gArgs.GetUnsuitableSectionOnlyArgs()) {
- InitWarning(strprintf(_("Config setting for %s only applied on %s network when in [%s] section."), arg, network, network));
- }
-
- // Warn if unrecognized section name are present in the config file.
- for (const auto& section : gArgs.GetUnrecognizedSections()) {
- InitWarning(strprintf("%s:%i " + _("Section [%s] is not recognized."), section.m_file, section.m_line, section.m_name));
- }
}
static std::string ResolveErrMsg(const char * const optname, const std::string& strBind)
@@ -950,6 +937,19 @@ bool AppInitParameterInteraction()
// also see: InitParameterInteraction()
+ // Warn if network-specific options (-addnode, -connect, etc) are
+ // specified in default section of config file, but not overridden
+ // on the command line or in this network's section of the config file.
+ std::string network = gArgs.GetChainName();
+ for (const auto& arg : gArgs.GetUnsuitableSectionOnlyArgs()) {
+ return InitError(strprintf(_("Config setting for %s only applied on %s network when in [%s] section."), arg, network, network));
+ }
+
+ // Warn if unrecognized section name are present in the config file.
+ for (const auto& section : gArgs.GetUnrecognizedSections()) {
+ InitWarning(strprintf("%s:%i " + _("Section [%s] is not recognized."), section.m_file, section.m_line, section.m_name));
+ }
+
if (!fs::is_directory(GetBlocksDir())) {
return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str()));
}
diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
index e409ced601..f278e5de95 100644
--- a/src/interfaces/chain.cpp
+++ b/src/interfaces/chain.cpp
@@ -202,17 +202,11 @@ public:
{
m_notifications->BlockDisconnected(*block);
}
- void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
- void ResendWalletTransactions(int64_t best_block_time, CConnman*) override
+ void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
{
- // `cs_main` is always held when this method is called, so it is safe to
- // call `assumeLocked`. This is awkward, and the `assumeLocked` method
- // should be able to be removed entirely if `ResendWalletTransactions`
- // is replaced by a wallet timer as suggested in
- // https://github.com/bitcoin/bitcoin/issues/15619
- auto locked_chain = m_chain.assumeLocked();
- m_notifications->ResendWalletTransactions(*locked_chain, best_block_time);
+ m_notifications->UpdatedBlockTip();
}
+ void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
Chain& m_chain;
Chain::Notifications* m_notifications;
};
@@ -347,6 +341,7 @@ public:
CAmount maxTxFee() override { return ::maxTxFee; }
bool getPruneMode() override { return ::fPruneMode; }
bool p2pEnabled() override { return g_connman != nullptr; }
+ bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !IsInitialBlockDownload(); }
bool isInitialBlockDownload() override { return IsInitialBlockDownload(); }
bool shutdownRequested() override { return ShutdownRequested(); }
int64_t getAdjustedTime() override { return GetAdjustedTime(); }
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index d11a59241e..4e0ef49f6c 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -223,6 +223,9 @@ public:
//! Check if p2p enabled.
virtual bool p2pEnabled() = 0;
+ //! Check if the node is ready to broadcast transactions.
+ virtual bool isReadyToBroadcast() = 0;
+
//! Check if in IBD.
virtual bool isInitialBlockDownload() = 0;
@@ -256,8 +259,8 @@ public:
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {}
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {}
virtual void BlockDisconnected(const CBlock& block) {}
+ virtual void UpdatedBlockTip() {}
virtual void ChainStateFlushed(const CBlockLocator& locator) {}
- virtual void ResendWalletTransactions(Lock& locked_chain, int64_t best_block_time) {}
};
//! Register handler for notifications.
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index 60173b29ac..0bb9739c9c 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -358,15 +358,16 @@ public:
}
WalletBalances getBalances() override
{
+ const auto bal = m_wallet->GetBalance();
WalletBalances result;
- result.balance = m_wallet->GetBalance();
- result.unconfirmed_balance = m_wallet->GetUnconfirmedBalance();
- result.immature_balance = m_wallet->GetImmatureBalance();
+ result.balance = bal.m_mine_trusted;
+ result.unconfirmed_balance = bal.m_mine_untrusted_pending;
+ result.immature_balance = bal.m_mine_immature;
result.have_watch_only = m_wallet->HaveWatchOnly();
if (result.have_watch_only) {
- result.watch_only_balance = m_wallet->GetBalance(ISMINE_WATCH_ONLY);
- result.unconfirmed_watch_only_balance = m_wallet->GetUnconfirmedWatchOnlyBalance();
- result.immature_watch_only_balance = m_wallet->GetImmatureWatchOnlyBalance();
+ result.watch_only_balance = bal.m_watchonly_trusted;
+ result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending;
+ result.immature_watch_only_balance = bal.m_watchonly_immature;
}
return result;
}
@@ -382,7 +383,7 @@ public:
num_blocks = locked_chain->getHeight().get_value_or(-1);
return true;
}
- CAmount getBalance() override { return m_wallet->GetBalance(); }
+ CAmount getBalance() override { return m_wallet->GetBalance().m_mine_trusted; }
CAmount getAvailableBalance(const CCoinControl& coin_control) override
{
return m_wallet->GetAvailableBalance(&coin_control);
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 0f13d6e269..044fcc90cd 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -175,8 +175,6 @@ namespace {
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration GUARDED_BY(cs_main);
- std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block
-
struct IteratorComparator
{
template<typename I>
@@ -1121,8 +1119,6 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB
});
connman->WakeMessageHandler();
}
-
- nTimeBestReceived = GetTime();
}
/**
@@ -3550,14 +3546,6 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
}
}
- // Resend wallet transactions that haven't gotten in a block yet
- // Except during reindex, importing and IBD, when old wallet
- // transactions become unconfirmed and spams other nodes.
- if (!fReindex && !fImporting && !IsInitialBlockDownload())
- {
- GetMainSignals().Broadcast(nTimeBestReceived, connman);
- }
-
//
// Try sending block announcements via headers
//
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 0fb7d49d7d..184129e330 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -2,9 +2,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <coins.h>
+#include <consensus/tx_verify.h>
+#include <policy/policy.h>
#include <psbt.h>
#include <util/strencodings.h>
+#include <numeric>
+
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{
inputs.resize(tx.vin.size());
@@ -205,7 +210,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
}
-bool PSBTInputSigned(PSBTInput& input)
+bool PSBTInputSigned(const PSBTInput& input)
{
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
}
@@ -325,3 +330,162 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector
return TransactionError::OK;
}
+
+std::string PSBTRoleName(PSBTRole role) {
+ switch (role) {
+ case PSBTRole::UPDATER: return "updater";
+ case PSBTRole::SIGNER: return "signer";
+ case PSBTRole::FINALIZER: return "finalizer";
+ case PSBTRole::EXTRACTOR: return "extractor";
+ }
+}
+
+PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
+{
+ // Go through each input and build status
+ PSBTAnalysis result;
+
+ bool calc_fee = true;
+ bool all_final = true;
+ bool only_missing_sigs = true;
+ bool only_missing_final = false;
+ CAmount in_amt = 0;
+
+ result.inputs.resize(psbtx.tx->vin.size());
+
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& input = psbtx.inputs[i];
+ PSBTInputAnalysis& input_analysis = result.inputs[i];
+
+ // Check for a UTXO
+ CTxOut utxo;
+ if (psbtx.GetInputUTXO(utxo, i)) {
+ in_amt += utxo.nValue;
+ input_analysis.has_utxo = true;
+ } else {
+ input_analysis.has_utxo = false;
+ input_analysis.is_final = false;
+ input_analysis.next = PSBTRole::UPDATER;
+ calc_fee = false;
+ }
+
+ // Check if it is final
+ if (!utxo.IsNull() && !PSBTInputSigned(input)) {
+ input_analysis.is_final = false;
+ all_final = false;
+
+ // Figure out what is missing
+ SignatureData outdata;
+ bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata);
+
+ // Things are missing
+ if (!complete) {
+ input_analysis.missing_pubkeys = outdata.missing_pubkeys;
+ input_analysis.missing_redeem_script = outdata.missing_redeem_script;
+ input_analysis.missing_witness_script = outdata.missing_witness_script;
+ input_analysis.missing_sigs = outdata.missing_sigs;
+
+ // If we are only missing signatures and nothing else, then next is signer
+ if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) {
+ input_analysis.next = PSBTRole::SIGNER;
+ } else {
+ only_missing_sigs = false;
+ input_analysis.next = PSBTRole::UPDATER;
+ }
+ } else {
+ only_missing_final = true;
+ input_analysis.next = PSBTRole::FINALIZER;
+ }
+ } else if (!utxo.IsNull()){
+ input_analysis.is_final = true;
+ }
+ }
+
+ if (all_final) {
+ only_missing_sigs = false;
+ result.next = PSBTRole::EXTRACTOR;
+ }
+ if (calc_fee) {
+ // Get the output amount
+ CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0),
+ [](CAmount a, const CTxOut& b) {
+ return a += b.nValue;
+ }
+ );
+
+ // Get the fee
+ CAmount fee = in_amt - out_amt;
+ result.fee = fee;
+
+ // Estimate the size
+ CMutableTransaction mtx(*psbtx.tx);
+ CCoinsView view_dummy;
+ CCoinsViewCache view(&view_dummy);
+ bool success = true;
+
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& input = psbtx.inputs[i];
+ Coin newcoin;
+
+ if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) {
+ success = false;
+ break;
+ } else {
+ mtx.vin[i].scriptSig = input.final_script_sig;
+ mtx.vin[i].scriptWitness = input.final_script_witness;
+ newcoin.nHeight = 1;
+ view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
+ }
+ }
+
+ if (success) {
+ CTransaction ctx = CTransaction(mtx);
+ size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
+ result.estimated_vsize = size;
+ // Estimate fee rate
+ CFeeRate feerate(fee, size);
+ result.estimated_feerate = feerate;
+ }
+
+ if (only_missing_sigs) {
+ result.next = PSBTRole::SIGNER;
+ } else if (only_missing_final) {
+ result.next = PSBTRole::FINALIZER;
+ } else if (all_final) {
+ result.next = PSBTRole::EXTRACTOR;
+ } else {
+ result.next = PSBTRole::UPDATER;
+ }
+ } else {
+ result.next = PSBTRole::UPDATER;
+ }
+
+ return result;
+}
+
+bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
+{
+ bool invalid;
+ std::string tx_data = DecodeBase64(base64_tx, &invalid);
+ if (invalid) {
+ error = "invalid base64";
+ return false;
+ }
+ return DecodeRawPSBT(psbt, tx_data, error);
+}
+
+bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
+{
+ CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
+ try {
+ ss_data >> psbt;
+ if (!ss_data.empty()) {
+ error = "extra data after PSBT";
+ return false;
+ }
+ } catch (const std::exception& e) {
+ error = e.what();
+ return false;
+ }
+ return true;
+}
diff --git a/src/psbt.h b/src/psbt.h
index c889dad361..fcb3337a53 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -7,6 +7,8 @@
#include <attributes.h>
#include <node/transaction.h>
+#include <optional.h>
+#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/sign.h>
@@ -548,8 +550,42 @@ struct PartiallySignedTransaction
}
};
+enum class PSBTRole {
+ UPDATER,
+ SIGNER,
+ FINALIZER,
+ EXTRACTOR
+};
+
+/**
+ * Holds an analysis of one input from a PSBT
+ */
+struct PSBTInputAnalysis {
+ bool has_utxo; //!< Whether we have UTXO information for this input
+ bool is_final; //!< Whether the input has all required information including signatures
+ PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next
+
+ std::vector<CKeyID> missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing
+ std::vector<CKeyID> missing_sigs; //!< Pubkeys whose signatures are missing
+ uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing
+ uint256 missing_witness_script; //!< SHA256 of witness script, if missing
+};
+
+/**
+ * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
+ */
+struct PSBTAnalysis {
+ Optional<size_t> estimated_vsize; //!< Estimated weight of the transaction
+ Optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction
+ Optional<CAmount> fee; //!< Amount of fee being paid by the transaction
+ std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction
+ PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next
+};
+
+std::string PSBTRoleName(PSBTRole role);
+
/** Checks whether a PSBTInput is already signed. */
-bool PSBTInputSigned(PSBTInput& input);
+bool PSBTInputSigned(const PSBTInput& input);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false);
@@ -580,4 +616,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
*/
NODISCARD TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
+/**
+ * Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
+ *
+ * @param[in] psbtx the PSBT to analyze
+ * @return A PSBTAnalysis with information about the provided PSBT.
+ */
+PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx);
+
+//! Decode a base64ed PSBT into a PartiallySignedTransaction
+NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
+//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
+NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
+
#endif // BITCOIN_PSBT_H
diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui
index 2f916d0b44..8876ea1337 100644
--- a/src/qt/forms/receivecoinsdialog.ui
+++ b/src/qt/forms/receivecoinsdialog.ui
@@ -189,7 +189,7 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useBech32">
+ <widget class="QCheckBox" name="useLegacyAddress">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -206,10 +206,10 @@
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
- <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</string>
+ <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When checked, an address compatible with older wallets will be created instead.</string>
</property>
<property name="text">
- <string>Generate native segwit (Bech32) address</string>
+ <string>Generate legacy address</string>
</property>
</widget>
</item>
@@ -360,7 +360,7 @@
<tabstops>
<tabstop>reqLabel</tabstop>
<tabstop>reqAmount</tabstop>
- <tabstop>useBech32</tabstop>
+ <tabstop>useLegacyAddress</tabstop>
<tabstop>reqMessage</tabstop>
<tabstop>receiveButton</tabstop>
<tabstop>clearButton</tabstop>
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 22a79a12bb..fc58090dcd 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -95,9 +95,9 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this);
if (model->wallet().getDefaultAddressType() == OutputType::BECH32) {
- ui->useBech32->setCheckState(Qt::Checked);
+ ui->useLegacyAddress->setCheckState(Qt::Unchecked);
} else {
- ui->useBech32->setCheckState(Qt::Unchecked);
+ ui->useLegacyAddress->setCheckState(Qt::Checked);
}
// Set the button to be enabled or disabled based on whether the wallet can give out new addresses.
@@ -150,7 +150,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()
QString label = ui->reqLabel->text();
/* Generate new receiving address */
OutputType address_type;
- if (ui->useBech32->isChecked()) {
+ if (!ui->useLegacyAddress->isChecked()) {
address_type = OutputType::BECH32;
} else {
address_type = model->wallet().getDefaultAddressType();
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 5dcd04323e..6ecde84cdb 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1054,7 +1054,9 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE),
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
+ "/kB.\nSet to 0 to accept any fee rate.\n"},
},
RPCResult{
"\"hex\" (string) The transaction hash in hex\n"
@@ -1549,7 +1551,6 @@ UniValue combinepsbt(const JSONRPCRequest& request)
throw JSONRPCTransactionError(error);
}
- UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
@@ -1944,148 +1945,56 @@ UniValue analyzepsbt(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
- // Go through each input and build status
+ PSBTAnalysis psbta = AnalyzePSBT(psbtx);
+
UniValue result(UniValue::VOBJ);
UniValue inputs_result(UniValue::VARR);
- bool calc_fee = true;
- bool all_final = true;
- bool only_missing_sigs = true;
- bool only_missing_final = false;
- CAmount in_amt = 0;
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- PSBTInput& input = psbtx.inputs[i];
+ for (const auto& input : psbta.inputs) {
UniValue input_univ(UniValue::VOBJ);
UniValue missing(UniValue::VOBJ);
- // Check for a UTXO
- CTxOut utxo;
- if (psbtx.GetInputUTXO(utxo, i)) {
- in_amt += utxo.nValue;
- input_univ.pushKV("has_utxo", true);
- } else {
- input_univ.pushKV("has_utxo", false);
- input_univ.pushKV("is_final", false);
- input_univ.pushKV("next", "updater");
- calc_fee = false;
- }
+ input_univ.pushKV("has_utxo", input.has_utxo);
+ input_univ.pushKV("is_final", input.is_final);
+ input_univ.pushKV("next", PSBTRoleName(input.next));
- // Check if it is final
- if (!utxo.IsNull() && !PSBTInputSigned(input)) {
- input_univ.pushKV("is_final", false);
- all_final = false;
-
- // Figure out what is missing
- SignatureData outdata;
- bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata);
-
- // Things are missing
- if (!complete) {
- if (!outdata.missing_pubkeys.empty()) {
- // Missing pubkeys
- UniValue missing_pubkeys_univ(UniValue::VARR);
- for (const CKeyID& pubkey : outdata.missing_pubkeys) {
- missing_pubkeys_univ.push_back(HexStr(pubkey));
- }
- missing.pushKV("pubkeys", missing_pubkeys_univ);
- }
- if (!outdata.missing_redeem_script.IsNull()) {
- // Missing redeemScript
- missing.pushKV("redeemscript", HexStr(outdata.missing_redeem_script));
- }
- if (!outdata.missing_witness_script.IsNull()) {
- // Missing witnessScript
- missing.pushKV("witnessscript", HexStr(outdata.missing_witness_script));
- }
- if (!outdata.missing_sigs.empty()) {
- // Missing sigs
- UniValue missing_sigs_univ(UniValue::VARR);
- for (const CKeyID& pubkey : outdata.missing_sigs) {
- missing_sigs_univ.push_back(HexStr(pubkey));
- }
- missing.pushKV("signatures", missing_sigs_univ);
- }
- input_univ.pushKV("missing", missing);
-
- // If we are only missing signatures and nothing else, then next is signer
- if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) {
- input_univ.pushKV("next", "signer");
- } else {
- only_missing_sigs = false;
- input_univ.pushKV("next", "updater");
- }
- } else {
- only_missing_final = true;
- input_univ.pushKV("next", "finalizer");
+ if (!input.missing_pubkeys.empty()) {
+ UniValue missing_pubkeys_univ(UniValue::VARR);
+ for (const CKeyID& pubkey : input.missing_pubkeys) {
+ missing_pubkeys_univ.push_back(HexStr(pubkey));
+ }
+ missing.pushKV("pubkeys", missing_pubkeys_univ);
+ }
+ if (!input.missing_redeem_script.IsNull()) {
+ missing.pushKV("redeemscript", HexStr(input.missing_redeem_script));
+ }
+ if (!input.missing_witness_script.IsNull()) {
+ missing.pushKV("witnessscript", HexStr(input.missing_witness_script));
+ }
+ if (!input.missing_sigs.empty()) {
+ UniValue missing_sigs_univ(UniValue::VARR);
+ for (const CKeyID& pubkey : input.missing_sigs) {
+ missing_sigs_univ.push_back(HexStr(pubkey));
}
- } else if (!utxo.IsNull()){
- input_univ.pushKV("is_final", true);
+ missing.pushKV("signatures", missing_sigs_univ);
+ }
+ if (!missing.getKeys().empty()) {
+ input_univ.pushKV("missing", missing);
}
inputs_result.push_back(input_univ);
}
result.pushKV("inputs", inputs_result);
- if (all_final) {
- only_missing_sigs = false;
- result.pushKV("next", "extractor");
+ if (psbta.estimated_vsize != nullopt) {
+ result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
}
- if (calc_fee) {
- // Get the output amount
- CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0),
- [](CAmount a, const CTxOut& b) {
- return a += b.nValue;
- }
- );
-
- // Get the fee
- CAmount fee = in_amt - out_amt;
-
- // Estimate the size
- CMutableTransaction mtx(*psbtx.tx);
- CCoinsView view_dummy;
- CCoinsViewCache view(&view_dummy);
- bool success = true;
-
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- PSBTInput& input = psbtx.inputs[i];
- if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true)) {
- mtx.vin[i].scriptSig = input.final_script_sig;
- mtx.vin[i].scriptWitness = input.final_script_witness;
-
- Coin newcoin;
- if (!psbtx.GetInputUTXO(newcoin.out, i)) {
- success = false;
- break;
- }
- newcoin.nHeight = 1;
- view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
- } else {
- success = false;
- break;
- }
- }
-
- if (success) {
- CTransaction ctx = CTransaction(mtx);
- size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
- result.pushKV("estimated_vsize", (int)size);
- // Estimate fee rate
- CFeeRate feerate(fee, size);
- result.pushKV("estimated_feerate", ValueFromAmount(feerate.GetFeePerK()));
- }
- result.pushKV("fee", ValueFromAmount(fee));
-
- if (only_missing_sigs) {
- result.pushKV("next", "signer");
- } else if (only_missing_final) {
- result.pushKV("next", "finalizer");
- } else if (all_final) {
- result.pushKV("next", "extractor");
- } else {
- result.pushKV("next", "updater");
- }
- } else {
- result.pushKV("next", "updater");
+ if (psbta.estimated_feerate != nullopt) {
+ result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK()));
}
+ if (psbta.fee != nullopt) {
+ result.pushKV("fee", ValueFromAmount(*psbta.fee));
+ }
+ result.pushKV("next", PSBTRoleName(psbta.next));
+
return result;
}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 10979b43b0..4cd63fada6 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -165,6 +165,10 @@ UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_s
}
}
+/**
+ * A pair of strings that can be aligned (through padding) with other Sections
+ * later on
+ */
struct Section {
Section(const std::string& left, const std::string& right)
: m_left{left}, m_right{right} {}
@@ -172,6 +176,10 @@ struct Section {
const std::string m_right;
};
+/**
+ * Keeps track of RPCArgs by transforming them into sections for the purpose
+ * of serializing everything to a single string
+ */
struct Sections {
std::vector<Section> m_sections;
size_t m_max_pad{0};
@@ -182,16 +190,26 @@ struct Sections {
m_sections.push_back(s);
}
+ /**
+ * Serializing RPCArgs depends on the outer type. Only arrays and
+ * dictionaries can be nested in json. The top-level outer type is "named
+ * arguments", a mix between a dictionary and arrays.
+ */
enum class OuterType {
ARR,
OBJ,
NAMED_ARG, // Only set on first recursion
};
+ /**
+ * Recursive helper to translate an RPCArg into sections
+ */
void Push(const RPCArg& arg, const size_t current_indent = 5, const OuterType outer_type = OuterType::NAMED_ARG)
{
const auto indent = std::string(current_indent, ' ');
const auto indent_next = std::string(current_indent + 2, ' ');
+ const bool push_name{outer_type == OuterType::OBJ}; // Dictionary keys must have a name
+
switch (arg.m_type) {
case RPCArg::Type::STR_HEX:
case RPCArg::Type::STR:
@@ -201,10 +219,10 @@ struct Sections {
case RPCArg::Type::BOOL: {
if (outer_type == OuterType::NAMED_ARG) return; // Nothing more to do for non-recursive types on first recursion
auto left = indent;
- if (arg.m_type_str.size() != 0 && outer_type == OuterType::OBJ) {
+ if (arg.m_type_str.size() != 0 && push_name) {
left += "\"" + arg.m_name + "\": " + arg.m_type_str.at(0);
} else {
- left += outer_type == OuterType::OBJ ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false);
+ left += push_name ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false);
}
left += ",";
PushSection({left, arg.ToDescriptionString()});
@@ -213,7 +231,7 @@ struct Sections {
case RPCArg::Type::OBJ:
case RPCArg::Type::OBJ_USER_KEYS: {
const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString();
- PushSection({indent + "{", right});
+ PushSection({indent + (push_name ? "\"" + arg.m_name + "\": " : "") + "{", right});
for (const auto& arg_inner : arg.m_inner) {
Push(arg_inner, current_indent + 2, OuterType::OBJ);
}
@@ -225,7 +243,7 @@ struct Sections {
}
case RPCArg::Type::ARR: {
auto left = indent;
- left += outer_type == OuterType::OBJ ? "\"" + arg.m_name + "\": " : "";
+ left += push_name ? "\"" + arg.m_name + "\": " : "";
left += "[";
const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString();
PushSection({left, right});
@@ -241,6 +259,9 @@ struct Sections {
}
}
+ /**
+ * Concatenate all sections with proper padding
+ */
std::string ToString() const
{
std::string ret;
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 43448d7222..a333d4d4ac 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -436,7 +436,7 @@ public:
pubkeys.reserve(entries.size());
for (auto& entry : entries) {
pubkeys.push_back(entry.first);
- out.origins.emplace(entry.first.GetID(), std::move(entry.second));
+ out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second)));
}
if (m_script_arg) {
for (const auto& subscript : subscripts) {
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 320956d0c4..36dd68a3d8 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -483,7 +483,13 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
-bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return LookupHelper(origins, keyid, info); }
+bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
+{
+ std::pair<CPubKey, KeyOriginInfo> out;
+ bool ret = LookupHelper(origins, keyid, out);
+ if (ret) info = std::move(out.second);
+ return ret;
+}
bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); }
FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b)
diff --git a/src/script/sign.h b/src/script/sign.h
index 491fb54c45..f746325b90 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -77,7 +77,7 @@ struct FlatSigningProvider final : public SigningProvider
{
std::map<CScriptID, CScript> scripts;
std::map<CKeyID, CPubKey> pubkeys;
- std::map<CKeyID, KeyOriginInfo> origins;
+ std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins;
std::map<CKeyID, CKey> keys;
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index ff2b8d4fc9..826615cb51 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -154,8 +154,8 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std:
// Test whether the observed key path is present in the 'paths' variable (which contains expected, unobserved paths),
// and then remove it from that set.
for (const auto& origin : script_provider.origins) {
- BOOST_CHECK_MESSAGE(paths.count(origin.second.path), "Unexpected key path: " + prv);
- left_paths.erase(origin.second.path);
+ BOOST_CHECK_MESSAGE(paths.count(origin.second.second.path), "Unexpected key path: " + prv);
+ left_paths.erase(origin.second.second.path);
}
}
}
diff --git a/src/validation.cpp b/src/validation.cpp
index ae3985485e..eee050dc89 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2799,7 +2799,7 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
bool ret = DisconnectTip(state, chainparams, &disconnectpool);
// DisconnectTip will add transactions to disconnectpool.
// Adjust the mempool to be consistent with the new tip, adding
- // transactions back to the mempool if disconnecting was succesful,
+ // transactions back to the mempool if disconnecting was successful,
// and we're not doing a very deep invalidation (in which case
// keeping the mempool up to date is probably futile anyway).
UpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret);
diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp
index 70c274d20e..5d0ee1d1fc 100644
--- a/src/validationinterface.cpp
+++ b/src/validationinterface.cpp
@@ -25,7 +25,6 @@ struct ValidationInterfaceConnections {
boost::signals2::scoped_connection BlockDisconnected;
boost::signals2::scoped_connection TransactionRemovedFromMempool;
boost::signals2::scoped_connection ChainStateFlushed;
- boost::signals2::scoped_connection Broadcast;
boost::signals2::scoped_connection BlockChecked;
boost::signals2::scoped_connection NewPoWValidBlock;
};
@@ -37,7 +36,6 @@ struct MainSignalsInstance {
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
- boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
@@ -101,7 +99,6 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1));
conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1));
conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1));
- conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
}
@@ -175,10 +172,6 @@ void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) {
});
}
-void CMainSignals::Broadcast(int64_t nBestBlockTime, CConnman* connman) {
- m_internals->Broadcast(nBestBlockTime, connman);
-}
-
void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
m_internals->BlockChecked(block, state);
}
diff --git a/src/validationinterface.h b/src/validationinterface.h
index f0374e8e78..78f3026a80 100644
--- a/src/validationinterface.h
+++ b/src/validationinterface.h
@@ -134,8 +134,6 @@ protected:
* Called on a background thread.
*/
virtual void ChainStateFlushed(const CBlockLocator &locator) {}
- /** Tells listeners to broadcast their data. */
- virtual void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) {}
/**
* Notifies listeners of a block validation result.
* If the provided CValidationState IsValid, the provided block
@@ -184,7 +182,6 @@ public:
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &);
void BlockDisconnected(const std::shared_ptr<const CBlock> &);
void ChainStateFlushed(const CBlockLocator &);
- void Broadcast(int64_t nBestBlockTime, CConnman* connman);
void BlockChecked(const CBlock&, const CValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
};
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 76a7eaa681..a8096dc671 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -211,8 +211,9 @@ void StartWallets(CScheduler& scheduler)
pwallet->postInitProcess();
}
- // Run a thread to flush wallet periodically
+ // Schedule periodic wallet flushes and tx rebroadcasts
scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
+ scheduler.scheduleEvery(MaybeResendWalletTxs, 1000);
}
void FlushWallets()
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 9c5dae3623..c339e111ba 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -900,7 +900,7 @@ struct ImportData
// Output data
std::set<CScript> import_scripts;
std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability)
- std::map<CKeyID, KeyOriginInfo> key_origins;
+ std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins;
};
enum class ScriptContext
@@ -1197,6 +1197,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
[&](const std::pair<CKeyID, CPubKey>& used_key) {
return privkey_map.count(used_key.first) > 0;
+ }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(),
+ [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) {
+ return privkey_map.count(entry.first) > 0;
});
if (!watch_only && !spendable) {
warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
@@ -1274,7 +1277,10 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
}
pwallet->UpdateTimeFirstKey(timestamp);
- }
+ }
+ for (const auto& entry : import_data.key_origins) {
+ pwallet->AddKeyOrigin(entry.second.first, entry.second.second);
+ }
for (const CKeyID& id : ordered_pubkeys) {
auto entry = pubkey_map.find(id);
if (entry == pubkey_map.end()) {
@@ -1285,10 +1291,6 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
- const auto& key_orig_it = import_data.key_origins.find(id);
- if (key_orig_it != import_data.key_origins.end()) {
- pwallet->AddKeyOrigin(pubkey, key_orig_it->second);
- }
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
// Add to keypool only works with pubkeys
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index f1da1fb589..d9ae0b9bd6 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -308,7 +308,7 @@ static UniValue setlabel(const JSONRPCRequest& request)
static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
{
- CAmount curBalance = pwallet->GetBalance();
+ CAmount curBalance = pwallet->GetBalance().m_mine_trusted;
// Check amount
if (nValue <= 0)
@@ -761,12 +761,14 @@ static UniValue getbalance(const JSONRPCRequest& request)
min_depth = request.params[1].get_int();
}
- isminefilter filter = ISMINE_SPENDABLE;
+ bool include_watchonly = false;
if (!request.params[2].isNull() && request.params[2].get_bool()) {
- filter = filter | ISMINE_WATCH_ONLY;
+ include_watchonly = true;
}
- return ValueFromAmount(pwallet->GetBalance(filter, min_depth));
+ const auto bal = pwallet->GetBalance(min_depth);
+
+ return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
}
static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
@@ -794,7 +796,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
- return ValueFromAmount(pwallet->GetUnconfirmedBalance());
+ return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending);
}
@@ -2416,11 +2418,12 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
+ const auto bal = pwallet->GetBalance();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
- obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance()));
- obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()));
- obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()));
+ obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
+ obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
+ obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index af57dbf5f6..7c8085dc20 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -58,7 +58,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height);
- BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0);
+ BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
}
// Verify ScanForWalletTransactions picks up transactions in both the old
@@ -73,7 +73,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
- BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
+ BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN);
}
// Prune the older block file.
@@ -92,7 +92,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
- BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
+ BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN);
}
// Prune the remaining block file.
@@ -110,7 +110,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height);
- BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0);
+ BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
}
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index f0499d5b32..9f89cbefa0 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1276,6 +1276,10 @@ void CWallet::BlockDisconnected(const CBlock& block) {
}
}
+void CWallet::UpdatedBlockTip()
+{
+ m_best_block_time = GetTime();
+}
void CWallet::BlockUntilSyncedToCurrentChain() {
@@ -2110,8 +2114,21 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
return CTransaction(tx1) == CTransaction(tx2);
}
-void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime)
+// Rebroadcast transactions from the wallet. We do this on a random timer
+// to slightly obfuscate which transactions come from our wallet.
+//
+// Ideally, we'd only resend transactions that we think should have been
+// mined in the most recent block. Any transaction that wasn't in the top
+// blockweight of transactions in the mempool shouldn't have been mined,
+// and so is probably just sitting in the mempool waiting to be confirmed.
+// Rebroadcasting does nothing to speed up confirmation and only damages
+// privacy.
+void CWallet::ResendWalletTransactions()
{
+ // During reindex, importing and IBD, old wallet transactions become
+ // unconfirmed. Don't resend them as that would spam other nodes.
+ if (!chain().isReadyToBroadcast()) return;
+
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) return;
@@ -2120,12 +2137,13 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
if (fFirst) return;
// Only do it if there's been a new block since last time
- if (nBestBlockTime < nLastResend) return;
+ if (m_best_block_time < nLastResend) return;
nLastResend = GetTime();
int relayed_tx_count = 0;
- { // cs_wallet scope
+ { // locked_chain and cs_wallet scope
+ auto locked_chain = chain().lock();
LOCK(cs_wallet);
// Relay transactions
@@ -2133,10 +2151,10 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
CWalletTx& wtx = item.second;
// only rebroadcast unconfirmed txes older than 5 minutes before the
// last block was found
- if (wtx.nTimeReceived > nBestBlockTime - 5 * 60) continue;
- relayed_tx_count += wtx.RelayWalletTransaction(locked_chain) ? 1 : 0;
+ if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
+ if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count;
}
- } // cs_wallet
+ } // locked_chain and cs_wallet
if (relayed_tx_count > 0) {
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count);
@@ -2145,7 +2163,12 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
/** @} */ // end of mapWallet
-
+void MaybeResendWalletTxs()
+{
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ pwallet->ResendWalletTransactions();
+ }
+}
/** @defgroup Actions
@@ -2154,84 +2177,32 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in
*/
-CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const
+CWallet::Balance CWallet::GetBalance(const int min_depth) const
{
- CAmount nTotal = 0;
+ Balance ret;
{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx& wtx = entry.second;
- if (wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) >= min_depth) {
- nTotal += wtx.GetAvailableCredit(*locked_chain, true, filter);
+ const bool is_trusted{wtx.IsTrusted(*locked_chain)};
+ const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)};
+ const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE)};
+ const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY)};
+ if (is_trusted && tx_depth >= min_depth) {
+ ret.m_mine_trusted += tx_credit_mine;
+ ret.m_watchonly_trusted += tx_credit_watchonly;
}
+ if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
+ ret.m_mine_untrusted_pending += tx_credit_mine;
+ ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
+ }
+ ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain);
+ ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain);
}
}
-
- return nTotal;
-}
-
-CAmount CWallet::GetUnconfirmedBalance() const
-{
- CAmount nTotal = 0;
- {
- auto locked_chain = chain().lock();
- LOCK(cs_wallet);
- for (const auto& entry : mapWallet)
- {
- const CWalletTx& wtx = entry.second;
- if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool())
- nTotal += wtx.GetAvailableCredit(*locked_chain);
- }
- }
- return nTotal;
-}
-
-CAmount CWallet::GetImmatureBalance() const
-{
- CAmount nTotal = 0;
- {
- auto locked_chain = chain().lock();
- LOCK(cs_wallet);
- for (const auto& entry : mapWallet)
- {
- const CWalletTx& wtx = entry.second;
- nTotal += wtx.GetImmatureCredit(*locked_chain);
- }
- }
- return nTotal;
-}
-
-CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
-{
- CAmount nTotal = 0;
- {
- auto locked_chain = chain().lock();
- LOCK(cs_wallet);
- for (const auto& entry : mapWallet)
- {
- const CWalletTx& wtx = entry.second;
- if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool())
- nTotal += wtx.GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY);
- }
- }
- return nTotal;
-}
-
-CAmount CWallet::GetImmatureWatchOnlyBalance() const
-{
- CAmount nTotal = 0;
- {
- auto locked_chain = chain().lock();
- LOCK(cs_wallet);
- for (const auto& entry : mapWallet)
- {
- const CWalletTx& wtx = entry.second;
- nTotal += wtx.GetImmatureWatchOnlyCredit(*locked_chain);
- }
- }
- return nTotal;
+ return ret;
}
CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 3aeeaafd1f..587b2814c0 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -657,6 +657,8 @@ private:
int64_t nNextResend = 0;
int64_t nLastResend = 0;
bool fBroadcastTransactions = false;
+ // Local time that the tip block was received. Used to schedule wallet rebroadcasts.
+ std::atomic<int64_t> m_best_block_time {0};
/**
* Used to keep track of spent outpoints, and
@@ -926,6 +928,7 @@ public:
void TransactionAddedToMempool(const CTransactionRef& tx) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const CBlock& block) override;
+ void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
struct ScanResult {
@@ -946,12 +949,16 @@ public:
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override;
- CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const;
- CAmount GetUnconfirmedBalance() const;
- CAmount GetImmatureBalance() const;
- CAmount GetUnconfirmedWatchOnlyBalance() const;
- CAmount GetImmatureWatchOnlyBalance() const;
+ void ResendWalletTransactions();
+ struct Balance {
+ CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
+ CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
+ CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain
+ CAmount m_watchonly_trusted{0};
+ CAmount m_watchonly_untrusted_pending{0};
+ CAmount m_watchonly_immature{0};
+ };
+ Balance GetBalance(int min_depth = 0) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend);
@@ -1225,6 +1232,12 @@ public:
friend struct WalletTestingSetup;
};
+/**
+ * Called periodically by the schedule thread. Prompts individual wallets to resend
+ * their transactions. Actual rebroadcast schedule is managed by the wallets themselves.
+ */
+void MaybeResendWalletTxs();
+
/** A key allocated from the key pool. */
class CReserveKey final : public CReserveScript
{
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 13481b0478..2b93e3c24d 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -25,6 +25,10 @@ class ConfArgsTest(BitcoinTestFramework):
conf.write('-dash=1\n')
self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -')
+ with open(inc_conf_file_path, 'w', encoding='utf8') as conf:
+ conf.write("wallet=foo\n")
+ self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.')
+
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('nono\n')
self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead')
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 67ab1f0130..49ae0fb1a9 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -4,9 +4,19 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests some generic aspects of the RPC interface."""
+from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_greater_than_or_equal
+def expect_http_status(expected_http_status, expected_rpc_code,
+ fcn, *args):
+ try:
+ fcn(*args)
+ raise AssertionError("Expected RPC error %d, got none" % expected_rpc_code)
+ except JSONRPCException as exc:
+ assert_equal(exc.error["code"], expected_rpc_code)
+ assert_equal(exc.http_status, expected_http_status)
+
class RPCInterfaceTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -48,9 +58,16 @@ class RPCInterfaceTest(BitcoinTestFramework):
assert_equal(result_by_id[3]['error'], None)
assert result_by_id[3]['result'] is not None
+ def test_http_status_codes(self):
+ self.log.info("Testing HTTP status codes for JSON-RPC requests...")
+
+ expect_http_status(404, -32601, self.nodes[0].invalidmethod)
+ expect_http_status(500, -8, self.nodes[0].getblockhash, 42)
+
def run_test(self):
self.test_getrpcinfo()
self.test_batch_request()
+ self.test_http_status_codes()
if __name__ == '__main__':
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 82a14af9da..3ca6bec254 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -7,19 +7,18 @@
Version 1 compact blocks are pre-segwit (txids)
Version 2 compact blocks are post-segwit (wtxids)
"""
-from decimal import Decimal
import random
from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment
-from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_block, msg_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_witness_block, msg_witness_blocktxn, MSG_WITNESS_FLAG, NODE_NETWORK, NODE_WITNESS, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex
+from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_block, msg_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_witness_block, msg_witness_blocktxn, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex
from test_framework.mininode import mininode_lock, P2PInterface
from test_framework.script import CScript, OP_TRUE, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, get_bip9_status, satoshi_round, sync_blocks, wait_until
+from test_framework.util import assert_equal, get_bip9_status, wait_until
# TestP2PConn: A peer we use to send messages to bitcoind, and store responses.
class TestP2PConn(P2PInterface):
- def __init__(self):
+ def __init__(self, cmpct_version):
super().__init__()
self.last_sendcmpct = []
self.block_announced = False
@@ -27,6 +26,7 @@ class TestP2PConn(P2PInterface):
# This is for synchronizing the p2p message traffic,
# so we can eg wait until a particular block is announced.
self.announced_blockhashes = set()
+ self.cmpct_version = cmpct_version
def on_sendcmpct(self, message):
self.last_sendcmpct.append(message)
@@ -94,11 +94,7 @@ class TestP2PConn(P2PInterface):
class CompactBlocksTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- # Node0 = pre-segwit, node1 = segwit-aware
- self.num_nodes = 2
- # This test was written assuming SegWit is activated using BIP9 at height 432 (3x confirmation window).
- # TODO: Rewrite this test to support SegWit being always active.
- self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex"]]
+ self.num_nodes = 1
self.utxos = []
def skip_test_if_missing_module(self):
@@ -117,11 +113,10 @@ class CompactBlocksTest(BitcoinTestFramework):
# Create 10 more anyone-can-spend utxo's for testing.
def make_utxos(self):
- # Doesn't matter which node we use, just use node0.
block = self.build_block_on_tip(self.nodes[0])
- self.test_node.send_and_ping(msg_block(block))
+ self.segwit_node.send_and_ping(msg_block(block))
assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256
- self.nodes[0].generate(100)
+ self.nodes[0].generatetoaddress(100, self.nodes[0].getnewaddress(address_type="bech32"))
total_value = block.vtx[0].vout[0].nValue
out_value = total_value // 10
@@ -135,10 +130,10 @@ class CompactBlocksTest(BitcoinTestFramework):
block2.vtx.append(tx)
block2.hashMerkleRoot = block2.calc_merkle_root()
block2.solve()
- self.test_node.send_and_ping(msg_block(block2))
+ self.segwit_node.send_and_ping(msg_block(block2))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256)
self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)])
- return
+
# Test "sendcmpct" (between peers preferring the same version):
# - No compact block announcements unless sendcmpct is sent.
@@ -149,7 +144,10 @@ class CompactBlocksTest(BitcoinTestFramework):
# are made with compact blocks.
# If old_node is passed in, request compact blocks with version=preferred-1
# and verify that it receives block announcements via compact block.
- def test_sendcmpct(self, node, test_node, preferred_version, old_node=None):
+ def test_sendcmpct(self, test_node, old_node=None):
+ preferred_version = test_node.cmpct_version
+ node = self.nodes[0]
+
# Make sure we get a SENDCMPCT message from our peer
def received_sendcmpct():
return (len(test_node.last_sendcmpct) > 0)
@@ -251,23 +249,18 @@ class CompactBlocksTest(BitcoinTestFramework):
# This index will be too high
prefilled_txn = PrefilledTransaction(1, block.vtx[0])
cmpct_block.prefilled_txn = [prefilled_txn]
- self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block))
+ self.segwit_node.send_await_disconnect(msg_cmpctblock(cmpct_block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock)
# Compare the generated shortids to what we expect based on BIP 152, given
# bitcoind's choice of nonce.
- def test_compactblock_construction(self, node, test_node, version, use_witness_address):
+ def test_compactblock_construction(self, test_node, use_witness_address=True):
+ version = test_node.cmpct_version
+ node = self.nodes[0]
# Generate a bunch of transactions.
node.generate(101)
num_transactions = 25
address = node.getnewaddress()
- if use_witness_address:
- # Want at least one segwit spend, so move all funds to
- # a witness address.
- address = node.getnewaddress(address_type='bech32')
- value_to_send = node.getbalance()
- node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1)))
- node.generate(1)
segwit_tx_generated = False
for i in range(num_transactions):
@@ -285,7 +278,7 @@ class CompactBlocksTest(BitcoinTestFramework):
test_node.wait_for_block_announcement(tip)
# Make sure we will receive a fast-announce compact block
- self.request_cb_announcements(test_node, node, version)
+ self.request_cb_announcements(test_node)
# Now mine a block, and look at the resulting compact block.
test_node.clear_block_announcement()
@@ -375,7 +368,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# Post-segwit: upgraded nodes would only make this request of cb-version-2,
# NODE_WITNESS peers. Unupgraded nodes would still make this request of
# any cb-version-1-supporting peer.
- def test_compactblock_requests(self, node, test_node, version, segwit):
+ def test_compactblock_requests(self, test_node, segwit=True):
+ version = test_node.cmpct_version
+ node = self.nodes[0]
# Try announcing a block with an inv or header, expect a compactblock
# request
for announce in ["inv", "header"]:
@@ -440,7 +435,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test that we only receive getblocktxn requests for transactions that the
# node needs, and that responding to them causes the block to be
# reconstructed.
- def test_getblocktxn_requests(self, node, test_node, version):
+ def test_getblocktxn_requests(self, test_node):
+ version = test_node.cmpct_version
+ node = self.nodes[0]
with_witness = (version == 2)
def test_getblocktxn_response(compact_block, peer, expected_result):
@@ -523,9 +520,9 @@ class CompactBlocksTest(BitcoinTestFramework):
# Incorrectly responding to a getblocktxn shouldn't cause the block to be
# permanently failed.
- def test_incorrect_blocktxn_response(self, node, test_node, version):
- if (len(self.utxos) == 0):
- self.make_utxos()
+ def test_incorrect_blocktxn_response(self, test_node):
+ version = test_node.cmpct_version
+ node = self.nodes[0]
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
@@ -579,7 +576,9 @@ class CompactBlocksTest(BitcoinTestFramework):
test_node.send_and_ping(msg_block(block))
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
- def test_getblocktxn_handler(self, node, test_node, version):
+ def test_getblocktxn_handler(self, test_node):
+ version = test_node.cmpct_version
+ node = self.nodes[0]
# bitcoind will not send blocktxn responses for blocks whose height is
# more than 10 blocks deep.
MAX_GETBLOCKTXN_DEPTH = 10
@@ -626,7 +625,8 @@ class CompactBlocksTest(BitcoinTestFramework):
assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16))
assert "blocktxn" not in test_node.last_message
- def test_compactblocks_not_at_tip(self, node, test_node):
+ def test_compactblocks_not_at_tip(self, test_node):
+ node = self.nodes[0]
# Test that requesting old compactblocks doesn't work.
MAX_CMPCTBLOCK_DEPTH = 5
new_blocks = []
@@ -681,11 +681,8 @@ class CompactBlocksTest(BitcoinTestFramework):
with mininode_lock:
assert "blocktxn" not in test_node.last_message
- def activate_segwit(self, node):
- node.generate(144 * 3)
- assert_equal(get_bip9_status(node, "segwit")["status"], 'active')
-
- def test_end_to_end_block_relay(self, node, listeners):
+ def test_end_to_end_block_relay(self, listeners):
+ node = self.nodes[0]
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
@@ -706,7 +703,8 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test that we don't get disconnected if we relay a compact block with valid header,
# but invalid transactions.
- def test_invalid_tx_in_compactblock(self, node, test_node, use_segwit):
+ def test_invalid_tx_in_compactblock(self, test_node, use_segwit=True):
+ node = self.nodes[0]
assert len(self.utxos)
utxo = self.utxos[0]
@@ -733,16 +731,18 @@ class CompactBlocksTest(BitcoinTestFramework):
# Helper for enabling cb announcements
# Send the sendcmpct request and sync headers
- def request_cb_announcements(self, peer, node, version):
+ def request_cb_announcements(self, peer):
+ node = self.nodes[0]
tip = node.getbestblockhash()
peer.get_headers(locator=[int(tip, 16)], hashstop=0)
msg = msg_sendcmpct()
- msg.version = version
+ msg.version = peer.cmpct_version
msg.announce = True
peer.send_and_ping(msg)
- def test_compactblock_reconstruction_multiple_peers(self, node, stalling_peer, delivery_peer):
+ def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer):
+ node = self.nodes[0]
assert len(self.utxos)
def announce_cmpct_block(node, peer):
@@ -793,126 +793,55 @@ class CompactBlocksTest(BitcoinTestFramework):
def run_test(self):
# Setup the p2p connections
- self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn())
- self.segwit_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK | NODE_WITNESS)
- self.old_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK)
+ self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
+ self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK)
+ self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
# We will need UTXOs to construct transactions in later tests.
self.make_utxos()
- self.log.info("Running tests, pre-segwit activation:")
+ assert_equal(get_bip9_status(self.nodes[0], "segwit")["status"], 'active')
self.log.info("Testing SENDCMPCT p2p message... ")
- self.test_sendcmpct(self.nodes[0], self.test_node, 1)
- sync_blocks(self.nodes)
- self.test_sendcmpct(self.nodes[1], self.segwit_node, 2, old_node=self.old_node)
- sync_blocks(self.nodes)
-
- self.log.info("Testing compactblock construction...")
- self.test_compactblock_construction(self.nodes[0], self.test_node, 1, False)
- sync_blocks(self.nodes)
- self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, False)
- sync_blocks(self.nodes)
-
- self.log.info("Testing compactblock requests... ")
- self.test_compactblock_requests(self.nodes[0], self.test_node, 1, False)
- sync_blocks(self.nodes)
- self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, False)
- sync_blocks(self.nodes)
-
- self.log.info("Testing getblocktxn requests...")
- self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1)
- sync_blocks(self.nodes)
- self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2)
- sync_blocks(self.nodes)
-
- self.log.info("Testing getblocktxn handler...")
- self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1)
- sync_blocks(self.nodes)
- self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2)
- self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1)
- sync_blocks(self.nodes)
-
- self.log.info("Testing compactblock requests/announcements not at chain tip...")
- self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node)
- sync_blocks(self.nodes)
- self.test_compactblocks_not_at_tip(self.nodes[1], self.segwit_node)
- self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node)
- sync_blocks(self.nodes)
-
- self.log.info("Testing handling of incorrect blocktxn responses...")
- self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1)
- sync_blocks(self.nodes)
- self.test_incorrect_blocktxn_response(self.nodes[1], self.segwit_node, 2)
- sync_blocks(self.nodes)
-
- # End-to-end block relay tests
- self.log.info("Testing end-to-end block relay...")
- self.request_cb_announcements(self.test_node, self.nodes[0], 1)
- self.request_cb_announcements(self.old_node, self.nodes[1], 1)
- self.request_cb_announcements(self.segwit_node, self.nodes[1], 2)
- self.test_end_to_end_block_relay(self.nodes[0], [self.segwit_node, self.test_node, self.old_node])
- self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node])
-
- self.log.info("Testing handling of invalid compact blocks...")
- self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node, False)
- self.test_invalid_tx_in_compactblock(self.nodes[1], self.segwit_node, False)
- self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node, False)
-
- self.log.info("Testing reconstructing compact blocks from all peers...")
- self.test_compactblock_reconstruction_multiple_peers(self.nodes[1], self.segwit_node, self.old_node)
- sync_blocks(self.nodes)
-
- # Advance to segwit activation
- self.log.info("Advancing to segwit activation")
- self.activate_segwit(self.nodes[1])
- self.log.info("Running tests, post-segwit activation...")
+ self.test_sendcmpct(self.segwit_node, old_node=self.old_node)
+ self.test_sendcmpct(self.additional_segwit_node)
self.log.info("Testing compactblock construction...")
- self.test_compactblock_construction(self.nodes[1], self.old_node, 1, True)
- self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, True)
- sync_blocks(self.nodes)
-
- self.log.info("Testing compactblock requests (unupgraded node)... ")
- self.test_compactblock_requests(self.nodes[0], self.test_node, 1, True)
-
- self.log.info("Testing getblocktxn requests (unupgraded node)...")
- self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1)
-
- # Need to manually sync node0 and node1, because post-segwit activation,
- # node1 will not download blocks from node0.
- self.log.info("Syncing nodes...")
- assert self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()
- while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()):
- block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount() + 1)
- self.nodes[1].submitblock(self.nodes[0].getblock(block_hash, False))
- assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
+ self.test_compactblock_construction(self.old_node)
+ self.test_compactblock_construction(self.segwit_node)
self.log.info("Testing compactblock requests (segwit node)... ")
- self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, True)
+ self.test_compactblock_requests(self.segwit_node)
self.log.info("Testing getblocktxn requests (segwit node)...")
- self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2)
- sync_blocks(self.nodes)
+ self.test_getblocktxn_requests(self.segwit_node)
self.log.info("Testing getblocktxn handler (segwit node should return witnesses)...")
- self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2)
- self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1)
+ self.test_getblocktxn_handler(self.segwit_node)
+ self.test_getblocktxn_handler(self.old_node)
+
+ self.log.info("Testing compactblock requests/announcements not at chain tip...")
+ self.test_compactblocks_not_at_tip(self.segwit_node)
+ self.test_compactblocks_not_at_tip(self.old_node)
+
+ self.log.info("Testing handling of incorrect blocktxn responses...")
+ self.test_incorrect_blocktxn_response(self.segwit_node)
+
+ self.log.info("Testing reconstructing compact blocks from all peers...")
+ self.test_compactblock_reconstruction_multiple_peers(self.segwit_node, self.additional_segwit_node)
# Test that if we submitblock to node1, we'll get a compact block
# announcement to all peers.
# (Post-segwit activation, blocks won't propagate from node0 to node1
# automatically, so don't bother testing a block announced to node0.)
self.log.info("Testing end-to-end block relay...")
- self.request_cb_announcements(self.test_node, self.nodes[0], 1)
- self.request_cb_announcements(self.old_node, self.nodes[1], 1)
- self.request_cb_announcements(self.segwit_node, self.nodes[1], 2)
- self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node])
+ self.request_cb_announcements(self.old_node)
+ self.request_cb_announcements(self.segwit_node)
+ self.test_end_to_end_block_relay([self.segwit_node, self.old_node])
self.log.info("Testing handling of invalid compact blocks...")
- self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node, False)
- self.test_invalid_tx_in_compactblock(self.nodes[1], self.segwit_node, True)
- self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node, True)
+ self.test_invalid_tx_in_compactblock(self.segwit_node)
+ self.test_invalid_tx_in_compactblock(self.old_node)
self.log.info("Testing invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message()
diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py
index 1140fe9b3e..4ba6ac1db2 100644
--- a/test/functional/test_framework/authproxy.py
+++ b/test/functional/test_framework/authproxy.py
@@ -35,6 +35,7 @@ ServiceProxy class:
import base64
import decimal
+from http import HTTPStatus
import http.client
import json
import logging
@@ -49,13 +50,14 @@ USER_AGENT = "AuthServiceProxy/0.1"
log = logging.getLogger("BitcoinRPC")
class JSONRPCException(Exception):
- def __init__(self, rpc_error):
+ def __init__(self, rpc_error, http_status=None):
try:
errmsg = '%(message)s (%(code)i)' % rpc_error
except (KeyError, TypeError):
errmsg = ''
super().__init__(errmsg)
self.error = rpc_error
+ self.http_status = http_status
def EncodeDecimal(o):
@@ -120,8 +122,11 @@ class AuthServiceProxy():
def get_request(self, *args, **argsn):
AuthServiceProxy.__id_count += 1
- log.debug("-%s-> %s %s" % (AuthServiceProxy.__id_count, self._service_name,
- json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
+ log.debug("-{}-> {} {}".format(
+ AuthServiceProxy.__id_count,
+ self._service_name,
+ json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii),
+ ))
if args and argsn:
raise ValueError('Cannot handle both named and positional arguments')
return {'version': '1.1',
@@ -131,19 +136,26 @@ class AuthServiceProxy():
def __call__(self, *args, **argsn):
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
- response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
+ response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
if response['error'] is not None:
- raise JSONRPCException(response['error'])
+ raise JSONRPCException(response['error'], status)
elif 'result' not in response:
raise JSONRPCException({
- 'code': -343, 'message': 'missing JSON-RPC result'})
+ 'code': -343, 'message': 'missing JSON-RPC result'}, status)
+ elif status != HTTPStatus.OK:
+ raise JSONRPCException({
+ 'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
else:
return response['result']
def batch(self, rpc_call_list):
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
log.debug("--> " + postdata)
- return self._request('POST', self.__url.path, postdata.encode('utf-8'))
+ response, status = self._request('POST', self.__url.path, postdata.encode('utf-8'))
+ if status != HTTPStatus.OK:
+ raise JSONRPCException({
+ 'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status)
+ return response
def _get_response(self):
req_start_time = time.time()
@@ -162,8 +174,9 @@ class AuthServiceProxy():
content_type = http_response.getheader('Content-Type')
if content_type != 'application/json':
- raise JSONRPCException({
- 'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
+ raise JSONRPCException(
+ {'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)},
+ http_response.status)
responsedata = http_response.read().decode('utf8')
response = json.loads(responsedata, parse_float=decimal.Decimal)
@@ -172,7 +185,7 @@ class AuthServiceProxy():
log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
else:
log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
- return response
+ return response, http_response.status
def __truediv__(self, relative_uri):
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index ec5d3b267e..8b2006a05c 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -369,7 +369,7 @@ class TestNode():
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0
if not sys.platform.startswith('linux'):
- self.log.warning("Can't profile with perf; only availabe on Linux platforms")
+ self.log.warning("Can't profile with perf; only available on Linux platforms")
return None
if not test_success('which perf'):
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index f771a37b99..190301b215 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -323,12 +323,14 @@ def get_auth_cookie(datadir):
if line.startswith("rpcpassword="):
assert password is None # Ensure that there is only one rpcpassword line
password = line.split("=")[1].strip("\n")
- if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")) and os.access(os.path.join(datadir, "regtest", ".cookie"), os.R_OK):
+ try:
with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f:
userpass = f.read()
split_userpass = userpass.split(':')
user = split_userpass[0]
password = split_userpass[1]
+ except OSError:
+ pass
if user is None or password is None:
raise ValueError("No RPC credentials")
return user, password
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 5bfbaa2f0b..81c650f4c1 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -629,7 +629,8 @@ class ImportMultiTest(BitcoinTestFramework):
self.log.info("Should import a 1-of-2 bare multisig from descriptor")
self.test_importmulti({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
"timestamp": "now"},
- success=True)
+ success=True,
+ warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
test_address(self.nodes[1],
key1.p2pkh_addr,
@@ -759,7 +760,7 @@ class ImportMultiTest(BitcoinTestFramework):
assert_equal(addr2, newaddr2)
# Import a multisig and make sure the keys don't go into the keypool
- self.log.info('Imported scripts with pubkeys shoud not have their pubkeys go into the keypool')
+ self.log.info('Imported scripts with pubkeys should not have their pubkeys go into the keypool')
addr1 = self.nodes[0].getnewaddress()
addr2 = self.nodes[0].getnewaddress()
pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index 8aafa94c2e..ec5e230e5a 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -39,6 +39,12 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
self.log.info("Create a new transaction and wait until it's broadcast")
txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16)
+ # Wallet rebroadcast is first scheduled 1 sec after startup (see
+ # nNextResend in ResendWalletTransactions()). Sleep for just over a
+ # second to be certain that it has been called before the first
+ # setmocktime call below.
+ time.sleep(1.1)
+
# Can take a few seconds due to transaction trickling
wait_until(lambda: node.p2p.tx_invs_received[txid] >= 1, lock=mininode_lock)