aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml5
-rw-r--r--build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj2
-rwxr-xr-xci/lint/04_install.sh10
-rw-r--r--ci/test/00_setup_env_mac.sh3
-rwxr-xr-xci/test/05_before_script.sh11
-rwxr-xr-xcontrib/gitian-build.py2
-rw-r--r--contrib/gitian-descriptors/gitian-osx.yml4
-rw-r--r--contrib/macdeploy/README.md65
-rwxr-xr-xcontrib/macdeploy/gen-sdk94
-rw-r--r--depends/hosts/darwin.mk12
-rw-r--r--depends/packages/native_cctools.mk12
-rw-r--r--doc/release-notes-11413.md11
-rw-r--r--doc/release-notes-18594.md5
-rw-r--r--doc/release-notes-19133.md7
-rw-r--r--doc/release-notes.md5
-rw-r--r--src/.clang-format5
-rw-r--r--src/Makefile.am1
-rw-r--r--src/Makefile.qt.include4
-rw-r--r--src/Makefile.test.include7
-rw-r--r--src/bitcoin-cli.cpp138
-rw-r--r--src/core_write.cpp12
-rw-r--r--src/interfaces/wallet.cpp5
-rw-r--r--src/interfaces/wallet.h3
-rw-r--r--src/net_processing.cpp12
-rw-r--r--src/policy/feerate.cpp9
-rw-r--r--src/policy/feerate.h14
-rw-r--r--src/policy/fees.h7
-rw-r--r--src/psbt.cpp11
-rw-r--r--src/psbt.h3
-rw-r--r--src/pubkey.h3
-rw-r--r--src/qt/bitcoingui.cpp10
-rw-r--r--src/qt/bitcoingui.h5
-rw-r--r--src/qt/forms/debugwindow.ui6
-rw-r--r--src/qt/forms/psbtoperationsdialog.ui148
-rw-r--r--src/qt/psbtoperationsdialog.cpp268
-rw-r--r--src/qt/psbtoperationsdialog.h54
-rw-r--r--src/qt/sendcoinsdialog.cpp2
-rw-r--r--src/qt/transactionrecord.cpp1
-rw-r--r--src/qt/transactiontablemodel.cpp4
-rw-r--r--src/qt/walletframe.cpp4
-rw-r--r--src/qt/walletframe.h2
-rw-r--r--src/qt/walletmodel.cpp9
-rw-r--r--src/qt/walletmodel.h3
-rw-r--r--src/qt/walletview.cpp97
-rw-r--r--src/qt/walletview.h2
-rw-r--r--src/rest.cpp8
-rw-r--r--src/rpc/blockchain.cpp6
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/rpc/mining.cpp20
-rw-r--r--src/rpc/mining.h11
-rw-r--r--src/rpc/misc.cpp4
-rw-r--r--src/rpc/rawtransaction.cpp4
-rw-r--r--src/rpc/rawtransaction_util.cpp4
-rw-r--r--src/rpc/util.cpp4
-rw-r--r--src/scheduler.cpp42
-rw-r--r--src/scheduler.h95
-rw-r--r--src/script/descriptor.cpp4
-rw-r--r--src/test/coins_tests.cpp2
-rw-r--r--src/test/descriptor_tests.cpp2
-rw-r--r--src/test/fuzz/crypto.cpp124
-rw-r--r--src/test/fuzz/decode_tx.cpp2
-rw-r--r--src/test/fuzz/fuzz.cpp16
-rw-r--r--src/test/fuzz/key.cpp2
-rw-r--r--src/test/key_tests.cpp44
-rw-r--r--src/test/netbase_tests.cpp3
-rw-r--r--src/test/scheduler_tests.cpp6
-rw-r--r--src/test/sighash_tests.cpp2
-rw-r--r--src/test/util/setup_common.cpp1
-rw-r--r--src/test/util/setup_common.h1
-rw-r--r--src/util/error.cpp6
-rw-r--r--src/util/fees.cpp40
-rw-r--r--src/util/fees.h1
-rw-r--r--src/wallet/rpcdump.cpp4
-rw-r--r--src/wallet/rpcwallet.cpp164
-rw-r--r--src/wallet/scriptpubkeyman.cpp26
-rw-r--r--src/wallet/scriptpubkeyman.h6
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp2
-rw-r--r--src/wallet/test/wallet_crypto_tests.cpp4
-rw-r--r--src/wallet/wallet.cpp12
-rw-r--r--src/wallet/wallet.h3
-rw-r--r--test/README.md8
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py18
-rwxr-xr-xtest/functional/feature_loadblock.py3
-rwxr-xr-xtest/functional/feature_logging.py3
-rwxr-xr-xtest/functional/feature_pruning.py12
-rwxr-xr-xtest/functional/feature_segwit.py3
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py102
-rwxr-xr-xtest/functional/p2p_addr_relay.py4
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py3
-rwxr-xr-xtest/functional/p2p_feefilter.py61
-rwxr-xr-xtest/functional/p2p_invalid_messages.py75
-rwxr-xr-xtest/functional/p2p_permissions.py6
-rwxr-xr-xtest/functional/p2p_segwit.py3
-rwxr-xr-xtest/functional/rpc_getblockfilter.py4
-rwxr-xr-xtest/functional/rpc_psbt.py2
-rwxr-xr-xtest/functional/test_framework/messages.py4
-rwxr-xr-xtest/functional/test_framework/mininode.py4
-rwxr-xr-xtest/functional/test_framework/test_framework.py73
-rw-r--r--test/functional/test_framework/util.py44
-rwxr-xr-xtest/functional/wallet_abandonconflict.py9
-rwxr-xr-xtest/functional/wallet_avoidreuse.py4
-rwxr-xr-xtest/functional/wallet_balance.py3
-rwxr-xr-xtest/functional/wallet_basic.py122
-rwxr-xr-xtest/functional/wallet_bumpfee.py26
-rwxr-xr-xtest/functional/wallet_dump.py3
-rwxr-xr-xtest/functional/wallet_hd.py6
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py3
-rwxr-xr-xtest/functional/wallet_zapwallettxes.py15
-rwxr-xr-xtest/lint/lint-python.sh1
-rwxr-xr-xtest/lint/lint-shell.sh1
-rw-r--r--test/lint/lint-spelling.ignore-words.txt1
111 files changed, 1795 insertions, 605 deletions
diff --git a/.travis.yml b/.travis.yml
index 9ab2227116..edec60afba 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -117,6 +117,11 @@ jobs:
FILE_ENV="./ci/test/00_setup_env_native_multiprocess.sh"
- stage: test
+ name: 'x86_64 Linux [GOAL: install] [focal] [no depends, only system libs, fuzzers under valgrind]'
+ env: >-
+ FILE_ENV="./ci/test/00_setup_env_native_fuzz_with_valgrind.sh"
+
+ - stage: test
name: 'x86_64 Linux [GOAL: install] [xenial] [no wallet]'
env: >-
FILE_ENV="./ci/test/00_setup_env_native_nowallet.sh"
diff --git a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj
index 992f64ec2e..6a3c9f1dc1 100644
--- a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj
+++ b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj
@@ -35,6 +35,7 @@
<ClCompile Include="..\..\src\qt\paymentserver.cpp" />
<ClCompile Include="..\..\src\qt\peertablemodel.cpp" />
<ClCompile Include="..\..\src\qt\platformstyle.cpp" />
+ <ClCompile Include="..\..\src\qt\psbtoperationsdialog.cpp" />
<ClCompile Include="..\..\src\qt\qrimagewidget.cpp" />
<ClCompile Include="..\..\src\qt\qvalidatedlineedit.cpp" />
<ClCompile Include="..\..\src\qt\qvaluecombobox.cpp" />
@@ -87,6 +88,7 @@
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_paymentserver.cpp" />
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_peertablemodel.cpp" />
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_platformstyle.cpp" />
+ <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_psbtoperationsdialog.cpp" />
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qrimagewidget.cpp" />
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qvalidatedlineedit.cpp" />
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_qvaluecombobox.cpp" />
diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh
index 26b576c1ae..80d0df4a78 100755
--- a/ci/lint/04_install.sh
+++ b/ci/lint/04_install.sh
@@ -6,11 +6,11 @@
export LC_ALL=C
-travis_retry pip3 install codespell==1.15.0
-travis_retry pip3 install flake8==3.7.8
+travis_retry pip3 install codespell==1.17.1
+travis_retry pip3 install flake8==3.8.3
travis_retry pip3 install yq
-travis_retry pip3 install mypy==0.700
+travis_retry pip3 install mypy==0.781
-SHELLCHECK_VERSION=v0.6.0
-curl -s "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/
+SHELLCHECK_VERSION=v0.7.1
+curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/
export PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}"
diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh
index a4dc54d1c1..45a29928cb 100644
--- a/ci/test/00_setup_env_mac.sh
+++ b/ci/test/00_setup_env_mac.sh
@@ -9,7 +9,8 @@ export LC_ALL=C.UTF-8
export CONTAINER_NAME=ci_macos_cross
export HOST=x86_64-apple-darwin16
export PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python3-dev python3-setuptools"
-export OSX_SDK=10.14
+export XCODE_VERSION=11.3.1
+export XCODE_BUILD_ID=11C505
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh
index 3685504524..de33881419 100755
--- a/ci/test/05_before_script.sh
+++ b/ci/test/05_before_script.sh
@@ -15,11 +15,14 @@ fi
DOCKER_EXEC mkdir -p ${DEPENDS_DIR}/SDKs ${DEPENDS_DIR}/sdk-sources
-if [ -n "$OSX_SDK" ] && [ ! -f ${DEPENDS_DIR}/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then
- curl --location --fail $SDK_URL/MacOSX${OSX_SDK}.sdk.tar.gz -o ${DEPENDS_DIR}/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz
+OSX_SDK_BASENAME="Xcode-${XCODE_VERSION}-${XCODE_BUILD_ID}-extracted-SDK-with-libcxx-headers.tar.gz"
+OSX_SDK_PATH="${DEPENDS_DIR}/sdk-sources/${OSX_SDK_BASENAME}"
+
+if [ -n "$XCODE_VERSION" ] && [ ! -f "$OSX_SDK_PATH" ]; then
+ curl --location --fail "${SDK_URL}/${OSX_SDK_BASENAME}" -o "$OSX_SDK_PATH"
fi
-if [ -n "$OSX_SDK" ] && [ -f ${DEPENDS_DIR}/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then
- DOCKER_EXEC tar -C ${DEPENDS_DIR}/SDKs -xf ${DEPENDS_DIR}/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz
+if [ -n "$XCODE_VERSION" ] && [ -f "$OSX_SDK_PATH" ]; then
+ DOCKER_EXEC tar -C "${DEPENDS_DIR}/SDKs" -xf "$OSX_SDK_PATH"
fi
if [[ $HOST = *-mingw32 ]]; then
DOCKER_EXEC update-alternatives --set $HOST-g++ \$\(which $HOST-g++-posix\)
diff --git a/contrib/gitian-build.py b/contrib/gitian-build.py
index 4a3df93cea..d498c9e2c8 100755
--- a/contrib/gitian-build.py
+++ b/contrib/gitian-build.py
@@ -209,7 +209,7 @@ def main():
args.macos = 'm' in args.os
# Disable for MacOS if no SDK found
- if args.macos and not os.path.isfile('gitian-builder/inputs/MacOSX10.14.sdk.tar.gz'):
+ if args.macos and not os.path.isfile('gitian-builder/inputs/Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz'):
print('Cannot build for MacOS, SDK does not exist. Will build for other OSes')
args.macos = False
diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml
index bbae7201e5..e0aaafc15a 100644
--- a/contrib/gitian-descriptors/gitian-osx.yml
+++ b/contrib/gitian-descriptors/gitian-osx.yml
@@ -32,7 +32,7 @@ remotes:
- "url": "https://github.com/bitcoin/bitcoin.git"
"dir": "bitcoin"
files:
-- "MacOSX10.14.sdk.tar.gz"
+- "Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz"
script: |
set -e -o pipefail
@@ -90,7 +90,7 @@ script: |
BASEPREFIX="${PWD}/depends"
mkdir -p ${BASEPREFIX}/SDKs
- tar -C ${BASEPREFIX}/SDKs -xf ${BUILD_DIR}/MacOSX10.14.sdk.tar.gz
+ tar -C ${BASEPREFIX}/SDKs -xf ${BUILD_DIR}/Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz
# Build dependencies for each host
for i in $HOSTS; do
diff --git a/contrib/macdeploy/README.md b/contrib/macdeploy/README.md
index 68ebb5def1..fe677e3a1f 100644
--- a/contrib/macdeploy/README.md
+++ b/contrib/macdeploy/README.md
@@ -14,55 +14,44 @@ When complete, it will have produced `Bitcoin-Qt.dmg`.
## SDK Extraction
-Our current macOS SDK (`macOSX10.14.sdk`) can be extracted from
-[Xcode_10.2.1.xip](https://download.developer.apple.com/Developer_Tools/Xcode_10.2.1/Xcode_10.2.1.xip).
+### Step 1: Obtaining `Xcode.app`
+
+Our current macOS SDK
+(`Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz`) can be
+extracted from
+[Xcode_11.3.1.xip](https://download.developer.apple.com/Developer_Tools/Xcode_11.3.1/Xcode_11.3.1.xip).
An Apple ID is needed to download this.
-`Xcode.app` is packaged in a `.xip` archive.
-This makes the SDK less-trivial to extract on non-macOS machines.
-One approach (tested on Debian Buster) is outlined below:
+After Xcode version 7.x, Apple started shipping the `Xcode.app` in a `.xip`
+archive. This makes the SDK less-trivial to extract on non-macOS machines. One
+approach (tested on Debian Buster) is outlined below:
```bash
+# Install/clone tools needed for extracting Xcode.app
+apt install cpio
+git clone https://github.com/bitcoin-core/apple-sdk-tools.git
-apt install clang cpio git liblzma-dev libxml2-dev libssl-dev make
-
-git clone https://github.com/tpoechtrager/xar
-pushd xar/xar
-./configure
-make
-make install
-popd
-
-git clone https://github.com/NiklasRosenstein/pbzx
-pushd pbzx
-clang -llzma -lxar pbzx.c -o pbzx -Wl,-rpath=/usr/local/lib
-popd
-
-xar -xf Xcode_10.2.1.xip -C .
-
-./pbzx/pbzx -n Content | cpio -i
-
-find Xcode.app -type d -name MacOSX.sdk -exec sh -c 'tar --transform="s/MacOSX.sdk/MacOSX10.14.sdk/" -c -C$(dirname {}) MacOSX.sdk/ | gzip -9n > MacOSX10.14.sdk.tar.gz' \;
+# Unpack Xcode_11.3.1.xip and place the resulting Xcode.app in your current
+# working directory
+python3 apple-sdk-tools/extract_xcode.py -f Xcode_11.3.1.xip | cpio -d -i
```
-on macOS the process is more straightforward:
+On macOS the process is more straightforward:
```bash
-xip -x Xcode_10.2.1.xip
-tar -s "/MacOSX.sdk/MacOSX10.14.sdk/" -C Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ -czf MacOSX10.14.sdk.tar.gz MacOSX.sdk
+xip -x Xcode_11.3.1.xip
```
-Our previously used macOS SDK (`MacOSX10.11.sdk`) can be extracted from
-[Xcode 7.3.1 dmg](https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_7.3.1/Xcode_7.3.1.dmg).
-The script [`extract-osx-sdk.sh`](./extract-osx-sdk.sh) automates this. First
-ensure the DMG file is in the current directory, and then run the script. You
-may wish to delete the `intermediate 5.hfs` file and `MacOSX10.11.sdk` (the
-directory) when you've confirmed the extraction succeeded.
+### Step 2: Generating `Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz` from `Xcode.app`
+
+To generate `Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz`, run
+the script [`gen-sdk`](./gen-sdk) with the path to `Xcode.app` (extracted in the
+previous stage) as the first argument.
```bash
-apt-get install p7zip-full sleuthkit
-contrib/macdeploy/extract-osx-sdk.sh
-rm -rf 5.hfs MacOSX10.11.sdk
+# Generate a Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers.tar.gz from
+# the supplied Xcode.app
+./contrib/macdeploy/gen-sdk '/path/to/Xcode.app'
```
## Deterministic macOS DMG Notes
@@ -91,13 +80,13 @@ and its `libLTO.so` rather than those from `llvmgcc`, as it was originally done
To complicate things further, all builds must target an Apple SDK. These SDKs are free to
download, but not redistributable. To obtain it, register for an Apple Developer Account,
-then download [Xcode 10.2.1](https://download.developer.apple.com/Developer_Tools/Xcode_10.2.1/Xcode_10.2.1.xip).
+then download [Xcode_11.3.1](https://download.developer.apple.com/Developer_Tools/Xcode_11.3.1/Xcode_11.3.1.xip).
This file is many gigabytes in size, but most (but not all) of what we need is
contained only in a single directory:
```bash
-Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk
+Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
```
See the SDK Extraction notes above for how to obtain it.
diff --git a/contrib/macdeploy/gen-sdk b/contrib/macdeploy/gen-sdk
new file mode 100755
index 0000000000..457d8f5e64
--- /dev/null
+++ b/contrib/macdeploy/gen-sdk
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+import argparse
+import plistlib
+import pathlib
+import sys
+import tarfile
+import gzip
+import os
+import contextlib
+
+@contextlib.contextmanager
+def cd(path):
+ """Context manager that restores PWD even if an exception was raised."""
+ old_pwd = os.getcwd()
+ os.chdir(str(path))
+ try:
+ yield
+ finally:
+ os.chdir(old_pwd)
+
+def run():
+ parser = argparse.ArgumentParser(
+ description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
+
+ parser.add_argument('xcode_app', metavar='XCODEAPP', nargs=1)
+ parser.add_argument("-o", metavar='OUTSDKTGZ', nargs=1, dest='out_sdktgz', required=False)
+
+ args = parser.parse_args()
+
+ xcode_app = pathlib.Path(args.xcode_app[0]).resolve()
+ assert xcode_app.is_dir(), "The supplied Xcode.app path '{}' either does not exist or is not a directory".format(xcode_app)
+
+ xcode_app_plist = xcode_app.joinpath("Contents/version.plist")
+ with xcode_app_plist.open('rb') as fp:
+ pl = plistlib.load(fp)
+ xcode_version = pl['CFBundleShortVersionString']
+ xcode_build_id = pl['ProductBuildVersion']
+ print("Found Xcode (version: {xcode_version}, build id: {xcode_build_id})".format(xcode_version=xcode_version, xcode_build_id=xcode_build_id))
+
+ sdk_dir = xcode_app.joinpath("Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk")
+ sdk_plist = sdk_dir.joinpath("System/Library/CoreServices/SystemVersion.plist")
+ with sdk_plist.open('rb') as fp:
+ pl = plistlib.load(fp)
+ sdk_version = pl['ProductVersion']
+ sdk_build_id = pl['ProductBuildVersion']
+ print("Found MacOSX SDK (version: {sdk_version}, build id: {sdk_build_id})".format(sdk_version=sdk_version, sdk_build_id=sdk_build_id))
+
+ out_name = "Xcode-{xcode_version}-{xcode_build_id}-extracted-SDK-with-libcxx-headers".format(xcode_version=xcode_version, xcode_build_id=xcode_build_id)
+
+ xcode_libcxx_dir = xcode_app.joinpath("Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1")
+ assert xcode_libcxx_dir.is_dir()
+
+ if args.out_sdktgz:
+ out_sdktgz_path = pathlib.Path(args.out_sdktgz_path)
+ else:
+ # Construct our own out_sdktgz if not specified on the command line
+ out_sdktgz_path = pathlib.Path("./{}.tar.gz".format(out_name))
+
+ def tarfp_add_with_base_change(tarfp, dir_to_add, alt_base_dir):
+ """Add all files in dir_to_add to tarfp, but prepent MEMBERPREFIX to the files'
+ names
+
+ e.g. if the only file under /root/bazdir is /root/bazdir/qux, invoking:
+
+ tarfp_add_with_base_change(tarfp, "foo/bar", "/root/bazdir")
+
+ would result in the following members being added to tarfp:
+
+ foo/bar/ -> corresponding to /root/bazdir
+ foo/bar/qux -> corresponding to /root/bazdir/qux
+
+ """
+ def change_tarinfo_base(tarinfo):
+ if tarinfo.name and tarinfo.name.startswith("./"):
+ tarinfo.name = str(pathlib.Path(alt_base_dir, tarinfo.name))
+ if tarinfo.linkname and tarinfo.linkname.startswith("./"):
+ tarinfo.linkname = str(pathlib.Path(alt_base_dir, tarinfo.linkname))
+ return tarinfo
+ with cd(dir_to_add):
+ tarfp.add(".", recursive=True, filter=change_tarinfo_base)
+
+ print("Creating output .tar.gz file...")
+ with out_sdktgz_path.open("wb") as fp:
+ with gzip.GzipFile(fileobj=fp, compresslevel=9, mtime=0) as gzf:
+ with tarfile.open(mode="w", fileobj=gzf) as tarfp:
+ print("Adding MacOSX SDK {} files...".format(sdk_version))
+ tarfp_add_with_base_change(tarfp, sdk_dir, out_name)
+ print("Adding libc++ headers...")
+ tarfp_add_with_base_change(tarfp, xcode_libcxx_dir, "{}/usr/include/c++/v1".format(out_name))
+ print("Done! Find the resulting gzipped tarball at:")
+ print(out_sdktgz_path.resolve())
+
+if __name__ == '__main__':
+ run()
diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk
index 82e086a326..5f0bffa5cb 100644
--- a/depends/hosts/darwin.mk
+++ b/depends/hosts/darwin.mk
@@ -1,8 +1,12 @@
OSX_MIN_VERSION=10.12
-OSX_SDK_VERSION=10.14
-OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk
-darwin_CC=clang -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK)
-darwin_CXX=clang++ -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -stdlib=libc++
+OSX_SDK_VERSION=10.15.1
+XCODE_VERSION=11.3.1
+XCODE_BUILD_ID=11C505
+LD64_VERSION=530
+
+OSX_SDK=$(SDK_PATH)/Xcode-$(XCODE_VERSION)-$(XCODE_BUILD_ID)-extracted-SDK-with-libcxx-headers
+darwin_CC=clang -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
+darwin_CXX=clang++ -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -stdlib=libc++ -mlinker-version=$(LD64_VERSION)
darwin_CFLAGS=-pipe
darwin_CXXFLAGS=$(darwin_CFLAGS)
diff --git a/depends/packages/native_cctools.mk b/depends/packages/native_cctools.mk
index 4195230b40..bdebd11862 100644
--- a/depends/packages/native_cctools.mk
+++ b/depends/packages/native_cctools.mk
@@ -1,14 +1,14 @@
package=native_cctools
-$(package)_version=3764b223c011574971ee3ae09ce968ba5dc2f00f
+$(package)_version=4da2f3b485bcf4cef526f30c0b8c0bcda99cdbb4
$(package)_download_path=https://github.com/tpoechtrager/cctools-port/archive
$(package)_file_name=$($(package)_version).tar.gz
-$(package)_sha256_hash=3e35907bf376269a844df08e03cbb43e345c88125374f2228e03724b5f9a2a04
+$(package)_sha256_hash=a2d491c0981cef72fee2b833598f20f42a6c44a7614a61c439bda93d56446fec
$(package)_build_subdir=cctools
-$(package)_clang_version=6.0.1
+$(package)_clang_version=8.0.0
$(package)_clang_download_path=https://releases.llvm.org/$($(package)_clang_version)
$(package)_clang_download_file=clang+llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz
$(package)_clang_file_name=clang-llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz
-$(package)_clang_sha256_hash=fa5416553ca94a8c071a27134c094a5fb736fe1bd0ecc5ef2d9bc02754e1bef0
+$(package)_clang_sha256_hash=9ef854b71949f825362a119bf2597f744836cb571131ae6b721cd102ffea8cd0
$(package)_libtapi_version=3efb201881e7a76a21e0554906cf306432539cef
$(package)_libtapi_download_path=https://github.com/tpoechtrager/apple-libtapi/archive
@@ -72,7 +72,5 @@ define $(package)_stage_cmds
cp -P bin/clang++ $($(package)_staging_prefix_dir)/bin/ &&\
cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \
cp -rf lib/clang/$($(package)_clang_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_clang_version)/include/ && \
- cp bin/llvm-dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil && \
- if `test -d include/c++/`; then cp -rf include/c++/ $($(package)_staging_prefix_dir)/include/; fi && \
- if `test -d lib/c++/`; then cp -rf lib/c++/ $($(package)_staging_prefix_dir)/lib/; fi
+ cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil
endef
diff --git a/doc/release-notes-11413.md b/doc/release-notes-11413.md
new file mode 100644
index 0000000000..32735e37f6
--- /dev/null
+++ b/doc/release-notes-11413.md
@@ -0,0 +1,11 @@
+Updated or changed RPC
+----------------------
+
+The `bumpfee`, `fundrawtransaction`, `sendmany`, `sendtoaddress`, and `walletcreatefundedpsbt`
+RPC commands have been updated to include two new fee estimation methods "BTC/kB" and "sat/B".
+The target is the fee expressed explicitly in the given form. Note that use of this feature
+will trigger BIP 125 (replace-by-fee) opt-in.
+
+In addition, the `estimate_mode` parameter is now case insensitive for all of the above RPC commands.
+
+The `bumpfee` command now uses `conf_target` rather than `confTarget` in the options.
diff --git a/doc/release-notes-18594.md b/doc/release-notes-18594.md
new file mode 100644
index 0000000000..6a2ef0a67c
--- /dev/null
+++ b/doc/release-notes-18594.md
@@ -0,0 +1,5 @@
+## CLI
+
+The `bitcoin-cli -getinfo` command now displays the wallet name and balance for
+each of the loaded wallets when more than one is loaded (e.g. in multiwallet
+mode) and a wallet is not specified with `-rpcwallet`. (#18594)
diff --git a/doc/release-notes-19133.md b/doc/release-notes-19133.md
new file mode 100644
index 0000000000..5150fbe1c7
--- /dev/null
+++ b/doc/release-notes-19133.md
@@ -0,0 +1,7 @@
+## CLI
+
+A new `bitcoin-cli -generate` command, equivalent to RPC `generatenewaddress`
+followed by `generatetoaddress`, can generate blocks for command line testing
+purposes. This is a client-side version of the
+[former](https://github.com/bitcoin/bitcoin/issues/14299) `generate` RPC. See
+the help for details. (#19133)
diff --git a/doc/release-notes.md b/doc/release-notes.md
index d9d0ecd631..e73bedfb10 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -45,6 +45,11 @@ wallet versions of Bitcoin Core are generally supported.
Compatibility
==============
+During this release cycle, work has been done to ensure that the codebase is fully
+compatible with C++17. The intention is to begin using C++17 features starting
+with the 0.22.0 release. This means that a compiler that supports C++17 will be
+required to compile 0.22.0.
+
Bitcoin Core is supported and extensively tested on operating systems
using the Linux kernel, macOS 10.12+, and Windows 7 and newer. Bitcoin
Core should also work on most other Unix-like systems but is not as
diff --git a/src/.clang-format b/src/.clang-format
index aae039dd77..a8f8565f80 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -1,9 +1,10 @@
Language: Cpp
AccessModifierOffset: -4
-AlignAfterOpenBracket: false
+AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: true
AlignTrailingComments: true
-AllowAllParametersOfDeclarationOnNextLine: false
+AllowAllArgumentsOnNextLine : true
+AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
diff --git a/src/Makefile.am b/src/Makefile.am
index a33ff8a461..632ed3e31f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -184,6 +184,7 @@ BITCOIN_CORE_H = \
reverse_iterator.h \
rpc/blockchain.h \
rpc/client.h \
+ rpc/mining.h \
rpc/protocol.h \
rpc/rawtransaction_util.h \
rpc/register.h \
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 13bfea7646..1f66516172 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -25,6 +25,7 @@ QT_FORMS_UI = \
qt/forms/openuridialog.ui \
qt/forms/optionsdialog.ui \
qt/forms/overviewpage.ui \
+ qt/forms/psbtoperationsdialog.ui \
qt/forms/receivecoinsdialog.ui \
qt/forms/receiverequestdialog.ui \
qt/forms/debugwindow.ui \
@@ -61,6 +62,7 @@ QT_MOC_CPP = \
qt/moc_overviewpage.cpp \
qt/moc_peertablemodel.cpp \
qt/moc_paymentserver.cpp \
+ qt/moc_psbtoperationsdialog.cpp \
qt/moc_qrimagewidget.cpp \
qt/moc_qvalidatedlineedit.cpp \
qt/moc_qvaluecombobox.cpp \
@@ -132,6 +134,7 @@ BITCOIN_QT_H = \
qt/paymentserver.h \
qt/peertablemodel.h \
qt/platformstyle.h \
+ qt/psbtoperationsdialog.h \
qt/qrimagewidget.h \
qt/qvalidatedlineedit.h \
qt/qvaluecombobox.h \
@@ -245,6 +248,7 @@ BITCOIN_QT_WALLET_CPP = \
qt/openuridialog.cpp \
qt/overviewpage.cpp \
qt/paymentserver.cpp \
+ qt/psbtoperationsdialog.cpp \
qt/qrimagewidget.cpp \
qt/receivecoinsdialog.cpp \
qt/receiverequestdialog.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 03cd9133c8..472382c7d2 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -32,6 +32,7 @@ FUZZ_TARGETS = \
test/fuzz/checkqueue \
test/fuzz/coins_deserialize \
test/fuzz/coins_view \
+ test/fuzz/crypto \
test/fuzz/crypto_common \
test/fuzz/cuckoocache \
test/fuzz/decode_tx \
@@ -479,6 +480,12 @@ test_fuzz_coins_view_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_coins_view_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_coins_view_SOURCES = test/fuzz/coins_view.cpp
+test_fuzz_crypto_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_crypto_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_crypto_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_crypto_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_crypto_SOURCES = test/fuzz/crypto.cpp
+
test_fuzz_crypto_common_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_crypto_common_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_crypto_common_LDADD = $(FUZZ_SUITE_LD_COMMON)
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 8d85789b4e..f5125f22db 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -11,6 +11,7 @@
#include <clientversion.h>
#include <optional.h>
#include <rpc/client.h>
+#include <rpc/mining.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <util/strencodings.h>
@@ -39,6 +40,9 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
static const bool DEFAULT_NAMED=false;
static const int CONTINUE_EXECUTION=-1;
+/** Default number of blocks to generate for RPC generatetoaddress. */
+static const std::string DEFAULT_NBLOCKS = "1";
+
static void SetupCliArgs()
{
SetupHelpOptions(gArgs);
@@ -50,6 +54,7 @@ static void SetupCliArgs()
gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ gArgs.AddArg("-generate", strprintf("Generate blocks immediately, equivalent to RPC generatenewaddress followed by RPC generatetoaddress. Optional positional integer arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to RPC generatetoaddress nblocks and maxtries arguments. Example: bitcoin-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
SetupChainParamsBaseOptions();
gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -286,6 +291,28 @@ public:
}
};
+/** Process RPC generatetoaddress request. */
+class GenerateToAddressRequestHandler : public BaseRequestHandler
+{
+public:
+ UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
+ {
+ address_str = args.at(1);
+ UniValue params{RPCConvertValues("generatetoaddress", args)};
+ return JSONRPCRequestObj("generatetoaddress", params, 1);
+ }
+
+ UniValue ProcessReply(const UniValue &reply) override
+ {
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", address_str);
+ result.pushKV("blocks", reply.get_obj()["result"]);
+ return JSONRPCReplyObj(result, NullUniValue, 1);
+ }
+protected:
+ std::string address_str;
+};
+
/** Process default single requests */
class DefaultRequestHandler: public BaseRequestHandler {
public:
@@ -453,6 +480,34 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
return response;
}
+/** Parse UniValue result to update the message to print to std::cout. */
+static void ParseResult(const UniValue& result, std::string& strPrint)
+{
+ if (result.isNull()) return;
+ strPrint = result.isStr() ? result.get_str() : result.write(2);
+}
+
+/** Parse UniValue error to update the message to print to std::cerr and the code to return. */
+static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
+{
+ if (error.isObject()) {
+ const UniValue& err_code = find_value(error, "code");
+ const UniValue& err_msg = find_value(error, "message");
+ if (!err_code.isNull()) {
+ strPrint = "error code: " + err_code.getValStr() + "\n";
+ }
+ if (err_msg.isStr()) {
+ strPrint += ("error message:\n" + err_msg.get_str());
+ }
+ if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) {
+ strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
+ }
+ } else {
+ strPrint = "error: " + error.write();
+ }
+ nRet = abs(error["code"].get_int());
+}
+
/**
* GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
* fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
@@ -477,6 +532,34 @@ static void GetWalletBalances(UniValue& result)
result.pushKV("balances", balances);
}
+/**
+ * Call RPC getnewaddress.
+ * @returns getnewaddress response as a UniValue object.
+ */
+static UniValue GetNewAddress()
+{
+ Optional<std::string> wallet_name{};
+ if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
+ std::unique_ptr<BaseRequestHandler> rh{MakeUnique<DefaultRequestHandler>()};
+ return ConnectAndCallRPC(rh.get(), "getnewaddress", /* args=*/{}, wallet_name);
+}
+
+/**
+ * Check bounds and set up args for RPC generatetoaddress params: nblocks, address, maxtries.
+ * @param[in] address Reference to const string address to insert into the args.
+ * @param args Reference to vector of string args to modify.
+ */
+static void SetGenerateToAddressArgs(const std::string& address, std::vector<std::string>& args)
+{
+ if (args.size() > 2) throw std::runtime_error("too many arguments (maximum 2 for nblocks and maxtries)");
+ if (args.size() == 0) {
+ args.emplace_back(DEFAULT_NBLOCKS);
+ } else if (args.at(0) == "0") {
+ throw std::runtime_error("the first argument (number of blocks to generate, default: " + DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
+ }
+ args.emplace(args.begin() + 1, address);
+}
+
static int CommandLineRPC(int argc, char *argv[])
{
std::string strPrint;
@@ -535,6 +618,15 @@ static int CommandLineRPC(int argc, char *argv[])
std::string method;
if (gArgs.IsArgSet("-getinfo")) {
rh.reset(new GetinfoRequestHandler());
+ } else if (gArgs.GetBoolArg("-generate", false)) {
+ const UniValue getnewaddress{GetNewAddress()};
+ const UniValue& error{find_value(getnewaddress, "error")};
+ if (error.isNull()) {
+ SetGenerateToAddressArgs(find_value(getnewaddress, "result").get_str(), args);
+ rh.reset(new GenerateToAddressRequestHandler());
+ } else {
+ ParseError(error, strPrint, nRet);
+ }
} else {
rh.reset(new DefaultRequestHandler());
if (args.size() < 1) {
@@ -543,40 +635,22 @@ static int CommandLineRPC(int argc, char *argv[])
method = args[0];
args.erase(args.begin()); // Remove trailing method name from arguments vector
}
- Optional<std::string> wallet_name{};
- if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
- const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
-
- // Parse reply
- UniValue result = find_value(reply, "result");
- const UniValue& error = find_value(reply, "error");
- if (!error.isNull()) {
- // Error
- strPrint = "error: " + error.write();
- nRet = abs(error["code"].get_int());
- if (error.isObject()) {
- const UniValue& errCode = find_value(error, "code");
- const UniValue& errMsg = find_value(error, "message");
- strPrint = errCode.isNull() ? "" : ("error code: " + errCode.getValStr() + "\n");
-
- if (errMsg.isStr()) {
- strPrint += ("error message:\n" + errMsg.get_str());
- }
- if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
- strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
+ if (nRet == 0) {
+ // Perform RPC call
+ Optional<std::string> wallet_name{};
+ if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
+ const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
+
+ // Parse reply
+ UniValue result = find_value(reply, "result");
+ const UniValue& error = find_value(reply, "error");
+ if (error.isNull()) {
+ if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
+ GetWalletBalances(result); // fetch multiwallet balances and append to result
}
- }
- } else {
- if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
- GetWalletBalances(result); // fetch multiwallet balances and append to result
- }
- // Result
- if (result.isNull()) {
- strPrint = "";
- } else if (result.isStr()) {
- strPrint = result.get_str();
+ ParseResult(result, strPrint);
} else {
- strPrint = result.write(2);
+ ParseError(error, strPrint, nRet);
}
}
} catch (const std::exception& e) {
diff --git a/src/core_write.cpp b/src/core_write.cpp
index 503640042b..69b62df901 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -131,13 +131,13 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags)
{
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | serializeFlags);
ssTx << tx;
- return HexStr(ssTx.begin(), ssTx.end());
+ return HexStr(ssTx);
}
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address)
{
out.pushKV("asm", ScriptToAsmStr(script));
- out.pushKV("hex", HexStr(script.begin(), script.end()));
+ out.pushKV("hex", HexStr(script));
std::vector<std::vector<unsigned char>> solns;
TxoutType type = Solver(script, solns);
@@ -158,7 +158,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey,
out.pushKV("asm", ScriptToAsmStr(scriptPubKey));
if (fIncludeHex)
- out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ out.pushKV("hex", HexStr(scriptPubKey));
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired) || type == TxoutType::PUBKEY) {
out.pushKV("type", GetTxnOutputType(type));
@@ -190,19 +190,19 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
const CTxIn& txin = tx.vin[i];
UniValue in(UniValue::VOBJ);
if (tx.IsCoinBase())
- in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()));
+ in.pushKV("coinbase", HexStr(txin.scriptSig));
else {
in.pushKV("txid", txin.prevout.hash.GetHex());
in.pushKV("vout", (int64_t)txin.prevout.n);
UniValue o(UniValue::VOBJ);
o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true));
- o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()));
+ o.pushKV("hex", HexStr(txin.scriptSig));
in.pushKV("scriptSig", o);
}
if (!tx.vin[i].scriptWitness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
for (const auto& item : tx.vin[i].scriptWitness.stack) {
- txinwitness.push_back(HexStr(item.begin(), item.end()));
+ txinwitness.push_back(HexStr(item));
}
in.pushKV("txinwitness", txinwitness);
}
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index 397403d308..b65eb72b1c 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -335,9 +335,10 @@ public:
bool sign,
bool bip32derivs,
PartiallySignedTransaction& psbtx,
- bool& complete) override
+ bool& complete,
+ size_t* n_signed) override
{
- return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs);
+ return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed);
}
WalletBalances getBalances() override
{
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 67569a3e55..e2161521f6 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -197,7 +197,8 @@ public:
bool sign,
bool bip32derivs,
PartiallySignedTransaction& psbtx,
- bool& complete) = 0;
+ bool& complete,
+ size_t* n_signed) = 0;
//! Get balances.
virtual WalletBalances getBalances() = 0;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index d7fbf6941d..b5912e18a7 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -189,7 +189,7 @@ namespace {
* We use this to avoid requesting transactions that have already been
* confirnmed.
*/
- RecursiveMutex g_cs_recent_confirmed_transactions;
+ Mutex g_cs_recent_confirmed_transactions;
std::unique_ptr<CRollingBloomFilter> g_recent_confirmed_transactions GUARDED_BY(g_cs_recent_confirmed_transactions);
/** Blocks that are in flight, and that are in the queue to be downloaded. */
@@ -2454,7 +2454,7 @@ void ProcessMessage(
if (vAddr.size() > 1000)
{
LOCK(cs_main);
- Misbehaving(pfrom.GetId(), 20, strprintf("message addr size() = %u", vAddr.size()));
+ Misbehaving(pfrom.GetId(), 20, strprintf("addr message size = %u", vAddr.size()));
return;
}
@@ -2530,7 +2530,7 @@ void ProcessMessage(
if (vInv.size() > MAX_INV_SZ)
{
LOCK(cs_main);
- Misbehaving(pfrom.GetId(), 20, strprintf("message inv size() = %u", vInv.size()));
+ Misbehaving(pfrom.GetId(), 20, strprintf("inv message size = %u", vInv.size()));
return;
}
@@ -2596,7 +2596,7 @@ void ProcessMessage(
if (vInv.size() > MAX_INV_SZ)
{
LOCK(cs_main);
- Misbehaving(pfrom.GetId(), 20, strprintf("message getdata size() = %u", vInv.size()));
+ Misbehaving(pfrom.GetId(), 20, strprintf("getdata message size = %u", vInv.size()));
return;
}
@@ -4387,9 +4387,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
//
// Message: feefilter
//
- // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay
if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
- !pto->HasPermission(PF_FORCERELAY)) {
+ !pto->HasPermission(PF_FORCERELAY) // peers with the forcerelay permission should not filter txs to us
+ ) {
CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
int64_t timeNow = GetTimeMicros();
if (timeNow > pto->m_tx_relay->nextSendTimeFeeFilter) {
diff --git a/src/policy/feerate.cpp b/src/policy/feerate.cpp
index 14be6192fe..a01e259731 100644
--- a/src/policy/feerate.cpp
+++ b/src/policy/feerate.cpp
@@ -7,8 +7,6 @@
#include <tinyformat.h>
-const std::string CURRENCY_UNIT = "BTC";
-
CFeeRate::CFeeRate(const CAmount& nFeePaid, size_t nBytes_)
{
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
@@ -37,7 +35,10 @@ CAmount CFeeRate::GetFee(size_t nBytes_) const
return nFee;
}
-std::string CFeeRate::ToString() const
+std::string CFeeRate::ToString(const FeeEstimateMode& fee_estimate_mode) const
{
- return strprintf("%d.%08d %s/kB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
+ switch (fee_estimate_mode) {
+ case FeeEstimateMode::SAT_B: return strprintf("%d.%03d %s/B", nSatoshisPerK / 1000, nSatoshisPerK % 1000, CURRENCY_ATOM);
+ default: return strprintf("%d.%08d %s/kB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
+ }
}
diff --git a/src/policy/feerate.h b/src/policy/feerate.h
index 61fa80c130..883940f73c 100644
--- a/src/policy/feerate.h
+++ b/src/policy/feerate.h
@@ -11,7 +11,17 @@
#include <string>
-extern const std::string CURRENCY_UNIT;
+const std::string CURRENCY_UNIT = "BTC"; // One formatted unit
+const std::string CURRENCY_ATOM = "sat"; // One indivisible minimum value unit
+
+/* Used to determine type of fee estimation requested */
+enum class FeeEstimateMode {
+ UNSET, //!< Use default settings based on other criteria
+ ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
+ CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
+ BTC_KB, //!< Use explicit BTC/kB fee given in coin control
+ SAT_B, //!< Use explicit sat/B fee given in coin control
+};
/**
* Fee rate in satoshis per kilobyte: CAmount / kB
@@ -46,7 +56,7 @@ public:
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
- std::string ToString() const;
+ std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::BTC_KB) const;
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); }
};
diff --git a/src/policy/fees.h b/src/policy/fees.h
index 6ee6e0d547..e445c1590d 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -45,13 +45,6 @@ enum class FeeReason {
REQUIRED,
};
-/* Used to determine type of fee estimation requested */
-enum class FeeEstimateMode {
- UNSET, //!< Use default settings based on other criteria
- ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
- CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
-};
-
/* Used to return detailed information about a feerate bucket */
struct EstimatorBucket
{
diff --git a/src/psbt.cpp b/src/psbt.cpp
index ef9781817a..10260740f0 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -214,6 +214,17 @@ bool PSBTInputSigned(const PSBTInput& input)
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
}
+size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
+ size_t count = 0;
+ for (const auto& input : psbt.inputs) {
+ if (!PSBTInputSigned(input)) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index)
{
const CTxOut& out = psbt.tx->vout.at(index);
diff --git a/src/psbt.h b/src/psbt.h
index 888e0fd119..0a8ea2ea0b 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -579,6 +579,9 @@ 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);
+/** Counts the unsigned inputs of a PSBT. */
+size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
+
/** Updates a PSBTOutput with information from provider.
*
* This fills in the redeem_script, witness_script, and hd_keypaths where possible.
diff --git a/src/pubkey.h b/src/pubkey.h
index 261842b7f7..4c28af4a4d 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -142,6 +142,9 @@ public:
unsigned int len = ::ReadCompactSize(s);
if (len <= SIZE) {
s.read((char*)vch, len);
+ if (len != size()) {
+ Invalidate();
+ }
} else {
// invalid pubkey, skip available data
char dummy;
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 1092cdd754..b4182e8524 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -321,8 +321,10 @@ void BitcoinGUI::createActions()
signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
verifyMessageAction = new QAction(tr("&Verify message..."), this);
verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
- m_load_psbt_action = new QAction(tr("Load PSBT..."), this);
+ m_load_psbt_action = new QAction(tr("&Load PSBT from file..."), this);
m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction"));
+ m_load_psbt_clipboard_action = new QAction(tr("Load PSBT from clipboard..."), this);
+ m_load_psbt_clipboard_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction from clipboard"));
openRPCConsoleAction = new QAction(tr("Node window"), this);
openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console"));
@@ -381,6 +383,7 @@ void BitcoinGUI::createActions()
connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); });
connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); });
+ connect(m_load_psbt_clipboard_action, &QAction::triggered, [this]{ gotoLoadPSBT(true); });
connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); });
connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses);
@@ -459,6 +462,7 @@ void BitcoinGUI::createMenuBar()
file->addAction(signMessageAction);
file->addAction(verifyMessageAction);
file->addAction(m_load_psbt_action);
+ file->addAction(m_load_psbt_clipboard_action);
file->addSeparator();
}
file->addAction(quitAction);
@@ -878,9 +882,9 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr)
{
if (walletFrame) walletFrame->gotoVerifyMessageTab(addr);
}
-void BitcoinGUI::gotoLoadPSBT()
+void BitcoinGUI::gotoLoadPSBT(bool from_clipboard)
{
- if (walletFrame) walletFrame->gotoLoadPSBT();
+ if (walletFrame) walletFrame->gotoLoadPSBT(from_clipboard);
}
#endif // ENABLE_WALLET
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index b009e279b6..697e83e772 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -139,6 +139,7 @@ private:
QAction* signMessageAction = nullptr;
QAction* verifyMessageAction = nullptr;
QAction* m_load_psbt_action = nullptr;
+ QAction* m_load_psbt_clipboard_action = nullptr;
QAction* aboutAction = nullptr;
QAction* receiveCoinsAction = nullptr;
QAction* receiveCoinsMenuAction = nullptr;
@@ -278,8 +279,8 @@ public Q_SLOTS:
void gotoSignMessageTab(QString addr = "");
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
- /** Show load Partially Signed Bitcoin Transaction dialog */
- void gotoLoadPSBT();
+ /** Load Partially Signed Bitcoin Transaction from file or clipboard */
+ void gotoLoadPSBT(bool from_clipboard = false);
/** Show open dialog */
void openClicked();
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 8b70800838..1217ca3e2e 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -1078,12 +1078,6 @@
<height>426</height>
</rect>
</property>
- <property name="minimumSize">
- <size>
- <width>300</width>
- <height>0</height>
- </size>
- </property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1">
<item row="0" column="0">
<widget class="QLabel" name="label_30">
diff --git a/src/qt/forms/psbtoperationsdialog.ui b/src/qt/forms/psbtoperationsdialog.ui
new file mode 100644
index 0000000000..c2e2f5035b
--- /dev/null
+++ b/src/qt/forms/psbtoperationsdialog.ui
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PSBTOperationsDialog</class>
+ <widget class="QDialog" name="PSBTOperationsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>585</width>
+ <height>327</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>12</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="bottomMargin">
+ <number>12</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" name="mainDialogLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="statusBar">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextEdit" name="transactionDescription">
+ <property name="undoRedoEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRowLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="signTransactionButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>50</weight>
+ <bold>false</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Sign Tx</string>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ <property name="default">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="broadcastTransactionButton">
+ <property name="text">
+ <string>Broadcast Tx</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="copyToClipboardButton">
+ <property name="text">
+ <string>Copy to Clipboard</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveButton">
+ <property name="text">
+ <string>Save...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp
new file mode 100644
index 0000000000..58167d4bb4
--- /dev/null
+++ b/src/qt/psbtoperationsdialog.cpp
@@ -0,0 +1,268 @@
+// Copyright (c) 2011-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <qt/psbtoperationsdialog.h>
+
+#include <core_io.h>
+#include <interfaces/node.h>
+#include <key_io.h>
+#include <node/psbt.h>
+#include <policy/policy.h>
+#include <qt/bitcoinunits.h>
+#include <qt/forms/ui_psbtoperationsdialog.h>
+#include <qt/guiutil.h>
+#include <qt/optionsmodel.h>
+#include <util/strencodings.h>
+
+#include <iostream>
+
+
+PSBTOperationsDialog::PSBTOperationsDialog(
+ QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent),
+ m_ui(new Ui::PSBTOperationsDialog),
+ m_wallet_model(wallet_model),
+ m_client_model(client_model)
+{
+ m_ui->setupUi(this);
+ setWindowTitle("PSBT Operations");
+
+ connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);
+ connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);
+ connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);
+ connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);
+
+ connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);
+
+ m_ui->signTransactionButton->setEnabled(false);
+ m_ui->broadcastTransactionButton->setEnabled(false);
+}
+
+PSBTOperationsDialog::~PSBTOperationsDialog()
+{
+ delete m_ui;
+}
+
+void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
+{
+ m_transaction_data = psbtx;
+
+ bool complete;
+ size_t n_could_sign;
+ FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
+ TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_could_sign);
+ if (err != TransactionError::OK) {
+ showStatus(tr("Failed to load transaction: %1")
+ .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
+ return;
+ }
+
+ m_ui->broadcastTransactionButton->setEnabled(complete);
+ m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);
+
+ updateTransactionDisplay();
+}
+
+void PSBTOperationsDialog::signTransaction()
+{
+ bool complete;
+ size_t n_signed;
+ TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_signed);
+
+ if (err != TransactionError::OK) {
+ showStatus(tr("Failed to sign transaction: %1")
+ .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
+ return;
+ }
+
+ updateTransactionDisplay();
+
+ if (!complete && n_signed < 1) {
+ showStatus(tr("Could not sign any more inputs."), StatusLevel::WARN);
+ } else if (!complete) {
+ showStatus(tr("Signed %1 inputs, but more signatures are still required.").arg(n_signed),
+ StatusLevel::INFO);
+ } else {
+ showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),
+ StatusLevel::INFO);
+ m_ui->broadcastTransactionButton->setEnabled(true);
+ }
+}
+
+void PSBTOperationsDialog::broadcastTransaction()
+{
+ CMutableTransaction mtx;
+ if (!FinalizeAndExtractPSBT(m_transaction_data, mtx)) {
+ // This is never expected to fail unless we were given a malformed PSBT
+ // (e.g. with an invalid signature.)
+ showStatus(tr("Unknown error processing transaction."), StatusLevel::ERR);
+ return;
+ }
+
+ CTransactionRef tx = MakeTransactionRef(mtx);
+ std::string err_string;
+ TransactionError error = BroadcastTransaction(
+ *m_client_model->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* await_callback */ false);
+
+ if (error == TransactionError::OK) {
+ showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")
+ .arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::INFO);
+ } else {
+ showStatus(tr("Transaction broadcast failed: %1")
+ .arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::ERR);
+ }
+}
+
+void PSBTOperationsDialog::copyToClipboard() {
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << m_transaction_data;
+ GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
+ showStatus(tr("PSBT copied to clipboard."), StatusLevel::INFO);
+}
+
+void PSBTOperationsDialog::saveTransaction() {
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << m_transaction_data;
+
+ QString selected_filter;
+ QString filename_suggestion = "";
+ bool first = true;
+ for (const CTxOut& out : m_transaction_data.tx->vout) {
+ if (!first) {
+ filename_suggestion.append("-");
+ }
+ CTxDestination address;
+ ExtractDestination(out.scriptPubKey, address);
+ QString amount = BitcoinUnits::format(m_wallet_model->getOptionsModel()->getDisplayUnit(), out.nValue);
+ QString address_str = QString::fromStdString(EncodeDestination(address));
+ filename_suggestion.append(address_str + "-" + amount);
+ first = false;
+ }
+ filename_suggestion.append(".psbt");
+ QString filename = GUIUtil::getSaveFileName(this,
+ tr("Save Transaction Data"), filename_suggestion,
+ tr("Partially Signed Transaction (Binary) (*.psbt)"), &selected_filter);
+ if (filename.isEmpty()) {
+ return;
+ }
+ std::ofstream out(filename.toLocal8Bit().data());
+ out << ssTx.str();
+ out.close();
+ showStatus(tr("PSBT saved to disk."), StatusLevel::INFO);
+}
+
+void PSBTOperationsDialog::updateTransactionDisplay() {
+ m_ui->transactionDescription->setText(QString::fromStdString(renderTransaction(m_transaction_data)));
+ showTransactionStatus(m_transaction_data);
+}
+
+std::string PSBTOperationsDialog::renderTransaction(const PartiallySignedTransaction &psbtx)
+{
+ QString tx_description = "";
+ CAmount totalAmount = 0;
+ for (const CTxOut& out : psbtx.tx->vout) {
+ CTxDestination address;
+ ExtractDestination(out.scriptPubKey, address);
+ totalAmount += out.nValue;
+ tx_description.append(tr(" * Sends %1 to %2")
+ .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, out.nValue))
+ .arg(QString::fromStdString(EncodeDestination(address))));
+ tx_description.append("<br>");
+ }
+
+ PSBTAnalysis analysis = AnalyzePSBT(psbtx);
+ tx_description.append(" * ");
+ if (!*analysis.fee) {
+ // This happens if the transaction is missing input UTXO information.
+ tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));
+ } else {
+ tx_description.append(tr("Pays transaction fee: "));
+ tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, *analysis.fee));
+
+ // add total amount in all subdivision units
+ tx_description.append("<hr />");
+ QStringList alternativeUnits;
+ for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
+ {
+ if(u != m_client_model->getOptionsModel()->getDisplayUnit()) {
+ alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
+ }
+ }
+ tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
+ .arg(BitcoinUnits::formatHtmlWithUnit(m_client_model->getOptionsModel()->getDisplayUnit(), totalAmount)));
+ tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
+ .arg(alternativeUnits.join(" " + tr("or") + " ")));
+ }
+
+ size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);
+ if (num_unsigned > 0) {
+ tx_description.append("<br><br>");
+ tx_description.append(tr("Transaction has %1 unsigned inputs.").arg(QString::number(num_unsigned)));
+ }
+
+ return tx_description.toStdString();
+}
+
+void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {
+ m_ui->statusBar->setText(msg);
+ switch (level) {
+ case StatusLevel::INFO: {
+ m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");
+ break;
+ }
+ case StatusLevel::WARN: {
+ m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");
+ break;
+ }
+ case StatusLevel::ERR: {
+ m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");
+ break;
+ }
+ }
+ m_ui->statusBar->show();
+}
+
+size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) {
+ size_t n_signed;
+ bool complete;
+ TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, m_transaction_data, complete, &n_signed);
+
+ if (err != TransactionError::OK) {
+ return 0;
+ }
+ return n_signed;
+}
+
+void PSBTOperationsDialog::showTransactionStatus(const PartiallySignedTransaction &psbtx) {
+ PSBTAnalysis analysis = AnalyzePSBT(psbtx);
+ size_t n_could_sign = couldSignInputs(psbtx);
+
+ switch (analysis.next) {
+ case PSBTRole::UPDATER: {
+ showStatus(tr("Transaction is missing some information about inputs."), StatusLevel::WARN);
+ break;
+ }
+ case PSBTRole::SIGNER: {
+ QString need_sig_text = tr("Transaction still needs signature(s).");
+ StatusLevel level = StatusLevel::INFO;
+ if (m_wallet_model->wallet().privateKeysDisabled()) {
+ need_sig_text += " " + tr("(But this wallet cannot sign transactions.)");
+ level = StatusLevel::WARN;
+ } else if (n_could_sign < 1) {
+ need_sig_text += " " + tr("(But this wallet does not have the right keys.)"); // XXX wording
+ level = StatusLevel::WARN;
+ }
+ showStatus(need_sig_text, level);
+ break;
+ }
+ case PSBTRole::FINALIZER:
+ case PSBTRole::EXTRACTOR: {
+ showStatus(tr("Transaction is fully signed and ready for broadcast."), StatusLevel::INFO);
+ break;
+ }
+ default: {
+ showStatus(tr("Transaction status is unknown."), StatusLevel::ERR);
+ break;
+ }
+ }
+}
diff --git a/src/qt/psbtoperationsdialog.h b/src/qt/psbtoperationsdialog.h
new file mode 100644
index 0000000000..f37bdbe39a
--- /dev/null
+++ b/src/qt/psbtoperationsdialog.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_QT_PSBTOPERATIONSDIALOG_H
+#define BITCOIN_QT_PSBTOPERATIONSDIALOG_H
+
+#include <QDialog>
+
+#include <psbt.h>
+#include <qt/clientmodel.h>
+#include <qt/walletmodel.h>
+
+namespace Ui {
+class PSBTOperationsDialog;
+}
+
+/** Dialog showing transaction details. */
+class PSBTOperationsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit PSBTOperationsDialog(QWidget* parent, WalletModel* walletModel, ClientModel* clientModel);
+ ~PSBTOperationsDialog();
+
+ void openWithPSBT(PartiallySignedTransaction psbtx);
+
+public Q_SLOTS:
+ void signTransaction();
+ void broadcastTransaction();
+ void copyToClipboard();
+ void saveTransaction();
+
+private:
+ Ui::PSBTOperationsDialog* m_ui;
+ PartiallySignedTransaction m_transaction_data;
+ WalletModel* m_wallet_model;
+ ClientModel* m_client_model;
+
+ enum class StatusLevel {
+ INFO,
+ WARN,
+ ERR
+ };
+
+ size_t couldSignInputs(const PartiallySignedTransaction &psbtx);
+ void updateTransactionDisplay();
+ std::string renderTransaction(const PartiallySignedTransaction &psbtx);
+ void showStatus(const QString &msg, StatusLevel level);
+ void showTransactionStatus(const PartiallySignedTransaction &psbtx);
+};
+
+#endif // BITCOIN_QT_PSBTOPERATIONSDIALOG_H
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 9e23fe78d8..0ac61f3adc 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -392,7 +392,7 @@ void SendCoinsDialog::on_sendButton_clicked()
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete);
+ const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
assert(!complete);
assert(err == TransactionError::OK);
// Serialize the PSBT
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 52007ef350..632a18de5c 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -234,6 +234,7 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
bool TransactionRecord::statusUpdateNeeded(const uint256& block_hash) const
{
+ assert(!block_hash.IsNull());
return status.m_cur_block_hash != block_hash || status.needsUpdate;
}
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index 327a489488..22ba5187bb 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -192,7 +192,7 @@ public:
interfaces::WalletTxStatus wtx;
int numBlocks;
int64_t block_time;
- if (rec->statusUpdateNeeded(cur_block_hash) && wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time)) {
+ if (!cur_block_hash.IsNull() && rec->statusUpdateNeeded(cur_block_hash) && wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time)) {
rec->updateStatus(wtx, cur_block_hash, numBlocks, block_time);
}
return rec;
@@ -664,7 +664,7 @@ QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientat
QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(parent);
- TransactionRecord* data = priv->index(walletModel->wallet(), walletModel->clientModel().getBestBlockHash(), row);
+ TransactionRecord* data = priv->index(walletModel->wallet(), walletModel->getLastBlockProcessed(), row);
if(data)
{
return createIndex(row, column, data);
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp
index 5e68ee4f93..ec56f2755f 100644
--- a/src/qt/walletframe.cpp
+++ b/src/qt/walletframe.cpp
@@ -165,11 +165,11 @@ void WalletFrame::gotoVerifyMessageTab(QString addr)
walletView->gotoVerifyMessageTab(addr);
}
-void WalletFrame::gotoLoadPSBT()
+void WalletFrame::gotoLoadPSBT(bool from_clipboard)
{
WalletView *walletView = currentWalletView();
if (walletView) {
- walletView->gotoLoadPSBT();
+ walletView->gotoLoadPSBT(from_clipboard);
}
}
diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h
index d90ade5005..2b5f263468 100644
--- a/src/qt/walletframe.h
+++ b/src/qt/walletframe.h
@@ -79,7 +79,7 @@ public Q_SLOTS:
void gotoVerifyMessageTab(QString addr = "");
/** Load Partially Signed Bitcoin Transaction */
- void gotoLoadPSBT();
+ void gotoLoadPSBT(bool from_clipboard = false);
/** Encrypt the wallet */
void encryptWallet(bool status);
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 671b5e1ce6..1143969352 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -87,7 +87,7 @@ void WalletModel::pollBalanceChanged()
{
// Avoid recomputing wallet balances unless a TransactionChanged or
// BlockTip notification was received.
- if (!fForceCheckBalanceChanged && m_cached_last_update_tip == m_client_model->getBestBlockHash()) return;
+ if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return;
// Try to get balances and return early if locks can't be acquired. This
// avoids the GUI from getting stuck on periodical polls if the core is
@@ -536,7 +536,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
if (create_psbt) {
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete);
+ const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
if (err != TransactionError::OK || complete) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
return false;
@@ -588,3 +588,8 @@ void WalletModel::refresh(bool pk_hash_only)
{
addressTableModel = new AddressTableModel(this, pk_hash_only);
}
+
+uint256 WalletModel::getLastBlockProcessed() const
+{
+ return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
+}
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 38e8a14556..fd52db2da3 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -155,6 +155,9 @@ public:
AddressTableModel* getAddressTableModel() const { return addressTableModel; }
void refresh(bool pk_hash_only = false);
+
+ uint256 getLastBlockProcessed() const;
+
private:
std::unique_ptr<interfaces::Wallet> m_wallet;
std::unique_ptr<interfaces::Handler> m_handler_unload;
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index 861d1c5f4a..cec9b0eeb8 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -4,13 +4,11 @@
#include <qt/walletview.h>
-#include <node/psbt.h>
-#include <node/transaction.h>
-#include <policy/policy.h>
#include <qt/addressbookpage.h>
#include <qt/askpassphrasedialog.h>
#include <qt/clientmodel.h>
#include <qt/guiutil.h>
+#include <qt/psbtoperationsdialog.h>
#include <qt/optionsmodel.h>
#include <qt/overviewpage.h>
#include <qt/platformstyle.h>
@@ -22,11 +20,14 @@
#include <qt/walletmodel.h>
#include <interfaces/node.h>
+#include <psbt.h>
#include <ui_interface.h>
#include <util/strencodings.h>
#include <QAction>
#include <QActionGroup>
+#include <QApplication>
+#include <QClipboard>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QProgressDialog>
@@ -204,78 +205,42 @@ void WalletView::gotoVerifyMessageTab(QString addr)
signVerifyMessageDialog->setAddress_VM(addr);
}
-void WalletView::gotoLoadPSBT()
+void WalletView::gotoLoadPSBT(bool from_clipboard)
{
- QString filename = GUIUtil::getOpenFileName(this,
- tr("Load Transaction Data"), QString(),
- tr("Partially Signed Transaction (*.psbt)"), nullptr);
- if (filename.isEmpty()) return;
- if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
- Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
- return;
+ std::string data;
+
+ if (from_clipboard) {
+ std::string raw = QApplication::clipboard()->text().toStdString();
+ bool invalid;
+ data = DecodeBase64(raw, &invalid);
+ if (invalid) {
+ Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR);
+ return;
+ }
+ } else {
+ QString filename = GUIUtil::getOpenFileName(this,
+ tr("Load Transaction Data"), QString(),
+ tr("Partially Signed Transaction (*.psbt)"), nullptr);
+ if (filename.isEmpty()) return;
+ if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
+ Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
+ return;
+ }
+ std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
+ data = std::string(std::istreambuf_iterator<char>{in}, {});
}
- std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
- std::string data(std::istreambuf_iterator<char>{in}, {});
std::string error;
PartiallySignedTransaction psbtx;
if (!DecodeRawPSBT(psbtx, data, error)) {
- Q_EMIT message(tr("Error"), tr("Unable to decode PSBT file") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
+ Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
return;
}
- CMutableTransaction mtx;
- bool complete = false;
- PSBTAnalysis analysis = AnalyzePSBT(psbtx);
- QMessageBox msgBox;
- msgBox.setText("PSBT");
- switch (analysis.next) {
- case PSBTRole::CREATOR:
- case PSBTRole::UPDATER:
- msgBox.setInformativeText("PSBT is incomplete. Copy to clipboard for manual inspection?");
- break;
- case PSBTRole::SIGNER:
- msgBox.setInformativeText("Transaction needs more signatures. Copy to clipboard?");
- break;
- case PSBTRole::FINALIZER:
- case PSBTRole::EXTRACTOR:
- complete = FinalizeAndExtractPSBT(psbtx, mtx);
- if (complete) {
- msgBox.setInformativeText(tr("Would you like to send this transaction?"));
- } else {
- // The analyzer missed something, e.g. if there are final_scriptSig/final_scriptWitness
- // but with invalid signatures.
- msgBox.setInformativeText(tr("There was an unexpected problem processing the PSBT. Copy to clipboard for manual inspection?"));
- }
- }
-
- msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
- switch (msgBox.exec()) {
- case QMessageBox::Yes: {
- if (complete) {
- std::string err_string;
- CTransactionRef tx = MakeTransactionRef(mtx);
-
- TransactionError result = BroadcastTransaction(*clientModel->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* wait_callback */ false);
- if (result == TransactionError::OK) {
- Q_EMIT message(tr("Success"), tr("Broadcasted transaction successfully."), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
- } else {
- Q_EMIT message(tr("Error"), QString::fromStdString(err_string), CClientUIInterface::MSG_ERROR);
- }
- } else {
- // Serialize the PSBT
- CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << psbtx;
- GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
- Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION);
- return;
- }
- }
- case QMessageBox::Cancel:
- break;
- default:
- assert(false);
- }
+ PSBTOperationsDialog* dlg = new PSBTOperationsDialog(this, walletModel, clientModel);
+ dlg->openWithPSBT(psbtx);
+ dlg->setAttribute(Qt::WA_DeleteOnClose);
+ dlg->exec();
}
bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
index fd09456baa..f186554758 100644
--- a/src/qt/walletview.h
+++ b/src/qt/walletview.h
@@ -84,7 +84,7 @@ public Q_SLOTS:
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
/** Load Partially Signed Bitcoin Transaction */
- void gotoLoadPSBT();
+ void gotoLoadPSBT(bool from_clipboard = false);
/** Show incoming transaction notification for new transactions.
diff --git a/src/rest.cpp b/src/rest.cpp
index cde8b472d3..8cb594a03b 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -188,7 +188,7 @@ static bool rest_headers(const util::Ref& context,
ssHeader << pindex->GetBlockHeader();
}
- std::string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n";
+ std::string strHex = HexStr(ssHeader) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
@@ -253,7 +253,7 @@ static bool rest_block(HTTPRequest* req,
case RetFormat::HEX: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
- std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
+ std::string strHex = HexStr(ssBlock) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
@@ -391,7 +391,7 @@ static bool rest_tx(const util::Ref& context, HTTPRequest* req, const std::strin
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
- std::string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";
+ std::string strHex = HexStr(ssTx) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
@@ -556,7 +556,7 @@ static bool rest_getutxos(const util::Ref& context, HTTPRequest* req, const std:
case RetFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << ::ChainActive().Height() << ::ChainActive().Tip()->GetBlockHash() << bitmap << outs;
- std::string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n";
+ std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 8252af3ee6..64f8a5bb3b 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -779,7 +779,7 @@ static UniValue getblockheader(const JSONRPCRequest& request)
{
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
- std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
+ std::string strHex = HexStr(ssBlock);
return strHex;
}
@@ -905,7 +905,7 @@ static UniValue getblock(const JSONRPCRequest& request)
{
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
- std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
+ std::string strHex = HexStr(ssBlock);
return strHex;
}
@@ -2159,7 +2159,7 @@ UniValue scantxoutset(const JSONRPCRequest& request)
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.hash.GetHex());
unspent.pushKV("vout", (int32_t)outpoint.n);
- unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
+ unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
unspent.pushKV("height", (int32_t)coin.nHeight);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 3045a74d7a..66ace7263a 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -217,7 +217,7 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal)
UniValue jVal;
if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
!jVal.isArray() || jVal.size()!=1)
- throw std::runtime_error(std::string("Error parsing JSON:")+strVal);
+ throw std::runtime_error(std::string("Error parsing JSON: ") + strVal);
return jVal[0];
}
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index d5fc3f57bb..fee6a893eb 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -17,6 +17,7 @@
#include <policy/fees.h>
#include <pow.h>
#include <rpc/blockchain.h>
+#include <rpc/mining.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/descriptor.h>
@@ -207,7 +208,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
{
{"num_blocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor to send the newly generated bitcoin to."},
- {"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
+ {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
},
RPCResult{
RPCResult::Type::ARR, "", "hashes of blocks generated",
@@ -221,7 +222,7 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
.Check(request);
const int num_blocks{request.params[0].get_int()};
- const int64_t max_tries{request.params[2].isNull() ? 1000000 : request.params[2].get_int()};
+ const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
CScript coinbase_script;
std::string error;
@@ -242,7 +243,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
{
{"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."},
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address to send the newly generated bitcoin to."},
- {"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."},
+ {"maxtries", RPCArg::Type::NUM, /* default */ ToString(DEFAULT_MAX_TRIES), "How many iterations to try."},
},
RPCResult{
RPCResult::Type::ARR, "", "hashes of blocks generated",
@@ -257,11 +258,8 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
},
}.Check(request);
- int nGenerate = request.params[0].get_int();
- uint64_t nMaxTries = 1000000;
- if (!request.params[2].isNull()) {
- nMaxTries = request.params[2].get_int();
- }
+ const int num_blocks{request.params[0].get_int()};
+ const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
CTxDestination destination = DecodeDestination(request.params[1].get_str());
if (!IsValidDestination(destination)) {
@@ -273,7 +271,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
CScript coinbase_script = GetScriptForDestination(destination);
- return generateBlocks(chainman, mempool, coinbase_script, nGenerate, nMaxTries);
+ return generateBlocks(chainman, mempool, coinbase_script, num_blocks, max_tries);
}
static UniValue generateblock(const JSONRPCRequest& request)
@@ -371,7 +369,7 @@ static UniValue generateblock(const JSONRPCRequest& request)
}
uint256 block_hash;
- uint64_t max_tries{1000000};
+ uint64_t max_tries{DEFAULT_MAX_TRIES};
unsigned int extra_nonce{0};
if (!GenerateBlock(EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
@@ -875,7 +873,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
result.pushKV("height", (int64_t)(pindexPrev->nHeight+1));
if (!pblocktemplate->vchCoinbaseCommitment.empty()) {
- result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment.begin(), pblocktemplate->vchCoinbaseCommitment.end()));
+ result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment));
}
return result;
diff --git a/src/rpc/mining.h b/src/rpc/mining.h
new file mode 100644
index 0000000000..acc74e1dcc
--- /dev/null
+++ b/src/rpc/mining.h
@@ -0,0 +1,11 @@
+// Copyright (c) 2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_RPC_MINING_H
+#define BITCOIN_RPC_MINING_H
+
+/** Default max iterations to try in RPC generatetodescriptor, generatetoaddress, and generateblock. */
+static const uint64_t DEFAULT_MAX_TRIES{1000000};
+
+#endif // BITCOIN_RPC_MINING_H
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index ce98a7c937..53d38f4e11 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -63,7 +63,7 @@ static UniValue validateaddress(const JSONRPCRequest& request)
ret.pushKV("address", currentAddress);
CScript scriptPubKey = GetScriptForDestination(dest);
- ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
UniValue detail = DescribeAddress(dest);
ret.pushKVs(detail);
@@ -131,7 +131,7 @@ static UniValue createmultisig(const JSONRPCRequest& request)
UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest));
- result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
+ result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());
return result;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 4acf180779..faec359d1c 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -305,7 +305,7 @@ static UniValue gettxoutproof(const JSONRPCRequest& request)
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
CMerkleBlock mb(block, setTxids);
ssMB << mb;
- std::string strHex = HexStr(ssMB.begin(), ssMB.end());
+ std::string strHex = HexStr(ssMB);
return strHex;
}
@@ -1186,7 +1186,7 @@ UniValue decodepsbt(const JSONRPCRequest& request)
if (!input.final_script_witness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
for (const auto& item : input.final_script_witness.stack) {
- txinwitness.push_back(HexStr(item.begin(), item.end()));
+ txinwitness.push_back(HexStr(item));
}
in.pushKV("final_scriptwitness", txinwitness);
}
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index bd82773bf2..1031716b4a 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -138,10 +138,10 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
entry.pushKV("vout", (uint64_t)txin.prevout.n);
UniValue witness(UniValue::VARR);
for (unsigned int i = 0; i < txin.scriptWitness.stack.size(); i++) {
- witness.push_back(HexStr(txin.scriptWitness.stack[i].begin(), txin.scriptWitness.stack[i].end()));
+ witness.push_back(HexStr(txin.scriptWitness.stack[i]));
}
entry.pushKV("witness", witness);
- entry.pushKV("scriptSig", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()));
+ entry.pushKV("scriptSig", HexStr(txin.scriptSig));
entry.pushKV("sequence", (uint64_t)txin.nSequence);
entry.pushKV("error", strMessage);
vErrorsRet.push_back(entry);
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index e7afef4cac..54ea352a72 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -224,7 +224,7 @@ public:
obj.pushKV("isscript", false);
obj.pushKV("iswitness", true);
obj.pushKV("witness_version", 0);
- obj.pushKV("witness_program", HexStr(id.begin(), id.end()));
+ obj.pushKV("witness_program", HexStr(id));
return obj;
}
@@ -234,7 +234,7 @@ public:
obj.pushKV("isscript", true);
obj.pushKV("iswitness", true);
obj.pushKV("witness_version", 0);
- obj.pushKV("witness_program", HexStr(id.begin(), id.end()));
+ obj.pushKV("witness_program", HexStr(id));
return obj;
}
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index c4bd47310b..7c361bf26f 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -30,9 +30,6 @@ void CScheduler::serviceQueue()
// is called.
while (!shouldStop()) {
try {
- if (!shouldStop() && taskQueue.empty()) {
- REVERSE_LOCK(lock);
- }
while (!shouldStop() && taskQueue.empty()) {
// Wait until there is something to do.
newTaskScheduled.wait(lock);
@@ -71,18 +68,6 @@ void CScheduler::serviceQueue()
newTaskScheduled.notify_one();
}
-void CScheduler::stop(bool drain)
-{
- {
- LOCK(newTaskMutex);
- if (drain)
- stopWhenEmpty = true;
- else
- stopRequested = true;
- }
- newTaskScheduled.notify_all();
-}
-
void CScheduler::schedule(CScheduler::Function f, std::chrono::system_clock::time_point t)
{
{
@@ -125,8 +110,8 @@ void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds
scheduleFromNow([=] { Repeat(*this, f, delta); }, delta);
}
-size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point &first,
- std::chrono::system_clock::time_point &last) const
+size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first,
+ std::chrono::system_clock::time_point& last) const
{
LOCK(newTaskMutex);
size_t result = taskQueue.size();
@@ -137,13 +122,15 @@ size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point &first,
return result;
}
-bool CScheduler::AreThreadsServicingQueue() const {
+bool CScheduler::AreThreadsServicingQueue() const
+{
LOCK(newTaskMutex);
return nThreadsServicingQueue;
}
-void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() {
+void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue()
+{
{
LOCK(m_cs_callbacks_pending);
// Try to avoid scheduling too many copies here, but if we
@@ -155,8 +142,9 @@ void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() {
m_pscheduler->schedule(std::bind(&SingleThreadedSchedulerClient::ProcessQueue, this), std::chrono::system_clock::now());
}
-void SingleThreadedSchedulerClient::ProcessQueue() {
- std::function<void ()> callback;
+void SingleThreadedSchedulerClient::ProcessQueue()
+{
+ std::function<void()> callback;
{
LOCK(m_cs_callbacks_pending);
if (m_are_callbacks_running) return;
@@ -172,7 +160,8 @@ void SingleThreadedSchedulerClient::ProcessQueue() {
struct RAIICallbacksRunning {
SingleThreadedSchedulerClient* instance;
explicit RAIICallbacksRunning(SingleThreadedSchedulerClient* _instance) : instance(_instance) {}
- ~RAIICallbacksRunning() {
+ ~RAIICallbacksRunning()
+ {
{
LOCK(instance->m_cs_callbacks_pending);
instance->m_are_callbacks_running = false;
@@ -184,7 +173,8 @@ void SingleThreadedSchedulerClient::ProcessQueue() {
callback();
}
-void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void ()> func) {
+void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void()> func)
+{
assert(m_pscheduler);
{
@@ -194,7 +184,8 @@ void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void ()> fun
MaybeScheduleProcessQueue();
}
-void SingleThreadedSchedulerClient::EmptyQueue() {
+void SingleThreadedSchedulerClient::EmptyQueue()
+{
assert(!m_pscheduler->AreThreadsServicingQueue());
bool should_continue = true;
while (should_continue) {
@@ -204,7 +195,8 @@ void SingleThreadedSchedulerClient::EmptyQueue() {
}
}
-size_t SingleThreadedSchedulerClient::CallbacksPending() {
+size_t SingleThreadedSchedulerClient::CallbacksPending()
+{
LOCK(m_cs_callbacks_pending);
return m_callbacks_pending.size();
}
diff --git a/src/scheduler.h b/src/scheduler.h
index 1e64195484..d7fe00d1b4 100644
--- a/src/scheduler.h
+++ b/src/scheduler.h
@@ -5,11 +5,6 @@
#ifndef BITCOIN_SCHEDULER_H
#define BITCOIN_SCHEDULER_H
-//
-// NOTE:
-// boost::thread should be ported to std::thread
-// when we support C++11.
-//
#include <condition_variable>
#include <functional>
#include <list>
@@ -17,24 +12,23 @@
#include <sync.h>
-//
-// Simple class for background tasks that should be run
-// periodically or once "after a while"
-//
-// Usage:
-//
-// CScheduler* s = new CScheduler();
-// s->scheduleFromNow(doSomething, std::chrono::milliseconds{11}); // Assuming a: void doSomething() { }
-// s->scheduleFromNow([=] { this->func(argument); }, std::chrono::milliseconds{3});
-// boost::thread* t = new boost::thread(std::bind(CScheduler::serviceQueue, s));
-//
-// ... then at program shutdown, make sure to call stop() to clean up the thread(s) running serviceQueue:
-// s->stop();
-// t->join();
-// delete t;
-// delete s; // Must be done after thread is interrupted/joined.
-//
-
+/**
+ * Simple class for background tasks that should be run
+ * periodically or once "after a while"
+ *
+ * Usage:
+ *
+ * CScheduler* s = new CScheduler();
+ * s->scheduleFromNow(doSomething, std::chrono::milliseconds{11}); // Assuming a: void doSomething() { }
+ * s->scheduleFromNow([=] { this->func(argument); }, std::chrono::milliseconds{3});
+ * std::thread* t = new std::thread([&] { s->serviceQueue(); });
+ *
+ * ... then at program shutdown, make sure to call stop() to clean up the thread(s) running serviceQueue:
+ * s->stop();
+ * t->join();
+ * delete t;
+ * delete s; // Must be done after thread is interrupted/joined.
+ */
class CScheduler
{
public:
@@ -43,7 +37,7 @@ public:
typedef std::function<void()> Function;
- // Call func at/after time t
+ /** Call func at/after time t */
void schedule(Function f, std::chrono::system_clock::time_point t);
/** Call f once after the delta has passed */
@@ -67,23 +61,33 @@ public:
*/
void MockForward(std::chrono::seconds delta_seconds);
- // To keep things as simple as possible, there is no unschedule.
-
- // Services the queue 'forever'. Should be run in a thread,
- // and interrupted using boost::interrupt_thread
+ /**
+ * Services the queue 'forever'. Should be run in a thread,
+ * and interrupted using boost::interrupt_thread
+ */
void serviceQueue();
- // Tell any threads running serviceQueue to stop as soon as they're
- // done servicing whatever task they're currently servicing (drain=false)
- // or when there is no work left to be done (drain=true)
- void stop(bool drain=false);
+ /** Tell any threads running serviceQueue to stop as soon as the current task is done */
+ void stop()
+ {
+ WITH_LOCK(newTaskMutex, stopRequested = true);
+ newTaskScheduled.notify_all();
+ }
+ /** Tell any threads running serviceQueue to stop when there is no work left to be done */
+ void StopWhenDrained()
+ {
+ WITH_LOCK(newTaskMutex, stopWhenEmpty = true);
+ newTaskScheduled.notify_all();
+ }
- // Returns number of tasks waiting to be serviced,
- // and first and last task times
- size_t getQueueInfo(std::chrono::system_clock::time_point &first,
- std::chrono::system_clock::time_point &last) const;
+ /**
+ * Returns number of tasks waiting to be serviced,
+ * and first and last task times
+ */
+ size_t getQueueInfo(std::chrono::system_clock::time_point& first,
+ std::chrono::system_clock::time_point& last) const;
- // Returns true if there are threads actively running in serviceQueue()
+ /** Returns true if there are threads actively running in serviceQueue() */
bool AreThreadsServicingQueue() const;
private:
@@ -106,19 +110,20 @@ private:
* B() will be able to observe all of the effects of callback A() which executed
* before it.
*/
-class SingleThreadedSchedulerClient {
+class SingleThreadedSchedulerClient
+{
private:
- CScheduler *m_pscheduler;
+ CScheduler* m_pscheduler;
RecursiveMutex m_cs_callbacks_pending;
- std::list<std::function<void ()>> m_callbacks_pending GUARDED_BY(m_cs_callbacks_pending);
+ std::list<std::function<void()>> m_callbacks_pending GUARDED_BY(m_cs_callbacks_pending);
bool m_are_callbacks_running GUARDED_BY(m_cs_callbacks_pending) = false;
void MaybeScheduleProcessQueue();
void ProcessQueue();
public:
- explicit SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {}
+ explicit SingleThreadedSchedulerClient(CScheduler* pschedulerIn) : m_pscheduler(pschedulerIn) {}
/**
* Add a callback to be executed. Callbacks are executed serially
@@ -126,10 +131,12 @@ public:
* Practically, this means that callbacks can behave as if they are executed
* in order by a single thread.
*/
- void AddToProcessQueue(std::function<void ()> func);
+ void AddToProcessQueue(std::function<void()> func);
- // Processes all remaining queue members on the calling thread, blocking until queue is empty
- // Must be called after the CScheduler has no remaining processing threads!
+ /**
+ * Processes all remaining queue members on the calling thread, blocking until queue is empty
+ * Must be called after the CScheduler has no remaining processing threads!
+ */
void EmptyQueue();
size_t CallbacksPending();
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 1c8c1df2be..5fa128d62d 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -235,7 +235,7 @@ public:
}
bool IsRange() const override { return false; }
size_t GetSize() const override { return m_pubkey.size(); }
- std::string ToString() const override { return HexStr(m_pubkey.begin(), m_pubkey.end()); }
+ std::string ToString() const override { return HexStr(m_pubkey); }
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{
CKey key;
@@ -583,7 +583,7 @@ class RawDescriptor final : public DescriptorImpl
{
const CScript m_script;
protected:
- std::string ToStringExtra() const override { return HexStr(m_script.begin(), m_script.end()); }
+ std::string ToStringExtra() const override { return HexStr(m_script); }
std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript*, FlatSigningProvider&) const override { return Vector(m_script); }
public:
RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {}
diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp
index 60196c36a5..173ec5e3d9 100644
--- a/src/test/coins_tests.cpp
+++ b/src/test/coins_tests.cpp
@@ -538,7 +538,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
CDataStream tmp(SER_DISK, CLIENT_VERSION);
uint64_t x = 3000000000ULL;
tmp << VARINT(x);
- BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
+ BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
try {
Coin cc5;
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 5f9a78ceb2..5d7065dafb 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -216,7 +216,7 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st
// For each of the produced scripts, verify solvability, and when possible, try to sign a transaction spending it.
for (size_t n = 0; n < spks.size(); ++n) {
- BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n].begin(), spks[n].end()));
+ BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n]));
BOOST_CHECK_EQUAL(IsSolvable(Merge(key_provider, script_provider), spks[n]), (flags & UNSOLVABLE) == 0);
if (flags & SIGNABLE) {
diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp
new file mode 100644
index 0000000000..595cdf9abb
--- /dev/null
+++ b/src/test/fuzz/crypto.cpp
@@ -0,0 +1,124 @@
+// Copyright (c) 2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <crypto/hmac_sha256.h>
+#include <crypto/hmac_sha512.h>
+#include <crypto/ripemd160.h>
+#include <crypto/sha1.h>
+#include <crypto/sha256.h>
+#include <crypto/sha512.h>
+#include <hash.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+
+#include <cstdint>
+#include <vector>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ std::vector<uint8_t> data = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+ if (data.empty()) {
+ data.resize(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4096), fuzzed_data_provider.ConsumeIntegral<uint8_t>());
+ }
+
+ CHash160 hash160;
+ CHash256 hash256;
+ CHMAC_SHA256 hmac_sha256{data.data(), data.size()};
+ CHMAC_SHA512 hmac_sha512{data.data(), data.size()};
+ CRIPEMD160 ripemd160;
+ CSHA1 sha1;
+ CSHA256 sha256;
+ CSHA512 sha512;
+ CSipHasher sip_hasher{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
+
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 2)) {
+ case 0: {
+ if (fuzzed_data_provider.ConsumeBool()) {
+ data = ConsumeRandomLengthByteVector(fuzzed_data_provider);
+ if (data.empty()) {
+ data.resize(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4096), fuzzed_data_provider.ConsumeIntegral<uint8_t>());
+ }
+ }
+
+ (void)hash160.Write(data.data(), data.size());
+ (void)hash256.Write(data.data(), data.size());
+ (void)hmac_sha256.Write(data.data(), data.size());
+ (void)hmac_sha512.Write(data.data(), data.size());
+ (void)ripemd160.Write(data.data(), data.size());
+ (void)sha1.Write(data.data(), data.size());
+ (void)sha256.Write(data.data(), data.size());
+ (void)sha512.Write(data.data(), data.size());
+ (void)sip_hasher.Write(data.data(), data.size());
+
+ (void)Hash(data.begin(), data.end());
+ (void)Hash160(data);
+ (void)Hash160(data.begin(), data.end());
+ (void)sha512.Size();
+ break;
+ }
+ case 1: {
+ (void)hash160.Reset();
+ (void)hash256.Reset();
+ (void)ripemd160.Reset();
+ (void)sha1.Reset();
+ (void)sha256.Reset();
+ (void)sha512.Reset();
+ break;
+ }
+ case 2: {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 8)) {
+ case 0: {
+ data.resize(CHash160::OUTPUT_SIZE);
+ hash160.Finalize(data.data());
+ break;
+ }
+ case 1: {
+ data.resize(CHash256::OUTPUT_SIZE);
+ hash256.Finalize(data.data());
+ break;
+ }
+ case 2: {
+ data.resize(CHMAC_SHA256::OUTPUT_SIZE);
+ hmac_sha256.Finalize(data.data());
+ break;
+ }
+ case 3: {
+ data.resize(CHMAC_SHA512::OUTPUT_SIZE);
+ hmac_sha512.Finalize(data.data());
+ break;
+ }
+ case 4: {
+ data.resize(CRIPEMD160::OUTPUT_SIZE);
+ ripemd160.Finalize(data.data());
+ break;
+ }
+ case 5: {
+ data.resize(CSHA1::OUTPUT_SIZE);
+ sha1.Finalize(data.data());
+ break;
+ }
+ case 6: {
+ data.resize(CSHA256::OUTPUT_SIZE);
+ sha256.Finalize(data.data());
+ break;
+ }
+ case 7: {
+ data.resize(CSHA512::OUTPUT_SIZE);
+ sha512.Finalize(data.data());
+ break;
+ }
+ case 8: {
+ data.resize(1);
+ data[0] = sip_hasher.Finalize() % 256;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/test/fuzz/decode_tx.cpp b/src/test/fuzz/decode_tx.cpp
index 09c4ff05df..0d89d4228a 100644
--- a/src/test/fuzz/decode_tx.cpp
+++ b/src/test/fuzz/decode_tx.cpp
@@ -14,7 +14,7 @@
void test_one_input(const std::vector<uint8_t>& buffer)
{
- const std::string tx_hex = HexStr(std::string{buffer.begin(), buffer.end()});
+ const std::string tx_hex = HexStr(buffer);
CMutableTransaction mtx;
const bool result_none = DecodeHexTx(mtx, tx_hex, false, false);
const bool result_try_witness = DecodeHexTx(mtx, tx_hex, false, true);
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index 82e1d55c0b..1e1807d734 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -12,7 +12,16 @@
const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
-#if defined(__AFL_COMPILER)
+// Decide if main(...) should be provided:
+// * AFL needs main(...) regardless of platform.
+// * macOS handles __attribute__((weak)) main(...) poorly when linking
+// against libFuzzer. See https://github.com/bitcoin/bitcoin/pull/18008
+// for details.
+#if defined(__AFL_COMPILER) || !defined(MAC_OSX)
+#define PROVIDE_MAIN_FUNCTION
+#endif
+
+#if defined(PROVIDE_MAIN_FUNCTION)
static bool read_stdin(std::vector<uint8_t>& data)
{
uint8_t buffer[1024];
@@ -44,9 +53,8 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
return 0;
}
-// Generally, the fuzzer will provide main(), except for AFL
-#if defined(__AFL_COMPILER)
-int main(int argc, char** argv)
+#if defined(PROVIDE_MAIN_FUNCTION)
+__attribute__((weak)) int main(int argc, char** argv)
{
initialize();
#ifdef __AFL_INIT
diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp
index c42a45f4f3..c746374c61 100644
--- a/src/test/fuzz/key.cpp
+++ b/src/test/fuzz/key.cpp
@@ -108,7 +108,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
assert(pubkey.IsCompressed());
assert(pubkey.IsValid());
assert(pubkey.IsFullyValid());
- assert(HexToPubKey(HexStr(pubkey.begin(), pubkey.end())) == pubkey);
+ assert(HexToPubKey(HexStr(pubkey)) == pubkey);
assert(GetAllDestinationsForKey(pubkey).size() == 3);
}
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
index cf2bd03698..fd35537c77 100644
--- a/src/test/key_tests.cpp
+++ b/src/test/key_tests.cpp
@@ -5,6 +5,7 @@
#include <key.h>
#include <key_io.h>
+#include <streams.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <util/strencodings.h>
@@ -220,4 +221,47 @@ BOOST_AUTO_TEST_CASE(key_key_negation)
BOOST_CHECK(key.GetPubKey().data()[0] == 0x03);
}
+static CPubKey UnserializePubkey(const std::vector<uint8_t>& data)
+{
+ CDataStream stream{SER_NETWORK, INIT_PROTO_VERSION};
+ stream << data;
+ CPubKey pubkey;
+ stream >> pubkey;
+ return pubkey;
+}
+
+static unsigned int GetLen(unsigned char chHeader)
+{
+ if (chHeader == 2 || chHeader == 3)
+ return CPubKey::COMPRESSED_SIZE;
+ if (chHeader == 4 || chHeader == 6 || chHeader == 7)
+ return CPubKey::SIZE;
+ return 0;
+}
+
+static void CmpSerializationPubkey(const CPubKey& pubkey)
+{
+ CDataStream stream{SER_NETWORK, INIT_PROTO_VERSION};
+ stream << pubkey;
+ CPubKey pubkey2;
+ stream >> pubkey2;
+ BOOST_CHECK(pubkey == pubkey2);
+}
+
+BOOST_AUTO_TEST_CASE(pubkey_unserialize)
+{
+ for (uint8_t i = 2; i <= 7; ++i) {
+ CPubKey key = UnserializePubkey({0x02});
+ BOOST_CHECK(!key.IsValid());
+ CmpSerializationPubkey(key);
+ key = UnserializePubkey(std::vector<uint8_t>(GetLen(i), i));
+ CmpSerializationPubkey(key);
+ if (i == 5) {
+ BOOST_CHECK(!key.IsValid());
+ } else {
+ BOOST_CHECK(key.IsValid());
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index d0ec401f9d..0fbf257f0e 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -160,6 +160,9 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK(ResolveSubNet("1.2.2.20/26").Match(ResolveIP("1.2.2.63")));
// All-Matching IPv6 Matches arbitrary IPv4 and IPv6
BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1:2:3:4:5:6:7:1234")));
+ // But not `::` or `0.0.0.0` because they are considered invalid addresses
+ BOOST_CHECK(!ResolveSubNet("::/0").Match(ResolveIP("::")));
+ BOOST_CHECK(!ResolveSubNet("::/0").Match(ResolveIP("0.0.0.0")));
BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1.2.3.4")));
// All-Matching IPv4 does not Match IPv6
BOOST_CHECK(!ResolveSubNet("0.0.0.0/0").Match(ResolveIP("1:2:3:4:5:6:7:1234")));
diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp
index fcee6a9b9d..2e5a7549b7 100644
--- a/src/test/scheduler_tests.cpp
+++ b/src/test/scheduler_tests.cpp
@@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE(manythreads)
}
// Drain the task queue then exit threads
- microTasks.stop(true);
+ microTasks.StopWhenDrained();
microThreads.join_all(); // ... wait until all the threads are done
int counterSum = 0;
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
}
// finish up
- scheduler.stop(true);
+ scheduler.StopWhenDrained();
threads.join_all();
BOOST_CHECK_EQUAL(counter1, 100);
@@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE(mockforward)
scheduler.MockForward(std::chrono::minutes{5});
// ensure scheduler has chance to process all tasks queued for before 1 ms from now.
- scheduler.scheduleFromNow([&scheduler] { scheduler.stop(false); }, std::chrono::milliseconds{1});
+ scheduler.scheduleFromNow([&scheduler] { scheduler.stop(); }, std::chrono::milliseconds{1});
scheduler_thread.join();
// check that the queue only has one job remaining
diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp
index 5ca136ea6e..c0bb92258b 100644
--- a/src/test/sighash_tests.cpp
+++ b/src/test/sighash_tests.cpp
@@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE(sighash_test)
ss << txTo;
std::cout << "\t[\"" ;
- std::cout << HexStr(ss.begin(), ss.end()) << "\", \"";
+ std::cout << HexStr(ss) << "\", \"";
std::cout << HexStr(scriptCode) << "\", ";
std::cout << nIn << ", ";
std::cout << nHashType << ", \"";
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 3b7a7c8d12..709d357b8a 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -19,6 +19,7 @@
#include <rpc/blockchain.h>
#include <rpc/register.h>
#include <rpc/server.h>
+#include <scheduler.h>
#include <script/sigcache.h>
#include <streams.h>
#include <txdb.h>
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index d5cda8a95b..e480782c12 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -11,7 +11,6 @@
#include <node/context.h>
#include <pubkey.h>
#include <random.h>
-#include <scheduler.h>
#include <txmempool.h>
#include <util/string.h>
diff --git a/src/util/error.cpp b/src/util/error.cpp
index c4d9ffd037..3e29083712 100644
--- a/src/util/error.cpp
+++ b/src/util/error.cpp
@@ -14,7 +14,7 @@ bilingual_str TransactionErrorString(const TransactionError err)
case TransactionError::OK:
return Untranslated("No error");
case TransactionError::MISSING_INPUTS:
- return Untranslated("Missing inputs");
+ return Untranslated("Inputs missing or spent");
case TransactionError::ALREADY_IN_CHAIN:
return Untranslated("Transaction already in block chain");
case TransactionError::P2P_DISABLED:
@@ -24,11 +24,11 @@ bilingual_str TransactionErrorString(const TransactionError err)
case TransactionError::MEMPOOL_ERROR:
return Untranslated("AcceptToMemoryPool failed");
case TransactionError::INVALID_PSBT:
- return Untranslated("PSBT is not sane");
+ return Untranslated("PSBT is not well-formed");
case TransactionError::PSBT_MISMATCH:
return Untranslated("PSBTs not compatible (different transactions)");
case TransactionError::SIGHASH_MISMATCH:
- return Untranslated("Specified sighash value does not match existing value");
+ return Untranslated("Specified sighash value does not match value stored in PSBT");
case TransactionError::MAX_FEE_EXCEEDED:
return Untranslated("Fee exceeds maximum configured by -maxtxfee");
// no default case, so the compiler can warn about missing cases
diff --git a/src/util/fees.cpp b/src/util/fees.cpp
index b335bfa666..6208a20a97 100644
--- a/src/util/fees.cpp
+++ b/src/util/fees.cpp
@@ -6,11 +6,16 @@
#include <util/fees.h>
#include <policy/fees.h>
+#include <util/strencodings.h>
+#include <util/string.h>
#include <map>
#include <string>
+#include <vector>
+#include <utility>
-std::string StringForFeeReason(FeeReason reason) {
+std::string StringForFeeReason(FeeReason reason)
+{
static const std::map<FeeReason, std::string> fee_reason_strings = {
{FeeReason::NONE, "None"},
{FeeReason::HALF_ESTIMATE, "Half Target 60% Threshold"},
@@ -29,16 +34,31 @@ std::string StringForFeeReason(FeeReason reason) {
return reason_string->second;
}
-bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode) {
- static const std::map<std::string, FeeEstimateMode> fee_modes = {
- {"UNSET", FeeEstimateMode::UNSET},
- {"ECONOMICAL", FeeEstimateMode::ECONOMICAL},
- {"CONSERVATIVE", FeeEstimateMode::CONSERVATIVE},
+const std::vector<std::pair<std::string, FeeEstimateMode>>& FeeModeMap()
+{
+ static const std::vector<std::pair<std::string, FeeEstimateMode>> FEE_MODES = {
+ {"unset", FeeEstimateMode::UNSET},
+ {"economical", FeeEstimateMode::ECONOMICAL},
+ {"conservative", FeeEstimateMode::CONSERVATIVE},
+ {(CURRENCY_UNIT + "/kB"), FeeEstimateMode::BTC_KB},
+ {(CURRENCY_ATOM + "/B"), FeeEstimateMode::SAT_B},
};
- auto mode = fee_modes.find(mode_string);
+ return FEE_MODES;
+}
- if (mode == fee_modes.end()) return false;
+std::string FeeModes(const std::string& delimiter)
+{
+ return Join(FeeModeMap(), delimiter, [&](const std::pair<std::string, FeeEstimateMode>& i) { return i.first; });
+}
- fee_estimate_mode = mode->second;
- return true;
+bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode)
+{
+ auto searchkey = ToUpper(mode_string);
+ for (const auto& pair : FeeModeMap()) {
+ if (ToUpper(pair.first) == searchkey) {
+ fee_estimate_mode = pair.second;
+ return true;
+ }
+ }
+ return false;
}
diff --git a/src/util/fees.h b/src/util/fees.h
index a930c8935a..d52046a44c 100644
--- a/src/util/fees.h
+++ b/src/util/fees.h
@@ -12,5 +12,6 @@ enum class FeeReason;
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
std::string StringForFeeReason(FeeReason reason);
+std::string FeeModes(const std::string& delimiter);
#endif // BITCOIN_UTIL_FEES_H
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 3a5459006d..c9ea6c2ad9 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -817,7 +817,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
create_time = FormatISO8601DateTime(it->second.nCreateTime);
}
if(spk_man.GetCScript(scriptid, script)) {
- file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time);
+ file << strprintf("%s %s script=1", HexStr(script), create_time);
file << strprintf(" # addr=%s\n", address);
}
}
@@ -1193,7 +1193,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// Check whether we have any work to do
for (const CScript& script : script_pub_keys) {
if (pwallet->IsMine(script) & ISMINE_SPENDABLE) {
- throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")");
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")");
}
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 7a9089456d..00927a2e55 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -45,6 +45,8 @@ using interfaces::FoundBlock;
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
+static const uint32_t WALLET_BTC_KB_TO_SAT_B = COIN / 1000; // 1 sat / B = 0.00001 BTC / kB
+
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
@@ -191,6 +193,42 @@ static std::string LabelFromValue(const UniValue& value)
return label;
}
+/**
+ * Update coin control with fee estimation based on the given parameters
+ *
+ * @param[in] pwallet Wallet pointer
+ * @param[in,out] cc Coin control which is to be updated
+ * @param[in] estimate_mode String value (e.g. "ECONOMICAL")
+ * @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
+ * @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
+ */
+static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
+{
+ if (!estimate_mode.isNull()) {
+ if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ }
+ }
+
+ if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
+ if (estimate_param.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
+ }
+
+ CAmount fee_rate = AmountFromValue(estimate_param);
+ if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
+ fee_rate /= WALLET_BTC_KB_TO_SAT_B;
+ }
+
+ cc.m_feerate = CFeeRate(fee_rate);
+
+ // default RBF to true for explicit fee rate modes
+ if (cc.m_signal_bip125_rbf == boost::none) cc.m_signal_bip125_rbf = true;
+ } else if (!estimate_param.isNull()) {
+ cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
+ }
+}
+
static UniValue getnewaddress(const JSONRPCRequest& request)
{
RPCHelpMan{"getnewaddress",
@@ -369,11 +407,9 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
" The recipient will receive less bitcoins than you enter in the amount field."},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
" dirty if they have previously been used in a transaction."},
},
@@ -384,6 +420,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
+ + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
+ + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
},
}.Check(request);
@@ -425,20 +463,12 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
}
- if (!request.params[6].isNull()) {
- coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
- }
-
- if (!request.params[7].isNull()) {
- if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
- }
- }
-
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]);
// We also enable partial spend avoidance if reuse avoidance is set.
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
+ SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
+
EnsureWalletIsUnlocked(pwallet);
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
@@ -780,11 +810,9 @@ static UniValue sendmany(const JSONRPCRequest& request)
},
},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
@@ -830,15 +858,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
}
- if (!request.params[6].isNull()) {
- coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
- }
-
- if (!request.params[7].isNull()) {
- if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
- }
- }
+ SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
std::set<CTxDestination> destinations;
std::vector<CRecipient> vecSend;
@@ -964,7 +984,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest));
- result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
+ result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());
return result;
}
@@ -2870,7 +2890,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
CScript redeemScript;
if (provider->GetCScript(hash, redeemScript)) {
- entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
+ entry.pushKV("redeemScript", HexStr(redeemScript));
// Now check if the redeemScript is actually a P2WSH script
CTxDestination witness_destination;
if (redeemScript.IsPayToWitnessScriptHash()) {
@@ -2882,7 +2902,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
if (provider->GetCScript(id, witnessScript)) {
- entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ entry.pushKV("witnessScript", HexStr(witnessScript));
}
}
}
@@ -2892,13 +2912,13 @@ static UniValue listunspent(const JSONRPCRequest& request)
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
if (provider->GetCScript(id, witnessScript)) {
- entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ entry.pushKV("witnessScript", HexStr(witnessScript));
}
}
}
}
- entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
entry.pushKV("confirmations", out.nDepth);
entry.pushKV("spendable", out.fSpendable);
@@ -2986,6 +3006,12 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
if (options.exists("feeRate"))
{
+ if (options.exists("conf_target")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
+ }
+ if (options.exists("estimate_mode")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
+ }
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
coinControl.fOverrideFeeRate = true;
}
@@ -2996,20 +3022,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
if (options.exists("replaceable")) {
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
}
- if (options.exists("conf_target")) {
- if (options.exists("feeRate")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
- }
- coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"], pwallet->chain().estimateMaxBlocks());
- }
- if (options.exists("estimate_mode")) {
- if (options.exists("feeRate")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
- }
- if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
- }
- }
+ SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
}
} else {
// if options is null and not a bool
@@ -3077,11 +3090,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
},
"options"},
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
@@ -3249,8 +3260,8 @@ static UniValue bumpfee(const JSONRPCRequest& request)
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
- {"confTarget", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
- {"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'confTarget'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
+ {"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
" Specify a fee rate instead of relying on the built-in fee estimator.\n"
"Must be at least 0.0001 " + CURRENCY_UNIT + " per kB higher than the current transaction fee rate.\n"},
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
@@ -3260,10 +3271,8 @@ static UniValue bumpfee(const JSONRPCRequest& request)
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
" are replaceable)."},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
},
"options"},
},
@@ -3302,15 +3311,24 @@ static UniValue bumpfee(const JSONRPCRequest& request)
RPCTypeCheckObj(options,
{
{"confTarget", UniValueType(UniValue::VNUM)},
+ {"conf_target", UniValueType(UniValue::VNUM)},
{"fee_rate", UniValueType(UniValue::VNUM)},
{"replaceable", UniValueType(UniValue::VBOOL)},
{"estimate_mode", UniValueType(UniValue::VSTR)},
},
true, true);
- if (options.exists("confTarget") && options.exists("fee_rate")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
- } else if (options.exists("confTarget")) { // TODO: alias this to conf_target
- coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"], pwallet->chain().estimateMaxBlocks());
+
+ if (options.exists("confTarget") && options.exists("conf_target")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and conf_target options should not both be set. Use conf_target (confTarget is deprecated).");
+ }
+
+ auto conf_target = options.exists("confTarget") ? options["confTarget"] : options["conf_target"];
+
+ if (!conf_target.isNull()) {
+ if (options.exists("fee_rate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
+ }
+ coin_control.m_confirm_target = ParseConfirmTarget(conf_target, pwallet->chain().estimateMaxBlocks());
} else if (options.exists("fee_rate")) {
CFeeRate fee_rate(AmountFromValue(options["fee_rate"]));
if (fee_rate <= CFeeRate(0)) {
@@ -3322,11 +3340,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
if (options.exists("replaceable")) {
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
}
- if (options.exists("estimate_mode")) {
- if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
- }
- }
+ SetFeeEstimateMode(pwallet, coin_control, options["estimate_mode"], conf_target);
}
// Make sure the results are valid at least up to the most recent block
@@ -3491,7 +3505,7 @@ public:
std::vector<std::vector<unsigned char>> solutions_data;
TxoutType which_type = Solver(subscript, solutions_data);
obj.pushKV("script", GetTxnOutputType(which_type));
- obj.pushKV("hex", HexStr(subscript.begin(), subscript.end()));
+ obj.pushKV("hex", HexStr(subscript));
CTxDestination embedded;
if (ExtractDestination(subscript, embedded)) {
@@ -3502,7 +3516,7 @@ public:
UniValue wallet_detail = boost::apply_visitor(*this, embedded);
subobj.pushKVs(wallet_detail);
subobj.pushKV("address", EncodeDestination(embedded));
- subobj.pushKV("scriptPubKey", HexStr(subscript.begin(), subscript.end()));
+ subobj.pushKV("scriptPubKey", HexStr(subscript));
// Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]);
obj.pushKV("embedded", std::move(subobj));
@@ -3513,7 +3527,7 @@ public:
UniValue pubkeys(UniValue::VARR);
for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
- pubkeys.push_back(HexStr(key.begin(), key.end()));
+ pubkeys.push_back(HexStr(key));
}
obj.pushKV("pubkeys", std::move(pubkeys));
}
@@ -3669,7 +3683,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
ret.pushKV("address", currentAddress);
CScript scriptPubKey = GetScriptForDestination(dest);
- ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
@@ -4016,10 +4030,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees"},
{"conf_target", RPCArg::Type::NUM, /* default */ "fall back to wallet's confirmation target (txconfirmtarget)", "Confirmation target (in blocks)"},
- {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
- " \"UNSET\"\n"
- " \"ECONOMICAL\"\n"
- " \"CONSERVATIVE\""},
+ {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
},
"options"},
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "true", "Include BIP 32 derivation paths for public keys if we know them"},
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 893db5dcc2..d7c50a9d2a 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -584,8 +584,11 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
return SigningResult::SIGNING_FAILED;
}
-TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) const
+TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
{
+ if (n_signed) {
+ *n_signed = 0;
+ }
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
@@ -616,6 +619,14 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
SignatureData sigdata;
input.FillSignatureData(sigdata);
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type);
+
+ bool signed_one = PSBTInputSigned(input);
+ if (n_signed && (signed_one || !sign)) {
+ // If sign is false, we assume that we _could_ sign if we get here. This
+ // will never have false negatives; it is hard to tell under what i
+ // circumstances it could have false positives.
+ (*n_signed)++;
+ }
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
@@ -2062,8 +2073,11 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
return SigningResult::OK;
}
-TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) const
+TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
{
+ if (n_signed) {
+ *n_signed = 0;
+ }
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
@@ -2115,6 +2129,14 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
}
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type);
+
+ bool signed_one = PSBTInputSigned(input);
+ if (n_signed && (signed_one || !sign)) {
+ // If sign is false, we assume that we _could_ sign if we get here. This
+ // will never have false negatives; it is hard to tell under what i
+ // circumstances it could have false positives.
+ (*n_signed)++;
+ }
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index d62d30f339..9fa2a68284 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -234,7 +234,7 @@ public:
/** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
- virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false) const { return TransactionError::INVALID_PSBT; }
+ virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; }
virtual uint256 GetID() const { return uint256(); }
@@ -393,7 +393,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
- TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
uint256 GetID() const override;
@@ -596,7 +596,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
- TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
uint256 GetID() const override;
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index b4c65a8665..3f85a48ff3 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Get the final tx
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
- std::string final_hex = HexStr(ssTx.begin(), ssTx.end());
+ std::string final_hex = HexStr(ssTx);
BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000");
// Mutate the transaction so that one of the inputs is invalid
diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp
index 97f8c94fa6..10ddfa22ef 100644
--- a/src/wallet/test/wallet_crypto_tests.cpp
+++ b/src/wallet/test/wallet_crypto_tests.cpp
@@ -24,10 +24,10 @@ static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, cons
if(!correctKey.empty())
BOOST_CHECK_MESSAGE(memcmp(crypt.vchKey.data(), correctKey.data(), crypt.vchKey.size()) == 0, \
- HexStr(crypt.vchKey.begin(), crypt.vchKey.end()) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end()));
+ HexStr(crypt.vchKey) + std::string(" != ") + HexStr(correctKey));
if(!correctIV.empty())
BOOST_CHECK_MESSAGE(memcmp(crypt.vchIV.data(), correctIV.data(), crypt.vchIV.size()) == 0,
- HexStr(crypt.vchIV.begin(), crypt.vchIV.end()) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end()));
+ HexStr(crypt.vchIV) + std::string(" != ") + HexStr(correctIV));
}
static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index f5f66ffc5d..57eec9baf9 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2476,8 +2476,11 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
return false;
}
-TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs) const
+TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed) const
{
+ if (n_signed) {
+ *n_signed = 0;
+ }
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
@@ -2508,10 +2511,15 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
- TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
+ int n_signed_this_spkm = 0;
+ TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
if (res != TransactionError::OK) {
return res;
}
+
+ if (n_signed) {
+ (*n_signed) += n_signed_this_spkm;
+ }
}
// Complete if every input is now signed
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cf000b0b70..9931671fb4 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -964,7 +964,8 @@ public:
bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true,
- bool bip32derivs = true) const;
+ bool bip32derivs = true,
+ size_t* n_signed = nullptr) const;
/**
* Create a new transaction paying the recipients with a set of coins
diff --git a/test/README.md b/test/README.md
index b036a66f67..2341eef00d 100644
--- a/test/README.md
+++ b/test/README.md
@@ -260,11 +260,11 @@ Use the `-v` option for verbose output.
| Lint test | Dependency | Version [used by CI](../ci/lint/04_install.sh) | Installation
|-----------|:----------:|:-------------------------------------------:|--------------
-| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.7.8](https://github.com/bitcoin/bitcoin/pull/15257) | `pip3 install flake8==3.7.8`
-| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.700](https://github.com/bitcoin/bitcoin/pull/18210) | `pip3 install mypy==0.700`
-| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.6.0](https://github.com/bitcoin/bitcoin/pull/15166) | [details...](https://github.com/koalaman/shellcheck#installing)
+| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.8.3](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install flake8==3.8.3`
+| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.781](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install mypy==0.781`
+| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.7.1](https://github.com/bitcoin/bitcoin/pull/19348) | [details...](https://github.com/koalaman/shellcheck#installing)
| [`lint-shell.sh`](lint/lint-shell.sh) | [yq](https://github.com/kislyuk/yq) | default | `pip3 install yq`
-| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [1.15.0](https://github.com/bitcoin/bitcoin/pull/16186) | `pip3 install codespell==1.15.0`
+| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [1.17.1](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install codespell==1.17.1`
Please be aware that on Linux distributions all dependencies are usually available as packages, but could be outdated.
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index cd380997c1..5cddd6527e 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -27,8 +27,6 @@ from test_framework.descriptors import descsum_create
from test_framework.util import (
assert_equal,
- sync_blocks,
- sync_mempools,
)
@@ -65,7 +63,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
def run_test(self):
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Sanity check the test framework:
res = self.nodes[self.num_nodes - 1].getblockchaininfo()
@@ -90,17 +88,17 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# Create a confirmed transaction, receiving coins
address = wallet.getnewaddress()
self.nodes[0].sendtoaddress(address, 10)
- sync_mempools(self.nodes)
+ self.sync_mempools()
self.nodes[0].generate(1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Create a conflicting transaction using RBF
return_address = self.nodes[0].getnewaddress()
tx1_id = self.nodes[1].sendtoaddress(return_address, 1)
tx2_id = self.nodes[1].bumpfee(tx1_id)["txid"]
# Confirm the transaction
- sync_mempools(self.nodes)
+ self.sync_mempools()
self.nodes[0].generate(1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
# Create another conflicting transaction using RBF
tx3_id = self.nodes[1].sendtoaddress(return_address, 1)
tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"]
@@ -313,15 +311,13 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
self.start_node(4)
# Open most recent wallet in v0.16 (no loadwallet RPC)
- self.stop_node(5)
- self.start_node(5, extra_args=["-wallet=w2"])
+ self.restart_node(5, extra_args=["-wallet=w2"])
wallet = node_v16.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['keypoolsize'] == 1
# Create upgrade wallet in v0.16
- self.stop_node(-1)
- self.start_node(-1, extra_args=["-wallet=u1_v16"])
+ self.restart_node(-1, extra_args=["-wallet=u1_v16"])
wallet = node_v16.get_wallet_rpc("u1_v16")
v16_addr = wallet.getnewaddress('', "bech32")
v16_info = wallet.validateaddress(v16_addr)
diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py
index 82f1331685..0a457ca17f 100755
--- a/test/functional/feature_loadblock.py
+++ b/test/functional/feature_loadblock.py
@@ -71,8 +71,7 @@ class LoadblockTest(BitcoinTestFramework):
check=True)
self.log.info("Restart second, unsynced node with bootstrap file")
- self.stop_node(1)
- self.start_node(1, ["-loadblock=" + bootstrap_file])
+ self.restart_node(1, extra_args=["-loadblock=" + bootstrap_file])
assert_equal(self.nodes[1].getblockcount(), 100) # start_node is blocking on all block files being imported
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 100)
diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py
index e4bf2d849d..afcbcf099a 100755
--- a/test/functional/feature_logging.py
+++ b/test/functional/feature_logging.py
@@ -67,8 +67,7 @@ class LoggingTest(BitcoinTestFramework):
assert not os.path.isfile(default_log_path)
# just sanity check no crash here
- self.stop_node(0)
- self.start_node(0, ["-debuglogfile=%s" % os.devnull])
+ self.restart_node(0, ["-debuglogfile=%s" % os.devnull])
if __name__ == '__main__':
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index c9362cf5aa..e46e5aacc8 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -263,8 +263,7 @@ class PruneTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "not in prune mode", node.pruneblockchain, 500)
# now re-start in manual pruning mode
- self.stop_node(node_number)
- self.start_node(node_number, extra_args=["-prune=1"])
+ self.restart_node(node_number, extra_args=["-prune=1"])
node = self.nodes[node_number]
assert_equal(node.getblockcount(), 995)
@@ -326,16 +325,14 @@ class PruneTest(BitcoinTestFramework):
assert not has_block(3), "blk00003.dat is still there, should be pruned by now"
# stop node, start back up with auto-prune at 550 MiB, make sure still runs
- self.stop_node(node_number)
- self.start_node(node_number, extra_args=["-prune=550"])
+ self.restart_node(node_number, extra_args=["-prune=550"])
self.log.info("Success")
def wallet_test(self):
# check that the pruning node's wallet is still in good shape
self.log.info("Stop and start pruning node to trigger wallet rescan")
- self.stop_node(2)
- self.start_node(2, extra_args=["-prune=550"])
+ self.restart_node(2, extra_args=["-prune=550"])
self.log.info("Success")
# check that wallet loads successfully when restarting a pruned node after IBD.
@@ -344,8 +341,7 @@ class PruneTest(BitcoinTestFramework):
connect_nodes(self.nodes[0], 5)
nds = [self.nodes[0], self.nodes[5]]
self.sync_blocks(nds, wait=5, timeout=300)
- self.stop_node(5) # stop and start to trigger rescan
- self.start_node(5, extra_args=["-prune=550"])
+ self.restart_node(5, extra_args=["-prune=550"]) # restart to trigger rescan
self.log.info("Success")
def run_test(self):
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 2298485640..5195d20dcb 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -559,8 +559,7 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
# Assert it is properly saved
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 7530e7daf6..80003aca0d 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -3,9 +3,15 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test bitcoin-cli"""
+
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_process_error, get_auth_cookie
+from test_framework.util import (
+ assert_equal,
+ assert_raises_process_error,
+ assert_raises_rpc_error,
+ get_auth_cookie,
+)
# The block reward of coinbaseoutput.nValue (50) BTC/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
@@ -13,6 +19,12 @@ from test_framework.util import assert_equal, assert_raises_process_error, get_a
BLOCKS = 101
BALANCE = (BLOCKS - 100) * 50
+JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
+BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
+TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
+WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
+WALLET_NOT_SPECIFIED = 'Wallet file not specified'
+
class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -75,7 +87,7 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
- # Setup to test -getinfo and -rpcwallet= with multiple wallets.
+ # Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
wallets = ['', 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)]
self.nodes[0].createwallet(wallet_name=wallets[1])
@@ -83,6 +95,8 @@ class TestBitcoinCli(BitcoinTestFramework):
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
+ rpcwallet2 = '-rpcwallet={}'.format(wallets[1])
+ rpcwallet3 = '-rpcwallet={}'.format(wallets[2])
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
@@ -123,17 +137,93 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
- cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[1])).send_cli()
+ cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
- cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[2])).send_cli()
+ cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
assert 'balance' not in cli_get_info_keys
assert 'balances' not in cli_get_info_keys
+
+ # Test bitcoin-cli -generate.
+ n1 = 3
+ n2 = 4
+ w2.walletpassphrase(password, self.rpc_timeout)
+ blocks = self.nodes[0].getblockcount()
+
+ self.log.info('Test -generate with no args')
+ generate = self.nodes[0].cli('-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1)
+
+ self.log.info('Test -generate with bad args')
+ assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli('-generate', 'foo').echo)
+ assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli('-generate', 0).echo)
+ assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli('-generate', 1, 2, 3).echo)
+
+ self.log.info('Test -generate with nblocks')
+ generate = self.nodes[0].cli('-generate', n1).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1)
+
+ self.log.info('Test -generate with nblocks and maxtries')
+ generate = self.nodes[0].cli('-generate', n2, 1000000).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n2)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1 + n2)
+
+ self.log.info('Test -generate -rpcwallet in single-wallet mode')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 2 + n1 + n2)
+
+ self.log.info('Test -generate -rpcwallet=unloaded wallet raises RPC error')
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate').echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 'foo').echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 0).echo)
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 1, 2, 3).echo)
+
+ # Test bitcoin-cli -generate with -rpcwallet in multiwallet mode.
+ self.nodes[0].loadwallet(wallets[2])
+ n3 = 4
+ n4 = 10
+ blocks = self.nodes[0].getblockcount()
+
+ self.log.info('Test -generate -rpcwallet with no args')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), 1)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1)
+
+ self.log.info('Test -generate -rpcwallet with bad args')
+ assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli(rpcwallet2, '-generate', 'foo').echo)
+ assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(rpcwallet2, '-generate', 0).echo)
+ assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli(rpcwallet2, '-generate', 1, 2, 3).echo)
+
+ self.log.info('Test -generate -rpcwallet with nblocks')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate', n3).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n3)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3)
+
+ self.log.info('Test -generate -rpcwallet with nblocks and maxtries')
+ generate = self.nodes[0].cli(rpcwallet2, '-generate', n4, 1000000).send_cli()
+ assert_equal(set(generate.keys()), {'address', 'blocks'})
+ assert_equal(len(generate["blocks"]), n4)
+ assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4)
+
+ self.log.info('Test -generate without -rpcwallet in multiwallet mode raises RPC error')
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate').echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 'foo').echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 0).echo)
+ assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 1, 2, 3).echo)
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
- self.nodes[0].generate(1) # maintain block parity with the wallet_compiled conditional branch
+ self.nodes[0].generate(25) # maintain block parity with the wallet_compiled conditional branch
self.log.info("Test -version with node stopped")
self.stop_node(0)
@@ -145,7 +235,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.nodes[0].wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
- assert_equal(blocks, BLOCKS + 1)
+ assert_equal(blocks, BLOCKS + 25)
if __name__ == '__main__':
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 6046237101..5c7e27a3a8 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -49,9 +49,9 @@ class AddrTest(BitcoinTestFramework):
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addr()
- self.log.info('Send too large addr message')
+ self.log.info('Send too-large addr message')
msg.addrs = ADDRS * 101
- with self.nodes[0].assert_debug_log(['message addr size() = 1010']):
+ with self.nodes[0].assert_debug_log(['addr message size = 1010']):
addr_source.send_and_ping(msg)
self.log.info('Check that addr message content is relayed and added to addrman')
diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py
index 9047fc6828..09b9ebeb2d 100755
--- a/test/functional/p2p_disconnect_ban.py
+++ b/test/functional/p2p_disconnect_ban.py
@@ -69,8 +69,7 @@ class DisconnectBanTest(BitcoinTestFramework):
self.nodes[1].setmocktime(old_time + 3)
assert_equal(len(self.nodes[1].listbanned()), 3)
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
listAfterShutdown = self.nodes[1].listbanned()
assert_equal("127.0.0.0/24", listAfterShutdown[0]['address'])
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index 805cb1e84f..f939ea965c 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -10,11 +10,13 @@ import time
from test_framework.messages import MSG_TX, msg_feefilter
from test_framework.mininode import mininode_lock, P2PInterface
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
def hashToHex(hash):
return format(hash, '064x')
+
# Wait up to 60 secs to see if the testnode has received all the expected invs
def allInvsMatch(invsExpected, testnode):
for x in range(60):
@@ -24,6 +26,18 @@ def allInvsMatch(invsExpected, testnode):
time.sleep(1)
return False
+
+class FeefilterConn(P2PInterface):
+ feefilter_received = False
+
+ def on_feefilter(self, message):
+ self.feefilter_received = True
+
+ def assert_feefilter_received(self, recv: bool):
+ with mininode_lock:
+ assert_equal(self.feefilter_received, recv)
+
+
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
@@ -38,6 +52,7 @@ class TestP2PConn(P2PInterface):
with mininode_lock:
self.txinvs = []
+
class FeeFilterTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
@@ -46,41 +61,54 @@ class FeeFilterTest(BitcoinTestFramework):
# mempool and wallet feerate calculation based on GetFee
# rounding down 3 places, leading to stranded transactions.
# See issue #16499
- self.extra_args = [["-minrelaytxfee=0.00000100", "-mintxfee=0.00000100"]]*self.num_nodes
+ self.extra_args = [["-minrelaytxfee=0.00000100", "-mintxfee=0.00000100"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
+ self.test_feefilter_forcerelay()
+ self.test_feefilter()
+
+ def test_feefilter_forcerelay(self):
+ self.log.info('Check that peers without forcerelay permission (default) get a feefilter message')
+ self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(True)
+
+ self.log.info('Check that peers with forcerelay permission do not get a feefilter message')
+ self.restart_node(0, extra_args=['-whitelist=forcerelay@127.0.0.1'])
+ self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(False)
+
+ # Restart to disconnect peers and load default extra_args
+ self.restart_node(0)
+ self.connect_nodes(1, 0)
+
+ def test_feefilter(self):
node1 = self.nodes[1]
node0 = self.nodes[0]
- # Get out of IBD
- node1.generate(1)
- self.sync_blocks()
- self.nodes[0].add_p2p_connection(TestP2PConn())
+ conn = self.nodes[0].add_p2p_connection(TestP2PConn())
# Test that invs are received by test connection for all txs at
# feerate of .2 sat/byte
node1.settxfee(Decimal("0.00000200"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Set a filter of .15 sat/byte on test connection
- self.nodes[0].p2p.send_and_ping(msg_feefilter(150))
+ conn.send_and_ping(msg_feefilter(150))
# Test that txs are still being received by test connection (paying .15 sat/byte)
node1.settxfee(Decimal("0.00000150"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Change tx fee rate to .1 sat/byte and test they are no longer received
# by the test connection
node1.settxfee(Decimal("0.00000100"))
[node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- self.sync_mempools() # must be sure node 0 has received all txs
+ self.sync_mempools() # must be sure node 0 has received all txs
# Send one transaction from node0 that should be received, so that we
# we can sync the test on receipt (if node1's txs were relayed, they'd
@@ -91,14 +119,15 @@ class FeeFilterTest(BitcoinTestFramework):
# as well.
node0.settxfee(Decimal("0.00020000"))
txids = [node0.sendtoaddress(node0.getnewaddress(), 1)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
# Remove fee filter and check that txs are received again
- self.nodes[0].p2p.send_and_ping(msg_feefilter(0))
+ conn.send_and_ping(msg_feefilter(0))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
- assert allInvsMatch(txids, self.nodes[0].p2p)
- self.nodes[0].p2p.clear_invs()
+ assert allInvsMatch(txids, conn)
+ conn.clear_invs()
+
if __name__ == '__main__':
FeeFilterTest().main()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index d99bc621de..d9a9ae5188 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -7,6 +7,9 @@
from test_framework.messages import (
CBlockHeader,
CInv,
+ MAX_HEADERS_RESULTS,
+ MAX_INV_SIZE,
+ MAX_PROTOCOL_MESSAGE_LENGTH,
msg_getdata,
msg_headers,
msg_inv,
@@ -24,8 +27,7 @@ from test_framework.util import (
wait_until,
)
-MSG_LIMIT = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH
-VALID_DATA_LIMIT = MSG_LIMIT - 5 # Account for the 5-byte length prefix
+VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix
class msg_unrecognized:
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
@@ -53,11 +55,13 @@ class InvalidMessagesTest(BitcoinTestFramework):
self.test_checksum()
self.test_size()
self.test_msgtype()
- self.test_large_inv()
+ self.test_oversized_inv_msg()
+ self.test_oversized_getdata_msg()
+ self.test_oversized_headers_msg()
self.test_resource_exhaustion()
def test_buffer(self):
- self.log.info("Test message with header split across two buffers, should be received")
+ self.log.info("Test message with header split across two buffers is received")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
# Create valid message
msg = conn.build_message(msg_ping(nonce=12345))
@@ -76,6 +80,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
def test_magic_bytes(self):
+ self.log.info("Test message with invalid magic bytes disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART badmsg']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
@@ -83,9 +88,10 @@ class InvalidMessagesTest(BitcoinTestFramework):
msg = b'\xff' * 4 + msg[4:]
conn.send_raw_message(msg)
conn.wait_for_disconnect(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_checksum(self):
+ self.log.info("Test message with invalid checksum logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['CHECKSUM ERROR (badmsg, 2 bytes), expected 78df0a04 was ffffffff']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
@@ -93,21 +99,22 @@ class InvalidMessagesTest(BitcoinTestFramework):
cut_len = 4 + 12 + 4
# modify checksum
msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:]
- self.nodes[0].p2p.send_raw_message(msg)
+ conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_size(self):
+ self.log.info("Test message with oversized payload disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['']):
- # Create a message with oversized payload
msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1))
msg = conn.build_message(msg)
- self.nodes[0].p2p.send_raw_message(msg)
+ conn.send_raw_message(msg)
conn.wait_for_disconnect(timeout=1)
- self.nodes[0].disconnect_p2ps()
+ self.nodes[0].disconnect_p2ps()
def test_msgtype(self):
+ self.log.info("Test message with invalid message type logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: ERRORS IN HEADER']):
msg = msg_unrecognized(str_data="d")
@@ -115,44 +122,52 @@ class InvalidMessagesTest(BitcoinTestFramework):
msg = conn.build_message(msg)
# Modify msgtype
msg = msg[:7] + b'\x00' + msg[7 + 1:]
- self.nodes[0].p2p.send_raw_message(msg)
+ conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
- self.nodes[0].disconnect_p2ps()
-
- def test_large_inv(self):
- conn = self.nodes[0].add_p2p_connection(P2PInterface())
- with self.nodes[0].assert_debug_log(['Misbehaving', '(0 -> 20): message inv size() = 50001']):
- msg = msg_inv([CInv(MSG_TX, 1)] * 50001)
- conn.send_and_ping(msg)
- with self.nodes[0].assert_debug_log(['Misbehaving', '(20 -> 40): message getdata size() = 50001']):
- msg = msg_getdata([CInv(MSG_TX, 1)] * 50001)
- conn.send_and_ping(msg)
- with self.nodes[0].assert_debug_log(['Misbehaving', '(40 -> 60): headers message size = 2001']):
- msg = msg_headers([CBlockHeader()] * 2001)
- conn.send_and_ping(msg)
self.nodes[0].disconnect_p2ps()
+ def test_oversized_msg(self, msg, size):
+ msg_type = msg.msgtype.decode('ascii')
+ self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size))
+ with self.nodes[0].assert_debug_log(['Misbehaving', '{} message size = {}'.format(msg_type, size)]):
+ self.nodes[0].add_p2p_connection(P2PInterface()).send_and_ping(msg)
+ self.nodes[0].disconnect_p2ps()
+
+ def test_oversized_inv_msg(self):
+ size = MAX_INV_SIZE + 1
+ self.test_oversized_msg(msg_inv([CInv(MSG_TX, 1)] * size), size)
+
+ def test_oversized_getdata_msg(self):
+ size = MAX_INV_SIZE + 1
+ self.test_oversized_msg(msg_getdata([CInv(MSG_TX, 1)] * size), size)
+
+ def test_oversized_headers_msg(self):
+ size = MAX_HEADERS_RESULTS + 1
+ self.test_oversized_msg(msg_headers([CBlockHeader()] * size), size)
+
def test_resource_exhaustion(self):
+ self.log.info("Test node stays up despite many large junk messages")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
conn2 = self.nodes[0].add_p2p_connection(P2PDataStore())
msg_at_size = msg_unrecognized(str_data="b" * VALID_DATA_LIMIT)
- assert len(msg_at_size.serialize()) == MSG_LIMIT
-
- self.log.info("Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...")
+ assert len(msg_at_size.serialize()) == MAX_PROTOCOL_MESSAGE_LENGTH
- # Run a bunch of times to test for memory exhaustion.
+ self.log.info("(a) Send 80 messages, each of maximum valid data size (4MB)")
for _ in range(80):
conn.send_message(msg_at_size)
# Check that, even though the node is being hammered by nonsense from one
# connection, it can still service other peers in a timely way.
+ self.log.info("(b) Check node still services peers in a timely way")
for _ in range(20):
conn2.sync_with_ping(timeout=2)
- # Peer 1, despite being served up a bunch of nonsense, should still be connected.
- self.log.info("Waiting for node to drop junk messages.")
+ self.log.info("(c) Wait for node to drop junk messages, while remaining connected")
conn.sync_with_ping(timeout=400)
+
+ # Despite being served up a bunch of nonsense, the peers should still be connected.
assert conn.is_connected
+ assert conn2.is_connected
self.nodes[0].disconnect_p2ps()
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 2c200fccad..bea202855d 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -43,6 +43,12 @@ class P2PPermissionsTests(BitcoinTestFramework):
True)
self.checkpermission(
+ # no permission (even with forcerelay)
+ ["-whitelist=@127.0.0.1", "-whitelistforcerelay=1"],
+ [],
+ False)
+
+ self.checkpermission(
# relay permission removed (no specific permissions)
["-whitelist=127.0.0.1", "-whitelistrelay=0"],
["noban", "mempool"],
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 8803086213..25dd765442 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -1898,8 +1898,7 @@ class SegWitTest(BitcoinTestFramework):
def test_upgrade_after_activation(self):
"""Test the behavior of starting up a segwit-aware node after the softfork has activated."""
- self.stop_node(2)
- self.start_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
+ self.restart_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
connect_nodes(self.nodes[0], 2)
# We reconnect more than 100 blocks, give it plenty of time
diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py
index bd93b6f7a4..8fa36445cd 100755
--- a/test/functional/rpc_getblockfilter.py
+++ b/test/functional/rpc_getblockfilter.py
@@ -7,7 +7,7 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal, assert_is_hex_string, assert_raises_rpc_error,
- connect_nodes, disconnect_nodes, sync_blocks
+ connect_nodes, disconnect_nodes
)
FILTER_TYPES = ["basic"]
@@ -30,7 +30,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
# Reorg node 0 to a new chain
connect_nodes(self.nodes[0], 1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
assert_equal(self.nodes[0].getblockcount(), 4)
chain1_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index c20f813d14..660953be9b 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -475,7 +475,7 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(analysis['next'], 'creator')
assert_equal(analysis['error'], 'PSBT is not valid. Input 0 specifies invalid prevout')
- assert_raises_rpc_error(-25, 'Missing inputs', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
+ assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 4d1dd4422e..eb1244035f 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -45,6 +45,10 @@ MAX_MONEY = 21000000 * COIN
BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out
+MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
+MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result
+MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message
+
NODE_NETWORK = (1 << 0)
NODE_GETUTXO = (1 << 1)
NODE_BLOOM = (1 << 2)
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index b6c37bc7e0..e6da33763d 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -26,6 +26,7 @@ import threading
from test_framework.messages import (
CBlockHeader,
+ MAX_HEADERS_RESULTS,
MIN_VERSION_SUPPORTED,
msg_addr,
msg_block,
@@ -553,7 +554,6 @@ class P2PDataStore(P2PInterface):
return
headers_list = [self.block_store[self.last_block_hash]]
- maxheaders = 2000
while headers_list[-1].sha256 not in locator.vHave:
# Walk back through the block store, adding headers to headers_list
# as we go.
@@ -569,7 +569,7 @@ class P2PDataStore(P2PInterface):
break
# Truncate the list if there are too many headers
- headers_list = headers_list[:-maxheaders - 1:-1]
+ headers_list = headers_list[:-MAX_HEADERS_RESULTS - 1:-1]
response = msg_headers(headers_list)
if response is not None:
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index c9fad91481..9d9e065158 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -31,8 +31,6 @@ from .util import (
disconnect_nodes,
get_datadir_path,
initialize_datadir,
- sync_blocks,
- sync_mempools,
)
@@ -355,9 +353,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# See fPreferredDownload in net_processing.
#
# If further outbound connections are needed, they can be added at the beginning of the test with e.g.
- # connect_nodes(self.nodes[1], 2)
+ # self.connect_nodes(1, 2)
for i in range(self.num_nodes - 1):
- connect_nodes(self.nodes[i + 1], i)
+ self.connect_nodes(i + 1, i)
self.sync_all()
def setup_nodes(self):
@@ -534,11 +532,17 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def wait_for_node_exit(self, i, timeout):
self.nodes[i].process.wait(timeout)
+ def connect_nodes(self, a, b):
+ connect_nodes(self.nodes[a], b)
+
+ def disconnect_nodes(self, a, b):
+ disconnect_nodes(self.nodes[a], b)
+
def split_network(self):
"""
Split the network of four nodes into nodes 0/1 and 2/3.
"""
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(1, 2)
self.sync_all(self.nodes[:2])
self.sync_all(self.nodes[2:])
@@ -546,18 +550,57 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""
Join the (previously split) network halves together.
"""
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.sync_all()
- def sync_blocks(self, nodes=None, **kwargs):
- sync_blocks(nodes or self.nodes, **kwargs)
-
- def sync_mempools(self, nodes=None, **kwargs):
- sync_mempools(nodes or self.nodes, **kwargs)
-
- def sync_all(self, nodes=None, **kwargs):
- self.sync_blocks(nodes, **kwargs)
- self.sync_mempools(nodes, **kwargs)
+ def sync_blocks(self, nodes=None, wait=1, timeout=60):
+ """
+ Wait until everybody has the same tip.
+ sync_blocks needs to be called with an rpc_connections set that has least
+ one node already synced to the latest, stable tip, otherwise there's a
+ chance it might return before all nodes are stably synced.
+ """
+ rpc_connections = nodes or self.nodes
+ timeout = int(timeout * self.options.timeout_factor)
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
+ best_hash = [x.getbestblockhash() for x in rpc_connections]
+ if best_hash.count(best_hash[0]) == len(rpc_connections):
+ return
+ # Check that each peer has at least one connection
+ assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
+ time.sleep(wait)
+ raise AssertionError("Block sync timed out after {}s:{}".format(
+ timeout,
+ "".join("\n {!r}".format(b) for b in best_hash),
+ ))
+
+ def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True):
+ """
+ Wait until everybody has the same transactions in their memory
+ pools
+ """
+ rpc_connections = nodes or self.nodes
+ timeout = int(timeout * self.options.timeout_factor)
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
+ pool = [set(r.getrawmempool()) for r in rpc_connections]
+ if pool.count(pool[0]) == len(rpc_connections):
+ if flush_scheduler:
+ for r in rpc_connections:
+ r.syncwithvalidationinterfacequeue()
+ return
+ # Check that each peer has at least one connection
+ assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
+ time.sleep(wait)
+ raise AssertionError("Mempool sync timed out after {}s:{}".format(
+ timeout,
+ "".join("\n {!r}".format(m) for m in pool),
+ ))
+
+ def sync_all(self, nodes=None):
+ self.sync_blocks(nodes)
+ self.sync_mempools(nodes)
# Private helper methods. These should not be accessed by the subclass test scripts.
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 17b2cbb971..506057f1fa 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -444,50 +444,6 @@ def connect_nodes(from_connection, node_num):
wait_until(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
-def sync_blocks(rpc_connections, *, wait=1, timeout=60):
- """
- Wait until everybody has the same tip.
-
- sync_blocks needs to be called with an rpc_connections set that has least
- one node already synced to the latest, stable tip, otherwise there's a
- chance it might return before all nodes are stably synced.
- """
- stop_time = time.time() + timeout
- while time.time() <= stop_time:
- best_hash = [x.getbestblockhash() for x in rpc_connections]
- if best_hash.count(best_hash[0]) == len(rpc_connections):
- return
- # Check that each peer has at least one connection
- assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
- time.sleep(wait)
- raise AssertionError("Block sync timed out after {}s:{}".format(
- timeout,
- "".join("\n {!r}".format(b) for b in best_hash),
- ))
-
-
-def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True):
- """
- Wait until everybody has the same transactions in their memory
- pools
- """
- stop_time = time.time() + timeout
- while time.time() <= stop_time:
- pool = [set(r.getrawmempool()) for r in rpc_connections]
- if pool.count(pool[0]) == len(rpc_connections):
- if flush_scheduler:
- for r in rpc_connections:
- r.syncwithvalidationinterfacequeue()
- return
- # Check that each peer has at least one connection
- assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
- time.sleep(wait)
- raise AssertionError("Mempool sync timed out after {}s:{}".format(
- timeout,
- "".join("\n {!r}".format(m) for m in pool),
- ))
-
-
# Transaction/Block functions
#############################
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index 90d17a806c..8837e13005 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -95,8 +95,7 @@ class AbandonConflictTest(BitcoinTestFramework):
# Restart the node with a higher min relay fee so the parent tx is no longer in mempool
# TODO: redo with eviction
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.0001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
assert self.nodes[0].getmempoolinfo()['loaded']
# Verify txs no longer in either node's mempool
@@ -123,8 +122,7 @@ class AbandonConflictTest(BitcoinTestFramework):
balance = newbalance
# Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.00001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"])
assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0)
@@ -145,8 +143,7 @@ class AbandonConflictTest(BitcoinTestFramework):
balance = newbalance
# Remove using high relay fee again
- self.stop_node(0)
- self.start_node(0, extra_args=["-minrelaytxfee=0.0001"])
+ self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
assert self.nodes[0].getmempoolinfo()['loaded']
assert_equal(len(self.nodes[0].getrawmempool()), 0)
newbalance = self.nodes[0].getbalance()
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 780cce9d02..eddd938847 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -110,9 +110,7 @@ class AvoidReuseTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
- # Stop and restart node 1
- self.stop_node(1)
- self.start_node(1)
+ self.restart_node(1)
connect_nodes(self.nodes[0], 1)
# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 8efa66a856..31829a18b3 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -12,7 +12,6 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
connect_nodes,
- sync_blocks,
)
@@ -264,7 +263,7 @@ class WalletTest(BitcoinTestFramework):
# Now confirm tx_orig
self.restart_node(1, ['-persistmempool=0'])
connect_nodes(self.nodes[0], 1)
- sync_blocks(self.nodes)
+ self.sync_blocks()
self.nodes[1].sendrawtransaction(tx_orig)
self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)
self.sync_all()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 797c903dd3..8962362276 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -219,6 +219,60 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ # Sendmany with explicit fee (BTC/kB)
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ estimate_mode='bTc/kB')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ conf_target=-1,
+ estimate_mode='bTc/kB')
+ fee_per_kb = 0.0002500
+ explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
+ txid = self.nodes[2].sendmany(
+ amounts={ address: 10 },
+ conf_target=fee_per_kb,
+ estimate_mode='bTc/kB',
+ )
+ self.nodes[2].generate(1)
+ self.sync_all(self.nodes[0:3])
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+ node_0_bal += Decimal('10')
+ assert_equal(self.nodes[0].getbalance(), node_0_bal)
+
+ # Sendmany with explicit fee (SAT/B)
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ estimate_mode='sat/b')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendmany,
+ amounts={ address: 10 },
+ conf_target=-1,
+ estimate_mode='sat/b')
+ fee_sat_per_b = 2
+ fee_per_kb = fee_sat_per_b / 100000.0
+ explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
+ txid = self.nodes[2].sendmany(
+ amounts={ address: 10 },
+ conf_target=fee_sat_per_b,
+ estimate_mode='sAT/b',
+ )
+ self.nodes[2].generate(1)
+ self.sync_all(self.nodes[0:3])
+ balance = self.nodes[2].getbalance()
+ node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ assert_equal(balance, node_2_bal)
+ node_0_bal += Decimal('10')
+ assert_equal(self.nodes[0].getbalance(), node_0_bal)
+
self.start_node(3, self.nodes[3].extra_args)
connect_nodes(self.nodes[0], 3)
self.sync_all()
@@ -349,6 +403,74 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
+ # send with explicit btc/kb fee
+ self.log.info("test explicit fee (sendtoaddress as btc/kb)")
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ prebalance = self.nodes[2].getbalance()
+ assert prebalance > 2
+ address = self.nodes[1].getnewaddress()
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ estimate_mode='BTc/Kb')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ conf_target=-1,
+ estimate_mode='btc/kb')
+ txid = self.nodes[2].sendtoaddress(
+ address=address,
+ amount=1.0,
+ conf_target=0.00002500,
+ estimate_mode='btc/kb',
+ )
+ tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
+ self.sync_all(self.nodes[0:3])
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ postbalance = self.nodes[2].getbalance()
+ fee = prebalance - postbalance - Decimal('1')
+ assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
+
+ # send with explicit sat/b fee
+ self.sync_all(self.nodes[0:3])
+ self.log.info("test explicit fee (sendtoaddress as sat/b)")
+ self.nodes[0].generate(1)
+ prebalance = self.nodes[2].getbalance()
+ assert prebalance > 2
+ address = self.nodes[1].getnewaddress()
+ # Throw if no conf_target provided
+ assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ estimate_mode='SAT/b')
+ # Throw if negative feerate
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[2].sendtoaddress,
+ address=address,
+ amount=1.0,
+ conf_target=-1,
+ estimate_mode='SAT/b')
+ txid = self.nodes[2].sendtoaddress(
+ address=address,
+ amount=1.0,
+ conf_target=2,
+ estimate_mode='SAT/B',
+ )
+ tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
+ self.sync_all(self.nodes[0:3])
+ self.nodes[0].generate(1)
+ self.sync_all(self.nodes[0:3])
+ postbalance = self.nodes[2].getbalance()
+ fee = prebalance - postbalance - Decimal('1')
+ assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
+
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 27197e3b6d..72c85b8832 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -71,6 +71,7 @@ class BumpFeeTest(BitcoinTestFramework):
self.log.info("Running tests")
dest_address = peer_node.getnewaddress()
+ test_invalid_parameters(rbf_node, dest_address)
test_simple_bumpfee_succeeds(self, "default", rbf_node, peer_node, dest_address)
test_simple_bumpfee_succeeds(self, "fee_rate", rbf_node, peer_node, dest_address)
test_feerate_args(self, rbf_node, peer_node, dest_address)
@@ -92,6 +93,28 @@ class BumpFeeTest(BitcoinTestFramework):
test_small_output_with_feerate_succeeds(self, rbf_node, dest_address)
test_no_more_inputs_fails(self, rbf_node, dest_address)
+def test_invalid_parameters(node, dest_address):
+ txid = spend_one_input(node, dest_address)
+ # invalid estimate mode
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
+ "estimate_mode": "moo",
+ })
+ assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
+ "estimate_mode": 38,
+ })
+ assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
+ "estimate_mode": {
+ "foo": "bar",
+ },
+ })
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
+ "estimate_mode": Decimal("3.141592"),
+ })
+ # confTarget and conf_target
+ assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set", node.bumpfee, txid, {
+ "confTarget": 123,
+ "conf_target": 456,
+ })
def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
self.log.info('Test simple bumpfee: {}'.format(mode))
@@ -127,9 +150,10 @@ def test_feerate_args(self, rbf_node, peer_node, dest_address):
self.sync_mempools((rbf_node, peer_node))
assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
- assert_raises_rpc_error(-8, "confTarget can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
+ assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
+ assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "confTarget": 1})
# Bumping to just above minrelay should fail to increase total fee enough, at least
assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py
index cc349c7bc5..ba1e494d9a 100755
--- a/test/functional/wallet_dump.py
+++ b/test/functional/wallet_dump.py
@@ -190,8 +190,7 @@ class WalletDumpTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump))
# Restart node with new wallet, and test importwallet
- self.stop_node(0)
- self.start_node(0, ['-wallet=w2'])
+ self.restart_node(0, ['-wallet=w2'])
# Make sure the address is not IsMine before import
result = self.nodes[0].getaddressinfo(multisig_addr)
diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py
index c441b75652..3c336623e2 100755
--- a/test/functional/wallet_hd.py
+++ b/test/functional/wallet_hd.py
@@ -103,8 +103,7 @@ class WalletHDTest(BitcoinTestFramework):
self.sync_all()
# Needs rescan
- self.stop_node(1)
- self.start_node(1, extra_args=self.extra_args[1] + ['-rescan'])
+ self.restart_node(1, extra_args=self.extra_args[1] + ['-rescan'])
assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)
# Try a RPC based rescan
@@ -183,8 +182,7 @@ class WalletHDTest(BitcoinTestFramework):
self.nodes[0].generate(10)
# Restart node 1 with keypool of 3 and a different wallet
self.nodes[1].createwallet(wallet_name='origin', blank=True)
- self.stop_node(1)
- self.start_node(1, extra_args=['-keypool=3', '-wallet=origin'])
+ self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin'])
connect_nodes(self.nodes[0], 1)
# sethdseed restoring and seeing txs to addresses out of the keypool
diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py
index 497a5dd95e..455f1fc5e8 100755
--- a/test/functional/wallet_reorgsrestore.py
+++ b/test/functional/wallet_reorgsrestore.py
@@ -77,8 +77,7 @@ class ReorgsRestoreTest(BitcoinTestFramework):
assert_equal(conflicted["walletconflicts"][0], conflicting["txid"])
# Node0 wallet is shutdown
- self.stop_node(0)
- self.start_node(0)
+ self.restart_node(0)
# The block chain re-orgs and the tx is included in a different block
self.nodes[1].generate(9)
diff --git a/test/functional/wallet_zapwallettxes.py b/test/functional/wallet_zapwallettxes.py
index adebff360a..7f1cdbd20b 100755
--- a/test/functional/wallet_zapwallettxes.py
+++ b/test/functional/wallet_zapwallettxes.py
@@ -49,17 +49,15 @@ class ZapWalletTXesTest (BitcoinTestFramework):
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop-start node0. Both confirmed and unconfirmed transactions remain in the wallet.
- self.stop_node(0)
- self.start_node(0)
+ # Restart node0. Both confirmed and unconfirmed transactions remain in the wallet.
+ self.restart_node(0)
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop node0 and restart with zapwallettxes and persistmempool. The unconfirmed
+ # Restart node0 with zapwallettxes and persistmempool. The unconfirmed
# transaction is zapped from the wallet, but is re-added when the mempool is reloaded.
- self.stop_node(0)
- self.start_node(0, ["-persistmempool=1", "-zapwallettxes=2"])
+ self.restart_node(0, ["-persistmempool=1", "-zapwallettxes=2"])
wait_until(lambda: self.nodes[0].getmempoolinfo()['size'] == 1, timeout=3)
self.nodes[0].syncwithvalidationinterfacequeue() # Flush mempool to wallet
@@ -67,10 +65,9 @@ class ZapWalletTXesTest (BitcoinTestFramework):
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
- # Stop node0 and restart with zapwallettxes, but not persistmempool.
+ # Restart node0 with zapwallettxes, but not persistmempool.
# The unconfirmed transaction is zapped and is no longer in the wallet.
- self.stop_node(0)
- self.start_node(0, ["-zapwallettxes=2"])
+ self.restart_node(0, ["-zapwallettxes=2"])
# tx1 is still be available because it was confirmed
assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh
index decea38c4f..72e8ef7c7d 100755
--- a/test/lint/lint-python.sh
+++ b/test/lint/lint-python.sh
@@ -39,7 +39,6 @@ enabled=(
E711 # comparison to None should be 'if cond is None:'
E714 # test for object identity should be "is not"
E721 # do not compare types, use "isinstance()"
- E741 # do not use variables named "l", "O", or "I"
E742 # do not define classes named "l", "O", or "I"
E743 # do not define functions named "l", "O", or "I"
E901 # SyntaxError: invalid syntax
diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh
index 563e076b35..9a26cd9c02 100755
--- a/test/lint/lint-shell.sh
+++ b/test/lint/lint-shell.sh
@@ -25,7 +25,6 @@ disabled=(
disabled_gitian=(
SC2094 # Make sure not to read and write the same file in the same pipeline.
SC2129 # Consider using { cmd1; cmd2; } >> file instead of individual redirects.
- SC2230 # which is non-standard. Use builtin 'command -v' instead.
)
EXIT_CODE=0
diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt
index a7a97eb41f..34f54325b3 100644
--- a/test/lint/lint-spelling.ignore-words.txt
+++ b/test/lint/lint-spelling.ignore-words.txt
@@ -14,3 +14,4 @@ setban
hist
ser
unselect
+lowercased