diff options
98 files changed, 784 insertions, 572 deletions
diff --git a/.travis.yml b/.travis.yml index 73e62cd2b0..3ede878839 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ env: global: - CI_RETRY_EXE="travis_retry" - CI_WAIT="while sleep 500; do echo .; done" - - CACHE_ERR_MSG="Error! Initial build successful, but not enough time remains to run later build stages and tests. Please manually re-run this job by using the travis restart button or asking a bitcoin maintainer to restart. The next run should not time out because the build cache has been saved." + - CACHE_ERR_MSG="Error! Initial build successful, but not enough time remains to run later build stages and tests. See https://docs.travis-ci.com/user/customizing-the-build#build-timeouts . Please manually re-run this job by using the travis restart button. The next run should not time out because the build cache has been saved." before_install: - set -o errexit; source ./ci/test/00_setup_env.sh - set -o errexit; source ./ci/test/03_before_install.sh @@ -144,7 +144,7 @@ jobs: FILE_ENV="./ci/test/00_setup_env_native_nowallet.sh" - stage: test - name: 'macOS 10.10 [GOAL: deploy] [no functional tests]' + name: 'macOS 10.12 [GOAL: deploy] [no functional tests]' env: >- FILE_ENV="./ci/test/00_setup_env_mac.sh" @@ -152,8 +152,8 @@ jobs: name: 'macOS 10.14 native [GOAL: install] [GUI] [no depends]' os: osx # Use the most recent version: - # Xcode 11, macOS 10.14, JDK 12.0.1 + # Xcode 11.2.1, macOS 10.14, JDK 13.0.1, SDK 10.15 # https://docs.travis-ci.com/user/reference/osx/#macos-version - osx_image: xcode11 + osx_image: xcode11.2 env: >- FILE_ENV="./ci/test/00_setup_env_mac_host.sh" diff --git a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in index 56401d7618..128c1bd8e7 100644 --- a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in +++ b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj.in @@ -42,6 +42,9 @@ <ProjectReference Include="..\libleveldb\libleveldb.vcxproj"> <Project>{18430fef-6b61-4c53-b396-718e02850f1b}</Project> </ProjectReference> + <ProjectReference Include="..\libtest_util\libtest_util.vcxproj"> + <Project>{1e065f03-3566-47d0-8fa9-daa72b084e7d}</Project> + </ProjectReference> </ItemGroup> <Target Name="RawBenchHeaderGen" BeforeTargets="PrepareForBuild"> <PropertyGroup> diff --git a/build_msvc/bitcoin.sln b/build_msvc/bitcoin.sln index d4b83b6529..9dff0e316e 100644 --- a/build_msvc/bitcoin.sln +++ b/build_msvc/bitcoin.sln @@ -44,6 +44,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbitcoin_qt", "libbitcoin EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bitcoin-qt", "bitcoin-qt\bitcoin-qt.vcxproj", "{7E99172D-7FF2-4CB6-B736-AC9B76ED412A}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtest_util", "libtest_util\libtest_util.vcxproj", "{868474FD-35F6-4400-8EED-30A33E7521D4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -220,11 +222,21 @@ Global {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x64.Build.0 = Release|x64 {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x86.ActiveCfg = Release|Win32 {7E99172D-7FF2-4CB6-B736-AC9B76ED412A}.Release|x86.Build.0 = Release|Win32 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Debug|x64.ActiveCfg = Debug|x64 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Debug|x64.Build.0 = Debug|x64 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Debug|x86.ActiveCfg = Debug|Win32 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Debug|x86.Build.0 = Debug|Win32 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Release|x64.ActiveCfg = Release|x64 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Release|x64.Build.0 = Release|x64 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Release|x86.ActiveCfg = Release|Win32 + {868474FD-35F6-4400-8EED-30A33E7521D4}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {8AA72EDA-2CD4-4564-B1E4-688B760EEEE9} + SolutionGuid = {8AA72EDA-2CD4-4564-B1E4-688B760EEEE9} + SolutionGuid = {8607C0F4-F33D-41B8-8D51-18E366A0F8DF} + SolutionGuid = {58AAB032-7274-49BD-845E-5EF4DBB69B70} EndGlobalSection EndGlobal diff --git a/build_msvc/libtest_util/libtest_util.vcxproj.in b/build_msvc/libtest_util/libtest_util.vcxproj.in new file mode 100644 index 0000000000..b5e844010e --- /dev/null +++ b/build_msvc/libtest_util/libtest_util.vcxproj.in @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\common.init.vcxproj" /> + <PropertyGroup Label="Globals"> + <ProjectGuid>{868474FD-35F6-4400-8EED-30A33E7521D4}</ProjectGuid> + </PropertyGroup> + <PropertyGroup Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + </PropertyGroup> + <ItemGroup> +@SOURCE_FILES@ + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <Import Project="..\common.vcxproj" /> +</Project> diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index 5ddda3c03e..3951438408 100644 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -18,6 +18,7 @@ libs = [ 'libbitcoin_wallet', 'libbitcoin_zmq', 'bench_bitcoin', + 'libtest_util', ] ignore_list = [ diff --git a/build_msvc/test_bitcoin/test_bitcoin.vcxproj b/build_msvc/test_bitcoin/test_bitcoin.vcxproj index 1d05ff6cba..96d20a2720 100644 --- a/build_msvc/test_bitcoin/test_bitcoin.vcxproj +++ b/build_msvc/test_bitcoin/test_bitcoin.vcxproj @@ -42,6 +42,9 @@ <ProjectReference Include="..\libbitcoin_zmq\libbitcoin_zmq.vcxproj"> <Project>{792d487f-f14c-49fc-a9de-3fc150f31c3f}</Project> </ProjectReference> + <ProjectReference Include="..\libtest_util\libtest_util.vcxproj"> + <Project>{1e065f03-3566-47d0-8fa9-daa72b084e7d}</Project> + </ProjectReference> <ProjectReference Include="..\libunivalue\libunivalue.vcxproj"> <Project>{5724ba7d-a09a-4ba8-800b-c4c1561b3d69}</Project> </ProjectReference> diff --git a/ci/README.md b/ci/README.md index fb1cd7460b..880e49b459 100644 --- a/ci/README.md +++ b/ci/README.md @@ -8,11 +8,21 @@ and numbered according to which stage and lifecycle step it belongs to. ### Running a stage locally +Be aware that the tests will be built and run in-place, so please run at your own risk. +If the repository is not a fresh git clone, you might have to clean files from previous builds or test runs first. + +The ci needs to perform various sysadmin tasks such as installing packages or writing to the user's home directory. +While most of the actions are done inside a docker container, this is not possible for all. Thus, cache directories, +such as the depends cache or ccache, are mounted as read-write into the docker container. While it should be fine to run +the ci system locally on you development box, the ci scripts can generally be assumed to have received less review and +testing compared to other parts of the codebase. If you want to keep the work tree clean, you might want to run the ci +system in a virtual machine with a Linux operating system of your choice. + To allow for a wide range of tested environments, but also ensure reproducibility to some extent, the test stage requires `docker` to be installed. To install all requirements on Ubuntu, run ``` -sudo apt install docker.io bash git +sudo apt install docker.io bash ``` To run the default test stage, @@ -26,6 +36,3 @@ To run the test stage with a specific configuration, ``` FILE_ENV="./ci/test/00_setup_env_arm.sh" ./ci/test_run_all.sh ``` - -Be aware that the tests will be build and run in-place, so please run at your own risk. -If the repository is not a fresh git clone, you might have to clean files from previous builds or test runs first. diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index c65054cf9e..2919072fe4 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -44,7 +44,7 @@ export BASE_BUILD_DIR=${BASE_BUILD_DIR:-$BASE_ROOT_DIR} export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_BUILD_DIR/out/$HOST} export SDK_URL=${SDK_URL:-https://bitcoincore.org/depends-sources/sdks} export WINEDEBUG=${WINEDEBUG:-fixme-all} -export DOCKER_PACKAGES=${DOCKER_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3} +export DOCKER_PACKAGES=${DOCKER_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3 rsync git} export GOAL=${GOAL:-install} export DIR_QA_ASSETS=${DIR_QA_ASSETS:-${BASE_BUILD_DIR}/qa-assets} export PATH=${BASE_ROOT_DIR}/ci/retry:$PATH diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh index f384ba9263..af166b6ca7 100644 --- a/ci/test/00_setup_env_mac.sh +++ b/ci/test/00_setup_env_mac.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export HOST=x86_64-apple-darwin14 +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.11 export RUN_UNIT_TESTS=false diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_host.sh index aa68a5700e..0b437a723f 100644 --- a/ci/test/00_setup_env_mac_host.sh +++ b/ci/test/00_setup_env_mac_host.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export HOST=x86_64-apple-darwin14 +export HOST=x86_64-apple-darwin16 export BREW_PACKAGES="automake berkeley-db4 libtool boost miniupnpc pkg-config qt qrencode python3 ccache zeromq" export PIP_PACKAGES="zmq" export RUN_CI_ON_HOST=true diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh index 38e5e6871b..b354940d35 100644 --- a/ci/test/00_setup_env_native_asan.sh +++ b/ci/test/00_setup_env_native_asan.sh @@ -9,4 +9,4 @@ export LC_ALL=C.UTF-8 export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev" export NO_DEPENDS=1 export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" +export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh index 9c38e87b48..f7a16206ab 100644 --- a/ci/test/00_setup_env_native_tsan.sh +++ b/ci/test/00_setup_env_native_tsan.sh @@ -10,4 +10,4 @@ export DOCKER_NAME_TAG=ubuntu:16.04 export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libqrencode-dev" export NO_DEPENDS=1 export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --disable-wallet --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=thread --disable-hardening --disable-asm CC=clang CXX=clang++" +export BITCOIN_CONFIG="--enable-zmq --disable-wallet --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=thread --disable-hardening --disable-asm CC=clang CXX=clang++" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index e70a8a9ea9..ce41c37b6e 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -33,12 +33,6 @@ fi mkdir -p "${BASE_SCRATCH_DIR}" mkdir -p "${CCACHE_DIR}" -if [ ! -d ${DIR_QA_ASSETS} ]; then - git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS} -fi -export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/ - -mkdir -p "${BASE_BUILD_DIR}/sanitizer-output/" export ASAN_OPTIONS="detect_stack_use_after_return=1" export LSAN_OPTIONS="suppressions=${BASE_BUILD_DIR}/test/sanitizer_suppressions/lsan" export TSAN_OPTIONS="suppressions=${BASE_BUILD_DIR}/test/sanitizer_suppressions/tsan:log_path=${BASE_BUILD_DIR}/sanitizer-output/tsan" @@ -54,7 +48,13 @@ if [ -z "$RUN_CI_ON_HOST" ]; then echo "Creating $DOCKER_NAME_TAG container to run in" ${CI_RETRY_EXE} docker pull "$DOCKER_NAME_TAG" - DOCKER_ID=$(docker run $DOCKER_ADMIN -idt --mount type=bind,src=$BASE_BUILD_DIR,dst=$BASE_BUILD_DIR --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR -w $BASE_BUILD_DIR --env-file /tmp/env $DOCKER_NAME_TAG) + DOCKER_ID=$(docker run $DOCKER_ADMIN -idt \ + --mount type=bind,src=$BASE_BUILD_DIR,dst=/ro_base,readonly \ + --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR \ + --mount type=bind,src=$BASE_BUILD_DIR/depends,dst=$BASE_BUILD_DIR/depends \ + -w $BASE_BUILD_DIR \ + --env-file /tmp/env \ + $DOCKER_NAME_TAG) DOCKER_EXEC () { docker exec $DOCKER_ID bash -c "export PATH=$BASE_SCRATCH_DIR/bins/:\$PATH && cd $PWD && $*" @@ -83,6 +83,18 @@ if [ "$TRAVIS_OS_NAME" != "osx" ]; then ${CI_RETRY_EXE} DOCKER_EXEC apt-get install --no-install-recommends --no-upgrade -y $PACKAGES $DOCKER_PACKAGES fi +if [ ! -d ${DIR_QA_ASSETS} ]; then + DOCKER_EXEC git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS} +fi +export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/ + +DOCKER_EXEC mkdir -p "${BASE_BUILD_DIR}/sanitizer-output/" + +if [ -z "$RUN_CI_ON_HOST" ]; then + echo "Create $BASE_BUILD_DIR" + DOCKER_EXEC rsync -a /ro_base/ $BASE_BUILD_DIR +fi + if [ "$USE_BUSY_BOX" = "true" ]; then echo "Setup to use BusyBox utils" DOCKER_EXEC mkdir -p $BASE_SCRATCH_DIR/bins/ diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index a0488f0807..3f0bba19ac 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -13,13 +13,13 @@ else DOCKER_EXEC echo \> \$HOME/.bitcoin fi -mkdir -p depends/SDKs depends/sdk-sources +DOCKER_EXEC mkdir -p depends/SDKs depends/sdk-sources if [ -n "$OSX_SDK" ] && [ ! -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then curl --location --fail $SDK_URL/MacOSX${OSX_SDK}.sdk.tar.gz -o depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz fi if [ -n "$OSX_SDK" ] && [ -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then - tar -C depends/SDKs -xf depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz + DOCKER_EXEC tar -C depends/SDKs -xf depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz fi if [[ $HOST = *-mingw32 ]]; then DOCKER_EXEC update-alternatives --set $HOST-g++ \$\(which $HOST-g++-posix\) diff --git a/ci/test/06_script_a.sh b/ci/test/06_script_a.sh index c4dc22bdd8..34b8477197 100755 --- a/ci/test/06_script_a.sh +++ b/ci/test/06_script_a.sh @@ -19,7 +19,9 @@ else fi END_FOLD +# Create folder on host and docker, so that `cd` works mkdir -p build +DOCKER_EXEC mkdir -p build # Temporarily disable errexit, because Travis macOS fails without error message set +o errexit @@ -27,10 +29,12 @@ cd build || (echo "could not enter build directory"; exit 1) set -o errexit BEGIN_FOLD configure -DOCKER_EXEC ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) +DOCKER_EXEC ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( (DOCKER_EXEC cat config.log) && false) END_FOLD BEGIN_FOLD distdir +# Create folder on host and docker, so that `cd` works +mkdir -p "bitcoin-$HOST" DOCKER_EXEC make distdir VERSION=$HOST END_FOLD @@ -39,7 +43,7 @@ cd "bitcoin-$HOST" || (echo "could not enter distdir bitcoin-$HOST"; exit 1) set -o errexit BEGIN_FOLD configure -DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) +DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( (DOCKER_EXEC cat config.log) && false) END_FOLD set -o errtrace diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index 7ac4907d32..49962bb49b 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -48,7 +48,3 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then DOCKER_EXEC test/fuzz/test_runner.py -l DEBUG ${DIR_FUZZ_IN} END_FOLD fi - -set +o errexit -cd ${BASE_BUILD_DIR} || (echo "could not enter travis build dir $BASE_BUILD_DIR"; exit 1) -set -o errexit diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index d8b684026c..c224085c32 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -57,7 +57,6 @@ ALLOWED_LIBRARIES = { 'libgcc_s.so.1', # GCC base support 'libc.so.6', # C library 'libpthread.so.0', # threading -'libanl.so.1', # DNS resolve 'libm.so.6', # math library 'librt.so.1', # real-time (clock) 'libatomic.so.1', diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index 79b1b22cdf..5f006d3a19 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -37,7 +37,7 @@ script: | set -e -o pipefail WRAP_DIR=$HOME/wrapped - HOSTS="x86_64-apple-darwin14" + HOSTS="x86_64-apple-darwin16" CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests GENISOIMAGE=$WRAP_DIR/genisoimage" FAKETIME_HOST_PROGS="" FAKETIME_PROGS="ar ranlib date dmg genisoimage" diff --git a/depends/README.md b/depends/README.md index e5e2a8a653..b56302b6c7 100644 --- a/depends/README.md +++ b/depends/README.md @@ -25,7 +25,7 @@ Common `host-platform-triplets` for cross compilation are: - `i686-pc-linux-gnu` for Linux 32 bit - `x86_64-pc-linux-gnu` for x86 Linux - `x86_64-w64-mingw32` for Win64 -- `x86_64-apple-darwin14` for macOS +- `x86_64-apple-darwin16` for macOS - `arm-linux-gnueabihf` for Linux ARM 32 bit - `aarch64-linux-gnu` for Linux ARM 64 bit - `riscv32-linux-gnu` for Linux RISC-V 32 bit diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk index a1c943d60b..1f88c209cf 100644 --- a/depends/hosts/darwin.mk +++ b/depends/hosts/darwin.mk @@ -1,4 +1,4 @@ -OSX_MIN_VERSION=10.10 +OSX_MIN_VERSION=10.12 OSX_SDK_VERSION=10.11 OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk LD64_VERSION=253.9 diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk index b4333abaf0..eb45e14f6f 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -1,19 +1,12 @@ package=libevent -$(package)_version=2.1.8-stable +$(package)_version=2.1.11-stable $(package)_download_path=https://github.com/libevent/libevent/archive/ $(package)_file_name=release-$($(package)_version).tar.gz -$(package)_sha256_hash=316ddb401745ac5d222d7c529ef1eada12f58f6376a66c1118eee803cb70f83d -$(package)_patches=fix_android_arc4random_addrandom.patch +$(package)_sha256_hash=229393ab2bf0dc94694f21836846b424f3532585bac3468738b7bf752c03901e -ifneq (,$(findstring android,$(host))) - define $(package)_preprocess_cmds - ./autogen.sh && patch -p1 < $($(package)_patch_dir)/fix_android_arc4random_addrandom.patch - endef -else - define $(package)_preprocess_cmds - ./autogen.sh - endef -endif +define $(package)_preprocess_cmds + ./autogen.sh +endef define $(package)_set_vars $(package)_config_opts=--disable-shared --disable-openssl --disable-libevent-regress --disable-samples diff --git a/depends/patches/libevent/fix_android_arc4random_addrandom.patch b/depends/patches/libevent/fix_android_arc4random_addrandom.patch deleted file mode 100644 index 5bcc64bef6..0000000000 --- a/depends/patches/libevent/fix_android_arc4random_addrandom.patch +++ /dev/null @@ -1,68 +0,0 @@ -From cadae3ab7abf45e61ecae8aac39d97d1f3cbd336 Mon Sep 17 00:00:00 2001 -From: Lawrence Nahum <lawrence@greenaddress.it> -Date: Sun, 3 Dec 2017 22:56:09 +0100 -Subject: [PATCH] fixup - ---- - configure.ac | 1 + - evutil_rand.c | 3 +++ - include/event2/util.h | 4 ++-- - 3 files changed, 6 insertions(+), 2 deletions(-) - -diff --git a/configure.ac b/configure.ac -index 7528d37..3bb2121 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -341,6 +341,7 @@ dnl Checks for library functions. - AC_CHECK_FUNCS([ \ - accept4 \ - arc4random \ -+ arc4random_addrandom \ - arc4random_buf \ - eventfd \ - epoll_create1 \ -diff --git a/evutil_rand.c b/evutil_rand.c -index 046a14b..3f0bf2c 100644 ---- a/evutil_rand.c -+++ b/evutil_rand.c -@@ -191,6 +191,7 @@ evutil_secure_rng_get_bytes(void *buf, size_t n) - { - ev_arc4random_buf(buf, n); - } -+#ifdef HAVE_ARC4RANDOM_ADDRANDOM - - void - evutil_secure_rng_add_bytes(const char *buf, size_t n) -@@ -199,6 +200,8 @@ evutil_secure_rng_add_bytes(const char *buf, size_t n) - n>(size_t)INT_MAX ? INT_MAX : (int)n); - } - -+#endif -+ - void - evutil_free_secure_rng_globals_(void) - { -diff --git a/include/event2/util.h b/include/event2/util.h -index dd4bbb6..a9a169d 100644 ---- a/include/event2/util.h -+++ b/include/event2/util.h -@@ -841,7 +841,7 @@ int evutil_secure_rng_init(void); - */ - EVENT2_EXPORT_SYMBOL - int evutil_secure_rng_set_urandom_device_file(char *fname); -- -+#ifdef HAVE_ARC4RANDOM_ADDRANDOM - /** Seed the random number generator with extra random bytes. - - You should almost never need to call this function; it should be -@@ -858,7 +858,7 @@ int evutil_secure_rng_set_urandom_device_file(char *fname); - */ - EVENT2_EXPORT_SYMBOL - void evutil_secure_rng_add_bytes(const char *dat, size_t datlen); -- -+#endif - #ifdef __cplusplus - } - #endif --- -2.14.3 diff --git a/doc/build-osx.md b/doc/build-osx.md index e435798e95..bf655538c7 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -110,7 +110,7 @@ tail -f $HOME/Library/Application\ Support/Bitcoin/debug.log ``` ## Notes -* Tested on OS X 10.10 Yosemite through macOS 10.14 Mojave on 64-bit Intel +* Tested on OS X 10.12 Sierra through macOS 10.15 Catalina on 64-bit Intel processors only. * Building with downloaded Qt binaries is not officially supported. See the notes in [#7714](https://github.com/bitcoin/bitcoin/issues/7714). diff --git a/doc/dependencies.md b/doc/dependencies.md index bfb663866d..4b57ef2e91 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -13,7 +13,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | | | GCC | | [4.8+](https://gcc.gnu.org/) (C++11 support) | | | | | HarfBuzz-NG | | | | | | -| libevent | [2.1.8-stable](https://github.com/libevent/libevent/releases) | 2.0.22 | No | | | +| libevent | [2.1.11-stable](https://github.com/libevent/libevent/releases) | 2.0.22 | No | | | | libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | librsvg | | | | | | | MiniUPnPc | [2.0.20180203](http://miniupnp.free.fr/files) | | No | | | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 5f9c18f2f5..49f60c54ec 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -12,12 +12,13 @@ Developer Notes - [Development tips and tricks](#development-tips-and-tricks) - [Compiling for debugging](#compiling-for-debugging) - [Compiling for gprof profiling](#compiling-for-gprof-profiling) - - [debug.log](#debuglog) + - [`debug.log`](#debuglog) - [Testnet and Regtest modes](#testnet-and-regtest-modes) - [DEBUG_LOCKORDER](#debug_lockorder) - [Valgrind suppressions file](#valgrind-suppressions-file) - [Compiling for test coverage](#compiling-for-test-coverage) - [Performance profiling with perf](#performance-profiling-with-perf) + - [Sanitizers](#sanitizers) - [Locking/mutex usage notes](#lockingmutex-usage-notes) - [Threads](#threads) - [Ignoring IDE/editor files](#ignoring-ideeditor-files) @@ -63,7 +64,7 @@ tool to clean up patches automatically before submission. - Braces on the same line for everything else. - 4 space indentation (no tabs) for every block except namespaces. - No indentation for `public`/`protected`/`private` or for `namespace`. - - No extra spaces inside parenthesis; don't do ( this ). + - No extra spaces inside parenthesis; don't do `( this )`. - No space after function names; one space after `if`, `for` and `while`. - If an `if` only has a single-statement `then`-clause, it can appear on the same line as the `if`, without braces. In every other case, @@ -77,7 +78,7 @@ code. separate words (snake_case). - Class member variables have a `m_` prefix. - Global variables have a `g_` prefix. - - Constant names are all uppercase, and use `_` to separate words. + - Compile-time constant names are all uppercase, and use `_` to separate words. - Class names, function names, and method names are UpperCamelCase (PascalCase). Do not prefix class names with `C`. - Test suite naming convention: The Boost test suite in file @@ -208,15 +209,15 @@ produce better debugging builds. Run configure with the `--enable-gprof` option, then make. -### debug.log +### `debug.log` -If the code is behaving strangely, take a look in the debug.log file in the data directory; +If the code is behaving strangely, take a look in the `debug.log` file in the data directory; error and debugging messages are written there. The `-debug=...` command-line option controls debugging; running with just `-debug` or `-debug=1` will turn -on all categories (and give you a very large debug.log file). +on all categories (and give you a very large `debug.log` file). -The Qt code routes `qDebug()` output to debug.log under category "qt": run with `-debug=qt` +The Qt code routes `qDebug()` output to `debug.log` under category "qt": run with `-debug=qt` to see it. ### Testnet and Regtest modes @@ -234,7 +235,7 @@ Bitcoin Core is a multi-threaded application, and deadlocks or other multi-threading bugs can be very difficult to track down. The `--enable-debug` configure option adds `-DDEBUG_LOCKORDER` to the compiler flags. This inserts run-time checks to keep track of which locks are held and adds warnings to the -debug.log file if inconsistencies are detected. +`debug.log` file if inconsistencies are detected. ### Valgrind suppressions file @@ -276,8 +277,7 @@ the functional test framework. Perf can observe a running process and sample (at some frequency) where its execution is. Perf installation is contingent on which kernel version you're running; see -[this StackExchange -thread](https://askubuntu.com/questions/50145/how-to-install-perf-monitoring-tool) +[this thread](https://askubuntu.com/questions/50145/how-to-install-perf-monitoring-tool) for specific instructions. Certain kernel parameters may need to be set for perf to be able to inspect the @@ -312,7 +312,7 @@ or using a graphical tool like [Hotspot](https://github.com/KDAB/hotspot). See the functional test documentation for how to invoke perf within tests. -**Sanitizers** +### Sanitizers Bitcoin Core can be compiled with various "sanitizers" enabled, which add instrumentation for issues regarding things like memory safety, thread race @@ -373,7 +373,7 @@ Deadlocks due to inconsistent lock ordering (thread 1 locks `cs_main` and then `cs_wallet`, while thread 2 locks them in the opposite order: result, deadlock as each waits for the other to release its lock) are a problem. Compile with `-DDEBUG_LOCKORDER` (or use `--enable-debug`) to get lock order inconsistencies -reported in the debug.log file. +reported in the `debug.log` file. Re-architecting the core code so there are better-defined interfaces between the various components is a goal, with any necessary locking @@ -387,8 +387,6 @@ Threads - ThreadImport : Loads blocks from `blk*.dat` files or `-loadblock=<file>`. -- StartNode : Starts other threads. - - ThreadDNSAddressSeed : Loads addresses of peers from the DNS. - ThreadMapPort : Universal plug-and-play startup/shutdown. @@ -401,7 +399,7 @@ Threads - ThreadMessageHandler : Higher-level message handling (sending and receiving). -- DumpAddresses : Dumps IP addresses of nodes to peers.dat. +- DumpAddresses : Dumps IP addresses of nodes to `peers.dat`. - ThreadRPCServer : Remote procedure call handler, listens on port 8332 for connections and services them. @@ -467,11 +465,6 @@ Wallet - Make sure that no crashes happen with run-time option `-disablewallet`. - - *Rationale*: In RPC code that conditionally uses the wallet (such as - `validateaddress`), it is easy to forget that global pointer `pwalletMain` - can be nullptr. See `test/functional/disablewallet.py` for functional tests - exercising the API with `-disablewallet`. - - Include `db_cxx.h` (BerkeleyDB header) only when `ENABLE_WALLET` is set. - *Rationale*: Otherwise compilation of the disable-wallet build will fail in environments without BerkeleyDB. @@ -546,11 +539,10 @@ class A } ``` -- By default, declare single-argument constructors `explicit`. +- By default, declare constructors `explicit`. - - *Rationale*: This is a precaution to avoid unintended conversions that might - arise when single-argument constructors are used as implicit conversion - functions. + - *Rationale*: This is a precaution to avoid unintended + [conversions](https://en.cppreference.com/w/cpp/language/converting_constructor). - Use explicitly signed or unsigned `char`s, or even better `uint8_t` and `int8_t`. Do not use bare `char` unless it is to pass to a third-party API. diff --git a/doc/release-notes.md b/doc/release-notes.md index 33a127e0d0..88740ea1da 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -46,16 +46,15 @@ Compatibility ============== Bitcoin Core is supported and extensively tested on operating systems using -the Linux kernel, macOS 10.10+, and Windows 7 and newer. It is not recommended +the Linux kernel, macOS 10.12+, and Windows 7 and newer. It is not recommended to use Bitcoin Core on unsupported systems. Bitcoin Core should also work on most other Unix-like systems but is not as frequently tested on them. -From Bitcoin Core 0.17.0 onwards, macOS versions earlier than 10.10 are no -longer supported, as Bitcoin Core is now built using Qt 5.9.x which requires -macOS 10.10+. Additionally, Bitcoin Core does not yet change appearance when -macOS "dark mode" is activated. +From Bitcoin Core 0.20.0 onwards, macOS versions earlier than 10.12 are no +longer supported. Additionally, Bitcoin Core does not yet change appearance +when macOS "dark mode" is activated. In addition to previously supported CPU platforms, this release's pre-compiled distribution provides binaries for the RISC-V platform. diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index ffa001bb7f..275078c88d 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -3,7 +3,7 @@ <plist version="0.9"> <dict> <key>LSMinimumSystemVersion</key> - <string>10.10.0</string> + <string>10.12.0</string> <key>LSArchitecturePriority</key> <array> diff --git a/src/Makefile.am b/src/Makefile.am index ffb97f26d7..27c87688b4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -717,6 +717,8 @@ if EMBEDDED_LEVELDB include Makefile.leveldb.include endif +include Makefile.test_util.include + if ENABLE_TESTS include Makefile.test.include endif diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index acd712c8a2..9e70db116b 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -40,10 +40,6 @@ bench_bench_bitcoin_SOURCES = \ bench/lockedpool.cpp \ bench/poly1305.cpp \ bench/prevector.cpp \ - test/util/transaction_utils.h \ - test/util/transaction_utils.cpp \ - test/util/setup_common.h \ - test/util/setup_common.cpp \ test/util.h \ test/util.cpp @@ -59,6 +55,7 @@ bench_bench_bitcoin_LDADD = \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ + $(LIBTEST_UTIL) \ $(LIBLEVELDB) \ $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 93e60ff832..cf09eee2cb 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -136,6 +136,7 @@ BITCOIN_QT_H = \ qt/rpcconsole.h \ qt/sendcoinsdialog.h \ qt/sendcoinsentry.h \ + qt/sendcoinsrecipient.h \ qt/signverifymessagedialog.h \ qt/splashscreen.h \ qt/trafficgraphwidget.h \ diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 37e1da2a78..8c47fabad9 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -26,12 +26,6 @@ TEST_QT_H = \ qt/test/util.h \ qt/test/wallettests.h -TEST_BITCOIN_CPP = \ - test/util/setup_common.cpp - -TEST_BITCOIN_H = \ - test/util/setup_common.h - qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_TEST_INCLUDES) @@ -42,9 +36,7 @@ qt_test_test_bitcoin_qt_SOURCES = \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ qt/test/util.cpp \ - $(TEST_QT_H) \ - $(TEST_BITCOIN_CPP) \ - $(TEST_BITCOIN_H) + $(TEST_QT_H) if ENABLE_WALLET qt_test_test_bitcoin_qt_SOURCES += \ qt/test/addressbooktests.cpp \ @@ -54,7 +46,7 @@ endif # ENABLE_WALLET nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) -qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER) +qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER) $(LIBTEST_UTIL) if ENABLE_WALLET qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_UTIL) $(LIBBITCOIN_WALLET) endif diff --git a/src/Makefile.test.include b/src/Makefile.test.include index a2c76df91c..b4ca01b1fe 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -58,31 +58,19 @@ RAW_TEST_FILES = GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h) BITCOIN_TEST_SUITE = \ - test/util/blockfilter.cpp \ - test/util/blockfilter.h \ - test/util/logging.cpp \ - test/util/logging.h \ - test/util/transaction_utils.cpp \ - test/util/transaction_utils.h \ test/main.cpp \ - test/util/setup_common.h \ - test/util/setup_common.cpp \ - test/util/str.h \ - test/util/str.cpp + $(TEST_UTIL_H) FUZZ_SUITE = \ test/fuzz/fuzz.cpp \ test/fuzz/fuzz.h \ - test/fuzz/FuzzedDataProvider.h \ - test/util/setup_common.cpp \ - test/util/setup_common.h \ - test/util/str.cpp \ - test/util/str.h + test/fuzz/FuzzedDataProvider.h FUZZ_SUITE_LD_COMMON = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ + $(LIBTEST_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBUNIVALUE) \ @@ -198,7 +186,7 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EVENT_CFLAGS) -test_test_bitcoin_LDADD = +test_test_bitcoin_LDADD = $(LIBTEST_UTIL) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include new file mode 100644 index 0000000000..cf55d141b0 --- /dev/null +++ b/src/Makefile.test_util.include @@ -0,0 +1,31 @@ +# Copyright (c) 2013-2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +LIBTEST_UTIL=libtest_util.a + +EXTRA_LIBRARIES += \ + $(LIBTEST_UTIL) + +TEST_UTIL_H = \ + test/util/blockfilter.h \ + test/util/logging.h \ + test/util/setup_common.h \ + test/util/str.h \ + test/util/transaction_utils.h + +libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +libtest_util_a_SOURCES = \ + test/util/blockfilter.cpp \ + test/util/logging.cpp \ + test/util/setup_common.cpp \ + test/util/str.cpp \ + test/util/transaction_utils.cpp \ + $(TEST_UTIL_H) + +LIBTEST_UTIL += $(LIBBITCOIN_SERVER) +LIBTEST_UTIL += $(LIBBITCOIN_COMMON) +LIBTEST_UTIL += $(LIBBITCOIN_UTIL) +LIBTEST_UTIL += $(LIBBITCOIN_CRYPTO_BASE) + diff --git a/src/init.cpp b/src/init.cpp index 421768441e..e7dda59590 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -281,9 +281,9 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - GetMainSignals().UnregisterWithMempoolSignals(mempool); globalVerifyHandle.reset(); ECC_Stop(); + if (node.mempool) node.mempool = nullptr; LogPrintf("%s: done\n", __func__); } @@ -1262,7 +1262,6 @@ bool AppInitMain(NodeContext& node) }, 60000); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); - GetMainSignals().RegisterWithMempoolSignals(mempool); // Create client interfaces for wallets that are supposed to be loaded // according to -wallet and -disablewallet options. This only constructs @@ -1635,6 +1634,11 @@ bool AppInitMain(NodeContext& node) return false; } + // Now that the chain state is loaded, make mempool generally available in the node context. For example the + // connection manager, wallet, or RPC threads, which are all started after this, may use it from the node context. + assert(!node.mempool); + node.mempool = &::mempool; + fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION); // Allowed to fail as this file IS missing on first startup. diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 701a748e55..94c6e8c7b7 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -18,8 +18,9 @@ #include <wallet/feebumper.h> #include <wallet/fees.h> #include <wallet/ismine.h> -#include <wallet/rpcwallet.h> #include <wallet/load.h> +#include <wallet/psbtwallet.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <memory> @@ -116,8 +117,22 @@ public: std::string error; return m_wallet->GetNewDestination(type, label, dest, error); } - bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetPubKey(address, pub_key); } - bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetKey(address, key); } + bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override + { + const SigningProvider* provider = m_wallet->GetSigningProvider(script); + if (provider) { + return provider->GetPubKey(address, pub_key); + } + return false; + } + bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) override + { + const SigningProvider* provider = m_wallet->GetSigningProvider(script); + if (provider) { + return provider->GetKey(address, key); + } + return false; + } bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } bool haveWatchOnly() override { @@ -343,6 +358,14 @@ public: } return {}; } + TransactionError fillPSBT(PartiallySignedTransaction& psbtx, + bool& complete, + int sighash_type = 1 /* SIGHASH_ALL */, + bool sign = true, + bool bip32derivs = false) override + { + return FillPSBT(m_wallet.get(), psbtx, complete, sighash_type, sign, bip32derivs); + } WalletBalances getBalances() override { const auto bal = m_wallet->GetBalance(); diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index a96b93b4c3..8d2b8a2eca 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -14,6 +14,7 @@ #include <functional> #include <map> #include <memory> +#include <psbt.h> #include <stdint.h> #include <string> #include <tuple> @@ -81,10 +82,10 @@ public: virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) = 0; //! Get public key. - virtual bool getPubKey(const CKeyID& address, CPubKey& pub_key) = 0; + virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0; //! Get private key. - virtual bool getPrivKey(const CKeyID& address, CKey& key) = 0; + virtual bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) = 0; //! Return whether wallet has private key. virtual bool isSpendable(const CTxDestination& dest) = 0; @@ -194,6 +195,13 @@ public: bool& in_mempool, int& num_blocks) = 0; + //! Fill PSBT. + virtual TransactionError fillPSBT(PartiallySignedTransaction& psbtx, + bool& complete, + int sighash_type = 1 /* SIGHASH_ALL */, + bool sign = true, + bool bip32derivs = false) = 0; + //! Get balances. virtual WalletBalances getBalances() = 0; diff --git a/src/key.cpp b/src/key.cpp index 3ba21753a2..10b6668aae 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -83,13 +83,13 @@ static int ec_privkey_import_der(const secp256k1_context* ctx, unsigned char *ou * <http://www.secg.org/sec1-v2.pdf>. The optional parameters and publicKey fields are * included. * - * privkey must point to an output buffer of length at least CKey::PRIVATE_KEY_SIZE bytes. + * privkey must point to an output buffer of length at least CKey::SIZE bytes. * privkeylen must initially be set to the size of the privkey buffer. Upon return it * will be set to the number of bytes used in the buffer. * key32 must point to a 32-byte raw private key. */ static int ec_privkey_export_der(const secp256k1_context *ctx, unsigned char *privkey, size_t *privkeylen, const unsigned char *key32, bool compressed) { - assert(*privkeylen >= CKey::PRIVATE_KEY_SIZE); + assert(*privkeylen >= CKey::SIZE); secp256k1_pubkey pubkey; size_t pubkeylen = 0; if (!secp256k1_ec_pubkey_create(ctx, &pubkey, key32)) { @@ -115,11 +115,11 @@ static int ec_privkey_export_der(const secp256k1_context *ctx, unsigned char *pr memcpy(ptr, begin, sizeof(begin)); ptr += sizeof(begin); memcpy(ptr, key32, 32); ptr += 32; memcpy(ptr, middle, sizeof(middle)); ptr += sizeof(middle); - pubkeylen = CPubKey::COMPRESSED_PUBLIC_KEY_SIZE; + pubkeylen = CPubKey::COMPRESSED_SIZE; secp256k1_ec_pubkey_serialize(ctx, ptr, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED); ptr += pubkeylen; *privkeylen = ptr - privkey; - assert(*privkeylen == CKey::COMPRESSED_PRIVATE_KEY_SIZE); + assert(*privkeylen == CKey::COMPRESSED_SIZE); } else { static const unsigned char begin[] = { 0x30,0x82,0x01,0x13,0x02,0x01,0x01,0x04,0x20 @@ -141,11 +141,11 @@ static int ec_privkey_export_der(const secp256k1_context *ctx, unsigned char *pr memcpy(ptr, begin, sizeof(begin)); ptr += sizeof(begin); memcpy(ptr, key32, 32); ptr += 32; memcpy(ptr, middle, sizeof(middle)); ptr += sizeof(middle); - pubkeylen = CPubKey::PUBLIC_KEY_SIZE; + pubkeylen = CPubKey::SIZE; secp256k1_ec_pubkey_serialize(ctx, ptr, &pubkeylen, &pubkey, SECP256K1_EC_UNCOMPRESSED); ptr += pubkeylen; *privkeylen = ptr - privkey; - assert(*privkeylen == CKey::PRIVATE_KEY_SIZE); + assert(*privkeylen == CKey::SIZE); } return 1; } @@ -173,8 +173,8 @@ CPrivKey CKey::GetPrivKey() const { CPrivKey privkey; int ret; size_t privkeylen; - privkey.resize(PRIVATE_KEY_SIZE); - privkeylen = PRIVATE_KEY_SIZE; + privkey.resize(SIZE); + privkeylen = SIZE; ret = ec_privkey_export_der(secp256k1_context_sign, privkey.data(), &privkeylen, begin(), fCompressed); assert(ret); privkey.resize(privkeylen); @@ -184,7 +184,7 @@ CPrivKey CKey::GetPrivKey() const { CPubKey CKey::GetPubKey() const { assert(fValid); secp256k1_pubkey pubkey; - size_t clen = CPubKey::PUBLIC_KEY_SIZE; + size_t clen = CPubKey::SIZE; CPubKey result; int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin()); assert(ret); @@ -276,7 +276,7 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const std::vector<unsigned char, secure_allocator<unsigned char>> vout(64); if ((nChild >> 31) == 0) { CPubKey pubkey = GetPubKey(); - assert(pubkey.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); + assert(pubkey.size() == CPubKey::COMPRESSED_SIZE); BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, vout.data()); } else { assert(size() == 32); @@ -19,7 +19,7 @@ /** * secure_allocator is defined in allocators.h * CPrivKey is a serialized private key, with all parameters included - * (PRIVATE_KEY_SIZE bytes) + * (SIZE bytes) */ typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey; @@ -30,15 +30,15 @@ public: /** * secp256k1: */ - static const unsigned int PRIVATE_KEY_SIZE = 279; - static const unsigned int COMPRESSED_PRIVATE_KEY_SIZE = 214; + static const unsigned int SIZE = 279; + static const unsigned int COMPRESSED_SIZE = 214; /** * see www.keylength.com * script supports up to 75 for single byte push */ static_assert( - PRIVATE_KEY_SIZE >= COMPRESSED_PRIVATE_KEY_SIZE, - "COMPRESSED_PRIVATE_KEY_SIZE is larger than PRIVATE_KEY_SIZE"); + SIZE >= COMPRESSED_SIZE, + "COMPRESSED_SIZE is larger than SIZE"); private: //! Whether this private key is valid. We check for correctness when modifying the key diff --git a/src/miner.cpp b/src/miner.cpp index 1c9174ee07..2f6feb9e02 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -435,7 +435,7 @@ void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned ++nExtraNonce; unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2 CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS; + txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)); assert(txCoinbase.vin[0].scriptSig.size() <= 100); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); diff --git a/src/node/context.h b/src/node/context.h index 2b124af4db..dab5b5d048 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -10,6 +10,7 @@ class BanMan; class CConnman; +class CTxMemPool; class PeerLogicValidation; namespace interfaces { class Chain; @@ -22,13 +23,13 @@ class ChainClient; //! This is used by init, rpc, and test code to pass object references around //! without needing to declare the same variables and parameters repeatedly, or //! to use globals. More variables could be added to this struct (particularly -//! references to validation and mempool objects) to eliminate use of globals +//! references to validation objects) to eliminate use of globals //! and make code more modular and testable. The struct isn't intended to have //! any member functions. It should just be a collection of references that can //! be used without pulling in unwanted dependencies or functionality. -struct NodeContext -{ +struct NodeContext { std::unique_ptr<CConnman> connman; + CTxMemPool* mempool{nullptr}; // Currently a raw pointer because the memory is not managed by this struct std::unique_ptr<PeerLogicValidation> peer_logic; std::unique_ptr<BanMan> banman; std::unique_ptr<interfaces::Chain> chain; diff --git a/src/psbt.h b/src/psbt.h index 6a5c468058..bcff66f7a1 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -171,7 +171,7 @@ struct PSBTInput case PSBT_IN_PARTIAL_SIG: { // Make sure that the key is the size of pubkey + 1 - if (key.size() != CPubKey::PUBLIC_KEY_SIZE + 1 && key.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1) { + if (key.size() != CPubKey::SIZE + 1 && key.size() != CPubKey::COMPRESSED_SIZE + 1) { throw std::ios_base::failure("Size of key was not the expected size for the type partial signature pubkey"); } // Read in the pubkey from key diff --git a/src/pubkey.cpp b/src/pubkey.cpp index d38df716bd..10953adc35 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -196,8 +196,8 @@ bool CPubKey::RecoverCompact(const uint256 &hash, const std::vector<unsigned cha if (!secp256k1_ecdsa_recover(secp256k1_context_verify, &pubkey, &sig, hash.begin())) { return false; } - unsigned char pub[PUBLIC_KEY_SIZE]; - size_t publen = PUBLIC_KEY_SIZE; + unsigned char pub[SIZE]; + size_t publen = SIZE; secp256k1_ec_pubkey_serialize(secp256k1_context_verify, pub, &publen, &pubkey, fComp ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED); Set(pub, pub + publen); return true; @@ -217,8 +217,8 @@ bool CPubKey::Decompress() { if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size())) { return false; } - unsigned char pub[PUBLIC_KEY_SIZE]; - size_t publen = PUBLIC_KEY_SIZE; + unsigned char pub[SIZE]; + size_t publen = SIZE; secp256k1_ec_pubkey_serialize(secp256k1_context_verify, pub, &publen, &pubkey, SECP256K1_EC_UNCOMPRESSED); Set(pub, pub + publen); return true; @@ -227,7 +227,7 @@ bool CPubKey::Decompress() { bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const { assert(IsValid()); assert((nChild >> 31) == 0); - assert(size() == COMPRESSED_PUBLIC_KEY_SIZE); + assert(size() == COMPRESSED_SIZE); unsigned char out[64]; BIP32Hash(cc, nChild, *begin(), begin()+1, out); memcpy(ccChild.begin(), out+32, 32); @@ -238,8 +238,8 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi if (!secp256k1_ec_pubkey_tweak_add(secp256k1_context_verify, &pubkey, out)) { return false; } - unsigned char pub[COMPRESSED_PUBLIC_KEY_SIZE]; - size_t publen = COMPRESSED_PUBLIC_KEY_SIZE; + unsigned char pub[COMPRESSED_SIZE]; + size_t publen = COMPRESSED_SIZE; secp256k1_ec_pubkey_serialize(secp256k1_context_verify, pub, &publen, &pubkey, SECP256K1_EC_COMPRESSED); pubkeyChild.Set(pub, pub + publen); return true; @@ -251,8 +251,8 @@ void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const { code[5] = (nChild >> 24) & 0xFF; code[6] = (nChild >> 16) & 0xFF; code[7] = (nChild >> 8) & 0xFF; code[8] = (nChild >> 0) & 0xFF; memcpy(code+9, chaincode.begin(), 32); - assert(pubkey.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); - memcpy(code+41, pubkey.begin(), CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); + assert(pubkey.size() == CPubKey::COMPRESSED_SIZE); + memcpy(code+41, pubkey.begin(), CPubKey::COMPRESSED_SIZE); } void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) { diff --git a/src/pubkey.h b/src/pubkey.h index fd815a871b..76f743da66 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -33,17 +33,17 @@ public: /** * secp256k1: */ - static constexpr unsigned int PUBLIC_KEY_SIZE = 65; - static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; - static constexpr unsigned int SIGNATURE_SIZE = 72; - static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65; + static constexpr unsigned int SIZE = 65; + static constexpr unsigned int COMPRESSED_SIZE = 33; + static constexpr unsigned int SIGNATURE_SIZE = 72; + static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65; /** * see www.keylength.com * script supports up to 75 for single byte push */ static_assert( - PUBLIC_KEY_SIZE >= COMPRESSED_PUBLIC_KEY_SIZE, - "COMPRESSED_PUBLIC_KEY_SIZE is larger than PUBLIC_KEY_SIZE"); + SIZE >= COMPRESSED_SIZE, + "COMPRESSED_SIZE is larger than SIZE"); private: @@ -51,15 +51,15 @@ private: * Just store the serialized data. * Its length can very cheaply be computed from the first byte. */ - unsigned char vch[PUBLIC_KEY_SIZE]; + unsigned char vch[SIZE]; //! Compute the length of a pubkey with a given first byte. unsigned int static GetLen(unsigned char chHeader) { if (chHeader == 2 || chHeader == 3) - return COMPRESSED_PUBLIC_KEY_SIZE; + return COMPRESSED_SIZE; if (chHeader == 4 || chHeader == 6 || chHeader == 7) - return PUBLIC_KEY_SIZE; + return SIZE; return 0; } @@ -140,7 +140,7 @@ public: void Unserialize(Stream& s) { unsigned int len = ::ReadCompactSize(s); - if (len <= PUBLIC_KEY_SIZE) { + if (len <= SIZE) { s.read((char*)vch, len); } else { // invalid pubkey, skip available data @@ -179,7 +179,7 @@ public: //! Check whether this is a compressed public key. bool IsCompressed() const { - return size() == COMPRESSED_PUBLIC_KEY_SIZE; + return size() == COMPRESSED_SIZE; } /** diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f928f1ca2a..3302dde4ed 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -468,7 +468,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { CPubKey pubkey; PKHash *pkhash = boost::get<PKHash>(&address); - if (pkhash && model->wallet().getPubKey(CKeyID(*pkhash), pubkey)) + if (pkhash && model->wallet().getPubKey(out.txout.scriptPubKey, CKeyID(*pkhash), pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); } diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 0214356eaa..64f52fcf45 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -63,7 +63,7 @@ <item row="4" column="2"> <widget class="QLineEdit" name="reqLabel"> <property name="toolTip"> - <string>An optional label to associate with the new receiving address.</string> + <string>An optional label to associate with the new receiving address (used by you to identify an invoice). It is also attached to the payment request.</string> </property> </widget> </item> @@ -93,7 +93,7 @@ <item row="6" column="2"> <widget class="QLineEdit" name="reqMessage"> <property name="toolTip"> - <string>An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</string> + <string>An optional message that is attached to the payment request and may be displayed to the sender.</string> </property> </widget> </item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index c4e0321f28..31e62ab63d 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -7,7 +7,7 @@ #include <qt/bitcoinaddressvalidator.h> #include <qt/bitcoinunits.h> #include <qt/qvalidatedlineedit.h> -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <base58.h> #include <chainparams.h> diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 199804f84d..6a0143ac7e 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -6,7 +6,7 @@ #include <qt/forms/ui_openuridialog.h> #include <qt/guiutil.h> -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <QUrl> diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 8b2533508d..6d4ce4a7e4 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -36,13 +36,17 @@ #include <config/bitcoin-config.h> #endif -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <QObject> #include <QString> class OptionsModel; +namespace interfaces { +class Node; +} // namespace interfaces + QT_BEGIN_NAMESPACE class QApplication; class QByteArray; diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index e492502002..2674c9b953 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -8,6 +8,7 @@ #include <qt/bitcoinunits.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> +#include <qt/walletmodel.h> #include <QClipboard> #include <QPixmap> diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index a6e1a2af16..1bb43ce6d1 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -5,10 +5,12 @@ #ifndef BITCOIN_QT_RECEIVEREQUESTDIALOG_H #define BITCOIN_QT_RECEIVEREQUESTDIALOG_H -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <QDialog> +class WalletModel; + namespace Ui { class ReceiveRequestDialog; } diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 1611ec823c..6d933f46d6 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -7,6 +7,7 @@ #include <qt/bitcoinunits.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> +#include <qt/walletmodel.h> #include <clientversion.h> #include <streams.h> diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index 130b709d46..30578b7a03 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -5,12 +5,14 @@ #ifndef BITCOIN_QT_RECENTREQUESTSTABLEMODEL_H #define BITCOIN_QT_RECENTREQUESTSTABLEMODEL_H -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <QAbstractTableModel> #include <QStringList> #include <QDateTime> +class WalletModel; + class RecentRequestEntry { public: diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 8edcca684d..9c4c9dbb17 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -21,11 +21,13 @@ #include <chainparams.h> #include <interfaces/node.h> #include <key_io.h> -#include <wallet/coincontrol.h> -#include <ui_interface.h> -#include <txmempool.h> #include <policy/fees.h> +#include <txmempool.h> +#include <ui_interface.h> +#include <wallet/coincontrol.h> #include <wallet/fees.h> +#include <wallet/psbtwallet.h> +#include <wallet/wallet.h> #include <QFontMetrics> #include <QScrollBar> @@ -186,6 +188,11 @@ void SendCoinsDialog::setModel(WalletModel *_model) // set default rbf checkbox state ui->optInRBF->setCheckState(Qt::Checked); + if (model->privateKeysDisabled()) { + ui->sendButton->setText(tr("Cr&eate Unsigned")); + ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); + } + // set the smartfee-sliders default value (wallets default conf.target or last stored value) QSettings settings; if (settings.value("nSmartFeeSliderPosition").toInt() != 0) { @@ -305,9 +312,19 @@ void SendCoinsDialog::on_sendButton_clicked() formatted.append(recipientElement); } - QString questionString = tr("Are you sure you want to send?"); + QString questionString; + if (model->privateKeysDisabled()) { + questionString.append(tr("Do you want to draft this transaction?")); + } else { + questionString.append(tr("Are you sure you want to send?")); + } + questionString.append("<br /><span style='font-size:10pt;'>"); - questionString.append(tr("Please, review your transaction.")); + if (model->privateKeysDisabled()) { + questionString.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); + } else { + questionString.append(tr("Please, review your transaction.")); + } questionString.append("</span>%1"); if(txFee > 0) @@ -358,8 +375,9 @@ void SendCoinsDialog::on_sendButton_clicked() } else { questionString = questionString.arg("<br /><br />" + formatted.at(0)); } - - SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, this); + const QString confirmation = model->privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins"); + const QString confirmButtonText = model->privateKeysDisabled() ? tr("Copy PSBT to clipboard") : tr("Send"); + SendConfirmationDialog confirmationDialog(confirmation, questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result()); @@ -369,17 +387,35 @@ void SendCoinsDialog::on_sendButton_clicked() return; } - // now send the prepared transaction - WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); - // process sendStatus and on error generate message shown to user - processSendCoinsReturn(sendStatus); + bool send_failure = false; + if (model->privateKeysDisabled()) { + CMutableTransaction mtx = CMutableTransaction{*(currentTransaction.getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + const TransactionError err = model->wallet().fillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + assert(!complete); + assert(err == TransactionError::OK); + // 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); + } else { + // now send the prepared transaction + WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); + // process sendStatus and on error generate message shown to user + processSendCoinsReturn(sendStatus); - if (sendStatus.status == WalletModel::OK) - { + if (sendStatus.status == WalletModel::OK) { + Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); + } else { + send_failure = true; + } + } + if (!send_failure) { accept(); CoinControlDialog::coinControl()->UnSelectAll(); coinControlUpdateLabels(); - Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); } fNewRecipientAllowed = true; } @@ -611,6 +647,9 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) coin_control = *CoinControlDialog::coinControl(); } + // Include watch-only for wallets without private key + coin_control.fAllowWatchOnly = model->privateKeysDisabled(); + // Calculate available amount to send. CAmount amount = model->wallet().getAvailableBalance(coin_control); for (int i = 0; i < ui->entries->count(); ++i) { @@ -663,6 +702,8 @@ void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl) // Either custom fee will be used or if not selected, the confirmation target from dropdown box ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex()); ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked(); + // Include watch-only for wallets without private key + ctrl.fAllowWatchOnly = model->privateKeysDisabled(); } void SendCoinsDialog::updateSmartFeeLabel() @@ -870,8 +911,8 @@ void SendCoinsDialog::coinControlUpdateLabels() } } -SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, QWidget* parent) - : QMessageBox(parent), secDelay(_secDelay) +SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent) + : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText) { setIcon(QMessageBox::Question); setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines). @@ -908,11 +949,11 @@ void SendConfirmationDialog::updateYesButton() if(secDelay > 0) { yesButton->setEnabled(false); - yesButton->setText(tr("Send") + " (" + QString::number(secDelay) + ")"); + yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")"); } else { yesButton->setEnabled(true); - yesButton->setText(tr("Send")); + yesButton->setText(confirmButtonText); } } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index c6c1816877..ccd8494613 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -108,7 +108,7 @@ class SendConfirmationDialog : public QMessageBox Q_OBJECT public: - SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, QWidget* parent = nullptr); + SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "Send", QWidget* parent = nullptr); int exec(); private Q_SLOTS: @@ -119,6 +119,7 @@ private: QAbstractButton *yesButton; QTimer countDownTimer; int secDelay; + QString confirmButtonText; }; #endif // BITCOIN_QT_SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index be417655b4..ad37e09114 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -14,6 +14,7 @@ #include <qt/guiutil.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> +#include <qt/walletmodel.h> #include <QApplication> #include <QClipboard> diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 42e2217130..aa69d30f99 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -5,13 +5,17 @@ #ifndef BITCOIN_QT_SENDCOINSENTRY_H #define BITCOIN_QT_SENDCOINSENTRY_H -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <QStackedWidget> class WalletModel; class PlatformStyle; +namespace interfaces { +class Node; +} // namespace interfaces + namespace Ui { class SendCoinsEntry; } diff --git a/src/qt/sendcoinsrecipient.h b/src/qt/sendcoinsrecipient.h new file mode 100644 index 0000000000..12279fab64 --- /dev/null +++ b/src/qt/sendcoinsrecipient.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_SENDCOINSRECIPIENT_H +#define BITCOIN_QT_SENDCOINSRECIPIENT_H + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <amount.h> +#include <serialize.h> + +#include <string> + +#include <QString> + +class SendCoinsRecipient +{ +public: + explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { } + explicit SendCoinsRecipient(const QString &addr, const QString &_label, const CAmount& _amount, const QString &_message): + address(addr), label(_label), amount(_amount), message(_message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} + + // If from an unauthenticated payment request, this is used for storing + // the addresses, e.g. address-A<br />address-B<br />address-C. + // Info: As we don't need to process addresses in here when using + // payment requests, we can abuse it for displaying an address list. + // Todo: This is a hack, should be replaced with a cleaner solution! + QString address; + QString label; + CAmount amount; + // If from a payment request, this is used for storing the memo + QString message; + // Keep the payment request around as a serialized string to ensure + // load/store is lossless. + std::string sPaymentRequest; + // Empty if no authentication or invalid signature/cert/etc. + QString authenticatedMerchant; + + bool fSubtractFeeFromAmount; // memory only + + static const int CURRENT_VERSION = 1; + int nVersion; + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + std::string sAddress = address.toStdString(); + std::string sLabel = label.toStdString(); + std::string sMessage = message.toStdString(); + std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); + + READWRITE(this->nVersion); + READWRITE(sAddress); + READWRITE(sLabel); + READWRITE(amount); + READWRITE(sMessage); + READWRITE(sPaymentRequest); + READWRITE(sAuthenticatedMerchant); + + if (ser_action.ForRead()) + { + address = QString::fromStdString(sAddress); + label = QString::fromStdString(sLabel); + message = QString::fromStdString(sMessage); + authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); + } + } +}; + +#endif // BITCOIN_QT_SENDCOINSRECIPIENT_H diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 1d0e1323bc..9d250bcb83 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -136,7 +136,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() } CKey key; - if (!model->wallet().getPrivKey(CKeyID(*pkhash), key)) + if (!model->wallet().getPrivKey(GetScriptForDestination(destination), CKeyID(*pkhash), key)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("Private key for the entered address is not available.")); diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 971d9f4a7c..374fc9b59b 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -32,7 +32,6 @@ void RPCNestedTests::rpcNestedTests() // do some test setup // could be moved to a more generic place when we add more tests on QT level tableRPC.appendCommand("rpcNestedTest", &vRPCCommands[0]); - //mempool.setSanityCheck(1.0); TestingSetup test; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 318b0756c7..9a93798aef 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -15,11 +15,12 @@ #include <consensus/consensus.h> #include <interfaces/node.h> +#include <interfaces/wallet.h> #include <key_io.h> -#include <validation.h> +#include <policy/policy.h> #include <script/script.h> #include <util/system.h> -#include <policy/policy.h> +#include <validation.h> #include <wallet/ismine.h> #include <stdint.h> diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 4c253f8ddd..7413a1f09e 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -2,17 +2,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <qt/walletcontroller.h> + #include <qt/askpassphrasedialog.h> #include <qt/createwalletdialog.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> -#include <qt/walletcontroller.h> - -#include <wallet/wallet.h> +#include <qt/walletmodel.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <util/string.h> +#include <wallet/wallet.h> #include <algorithm> diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index e50dd5c7eb..956245775e 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_QT_WALLETCONTROLLER_H #define BITCOIN_QT_WALLETCONTROLLER_H -#include <qt/walletmodel.h> +#include <qt/sendcoinsrecipient.h> #include <support/allocators/secure.h> #include <sync.h> @@ -23,10 +23,12 @@ class OptionsModel; class PlatformStyle; +class WalletModel; namespace interfaces { class Handler; class Node; +class Wallet; } // namespace interfaces class AskPassphraseDialog; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 33801d3907..fb92e29f21 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -183,7 +183,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact std::string strFailReason; auto& newTx = transaction.getWtx(); - newTx = m_wallet->createTransaction(vecSend, coinControl, true /* sign */, nChangePosRet, nFeeRequired, strFailReason); + newTx = m_wallet->createTransaction(vecSend, coinControl, !privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, strFailReason); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) transaction.reassignAmounts(nChangePosRet); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index a873519a34..8087356f5e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -9,9 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <amount.h> #include <key.h> -#include <serialize.h> #include <script/standard.h> #include <qt/walletmodeltransaction.h> @@ -29,6 +27,7 @@ class AddressTableModel; class OptionsModel; class PlatformStyle; class RecentRequestsTableModel; +class SendCoinsRecipient; class TransactionTableModel; class WalletModelTransaction; @@ -47,61 +46,6 @@ QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE -class SendCoinsRecipient -{ -public: - explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { } - explicit SendCoinsRecipient(const QString &addr, const QString &_label, const CAmount& _amount, const QString &_message): - address(addr), label(_label), amount(_amount), message(_message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} - - // If from an unauthenticated payment request, this is used for storing - // the addresses, e.g. address-A<br />address-B<br />address-C. - // Info: As we don't need to process addresses in here when using - // payment requests, we can abuse it for displaying an address list. - // Todo: This is a hack, should be replaced with a cleaner solution! - QString address; - QString label; - CAmount amount; - // If from a payment request, this is used for storing the memo - QString message; - // Keep the payment request around as a serialized string to ensure - // load/store is lossless. - std::string sPaymentRequest; - // Empty if no authentication or invalid signature/cert/etc. - QString authenticatedMerchant; - - bool fSubtractFeeFromAmount; // memory only - - static const int CURRENT_VERSION = 1; - int nVersion; - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - std::string sAddress = address.toStdString(); - std::string sLabel = label.toStdString(); - std::string sMessage = message.toStdString(); - std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); - - READWRITE(this->nVersion); - READWRITE(sAddress); - READWRITE(sLabel); - READWRITE(amount); - READWRITE(sMessage); - READWRITE(sPaymentRequest); - READWRITE(sAuthenticatedMerchant); - - if (ser_action.ForRead()) - { - address = QString::fromStdString(sAddress); - label = QString::fromStdString(sLabel); - message = QString::fromStdString(sMessage); - authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); - } - } -}; - /** Interface to Bitcoin wallet from Qt view code. */ class WalletModel : public QObject { diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 242ba13897..9e5d285e8c 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -5,7 +5,8 @@ #ifndef BITCOIN_QT_WALLETMODELTRANSACTION_H #define BITCOIN_QT_WALLETMODELTRANSACTION_H -#include <qt/walletmodel.h> +#include <primitives/transaction.h> +#include <qt/sendcoinsrecipient.h> #include <amount.h> diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 2f4b4412f5..946152d9aa 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -15,6 +15,7 @@ #include <hash.h> #include <index/blockfilterindex.h> #include <node/coinstats.h> +#include <node/context.h> #include <node/utxo_snapshot.h> #include <policy/feerate.h> #include <policy/policy.h> @@ -53,6 +54,15 @@ static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock; +CTxMemPool& EnsureMemPool() +{ + CHECK_NONFATAL(g_rpc_node); + if (!g_rpc_node->mempool) { + throw JSONRPCError(RPC_CLIENT_MEMPOOL_DISABLED, "Mempool disabled or instance not found"); + } + return *g_rpc_node->mempool; +} + /* Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex* blockindex) diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 8a1264f824..ccb3e39722 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -52,4 +52,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], //! direct way to pass in state to RPC methods without globals. extern NodeContext* g_rpc_node; +CTxMemPool& EnsureMemPool(); + #endif diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index ab22155651..85f9f1e8b7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -375,9 +375,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " }\n" " ,...\n" " ],\n" - " \"coinbaseaux\" : { (json object) data that should be included in the coinbase's scriptSig content\n" - " \"flags\" : \"xx\" (string) key name is to be ignored, and value included in scriptSig\n" - " },\n" + " \"coinbaseaux\" : { ... }, (json object) data that should be included in the coinbase's scriptSig content\n" " \"coinbasevalue\" : n, (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)\n" " \"coinbasetxn\" : { ... }, (json object) information for coinbase transaction\n" " \"target\" : \"xxxx\", (string) The hash target\n" @@ -607,7 +605,6 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) } UniValue aux(UniValue::VOBJ); - aux.pushKV("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())); arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index ef6537e4ec..ca779497b9 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -63,6 +63,9 @@ enum RPCErrorCode RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //!< Invalid IP/Subnet RPC_CLIENT_P2P_DISABLED = -31, //!< No valid connection manager instance found + //! Chain errors + RPC_CLIENT_MEMPOOL_DISABLED = -33, //!< No mempool instance found + //! Wallet errors RPC_WALLET_ERROR = -4, //!< Unspecified problem with wallet (key not found etc.) RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Not enough funds in wallet or account diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 983f251d6b..6f24caee21 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -763,7 +763,9 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[2], &keystore, coins); - return SignTransaction(mtx, &keystore, coins, request.params[3]); + UniValue result(UniValue::VOBJ); + SignTransaction(mtx, &keystore, coins, request.params[3], result); + return result; } static UniValue sendrawtransaction(const JSONRPCRequest& request) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index fe98fff4bb..40334883c5 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -268,7 +268,7 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst } } -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType) +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result) { int nHashType = ParseSighashString(hashType); @@ -319,12 +319,12 @@ UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keysto } bool fComplete = vErrors.empty(); - UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); result.pushKV("complete", fComplete); if (!vErrors.empty()) { + if (result.exists("errors")) { + vErrors.push_backV(result["errors"].getValues()); + } result.pushKV("errors", vErrors); } - - return result; } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 1936998ff3..0b7712b83c 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -21,9 +21,9 @@ class SigningProvider; * @param keystore Temporary keystore containing signing keys * @param coins Map of unspent outputs * @param hashType The signature hash type - * @returns JSON object with details of signed transaction + * @param result JSON object where signed transaction results accumulate */ -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType); +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result); /** * Parse a prevtxs UniValue array and get the map of coins from it diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 13cdd6c61a..32b388b7fa 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -815,8 +815,8 @@ std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptCon } } if (ctx == ParseScriptContext::P2SH) { - if (script_size + 3 > 520) { - error = strprintf("P2SH script is too large, %d bytes is larger than 520 bytes", script_size + 3); + if (script_size + 3 > MAX_SCRIPT_ELEMENT_SIZE) { + error = strprintf("P2SH script is too large, %d bytes is larger than %d bytes", script_size + 3, MAX_SCRIPT_ELEMENT_SIZE); return nullptr; } } diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 20fae2eebf..ad833bc025 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -61,17 +61,17 @@ static inline void popstack(std::vector<valtype>& stack) } bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) { - if (vchPubKey.size() < CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) { + if (vchPubKey.size() < CPubKey::COMPRESSED_SIZE) { // Non-canonical public key: too short return false; } if (vchPubKey[0] == 0x04) { - if (vchPubKey.size() != CPubKey::PUBLIC_KEY_SIZE) { + if (vchPubKey.size() != CPubKey::SIZE) { // Non-canonical public key: invalid length for uncompressed key return false; } } else if (vchPubKey[0] == 0x02 || vchPubKey[0] == 0x03) { - if (vchPubKey.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) { + if (vchPubKey.size() != CPubKey::COMPRESSED_SIZE) { // Non-canonical public key: invalid length for compressed key return false; } @@ -83,7 +83,7 @@ bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) { } bool static IsCompressedPubKey(const valtype &vchPubKey) { - if (vchPubKey.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) { + if (vchPubKey.size() != CPubKey::COMPRESSED_SIZE) { // Non-canonical public key: invalid length for compressed key return false; } diff --git a/src/script/sign.h b/src/script/sign.h index 9d0a5b4d70..4c2403f83f 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -101,7 +101,7 @@ template<typename Stream> void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, KeyOriginInfo>& hd_keypaths) { // Make sure that the key is the size of pubkey + 1 - if (key.size() != CPubKey::PUBLIC_KEY_SIZE + 1 && key.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1) { + if (key.size() != CPubKey::SIZE + 1 && key.size() != CPubKey::COMPRESSED_SIZE + 1) { throw std::ios_base::failure("Size of key was not the expected size for the type BIP32 keypath"); } // Read in the pubkey from key diff --git a/src/script/standard.cpp b/src/script/standard.cpp index fc6898f444..144bdcff98 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -44,12 +44,12 @@ const char* GetTxnOutputType(txnouttype t) static bool MatchPayToPubkey(const CScript& script, valtype& pubkey) { - if (script.size() == CPubKey::PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { - pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::PUBLIC_KEY_SIZE + 1); + if (script.size() == CPubKey::SIZE + 2 && script[0] == CPubKey::SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::SIZE + 1); return CPubKey::ValidSize(pubkey); } - if (script.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { - pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1); + if (script.size() == CPubKey::COMPRESSED_SIZE + 2 && script[0] == CPubKey::COMPRESSED_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_SIZE + 1); return CPubKey::ValidSize(pubkey); } return false; diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 5c2050e4a2..85e3351e72 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -23,6 +23,10 @@ #endif #include <algorithm> +#ifdef ARENA_DEBUG +#include <iomanip> +#include <iostream> +#endif LockedPoolManager* LockedPoolManager::_instance = nullptr; std::once_flag LockedPoolManager::init_flag; @@ -137,7 +141,7 @@ Arena::Stats Arena::stats() const } #ifdef ARENA_DEBUG -static void printchunk(char* base, size_t sz, bool used) { +static void printchunk(void* base, size_t sz, bool used) { std::cout << "0x" << std::hex << std::setw(16) << std::setfill('0') << base << " 0x" << std::hex << std::setw(16) << std::setfill('0') << sz << @@ -149,7 +153,7 @@ void Arena::walk() const printchunk(chunk.first, chunk.second, true); std::cout << std::endl; for (const auto& chunk: chunks_free) - printchunk(chunk.first, chunk.second, false); + printchunk(chunk.first, chunk.second->first, false); std::cout << std::endl; } #endif diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 6ed7350ea2..d79a598bf1 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -23,7 +23,17 @@ #include <boost/test/unit_test.hpp> -BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup) +namespace miner_tests { +struct MinerTestingSetup : public TestingSetup { + void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); + bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) + { + return CheckSequenceLocks(*m_node.mempool, tx, flags); + } +}; +} // namespace miner_tests + +BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) // BOOST_CHECK_EXCEPTION predicates to check the specific validation error class HasReason { @@ -89,16 +99,10 @@ static CBlockIndex CreateBlockIndex(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_mai return index; } -static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - LOCK(::mempool.cs); - return CheckSequenceLocks(::mempool, tx, flags); -} - // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. -static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs) +void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; @@ -114,19 +118,19 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use - mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); - mempool.addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); @@ -137,7 +141,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& tx.vin[0].prevout.hash = hashHighFeeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); - mempool.addUnchecked(entry.Fee(0).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(0).FromTx(tx)); size_t freeTxSize = ::GetSerializeSize(tx, PROTOCOL_VERSION); // Calculate a fee on child transaction that will put the package just @@ -147,7 +151,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& tx.vin[0].prevout.hash = hashFreeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); - mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { @@ -158,10 +162,10 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); + m_node.mempool->removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); - mempool.addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx); @@ -174,7 +178,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& tx.vout[0].nValue = 5000000000LL - 100000000; tx.vout[1].nValue = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); - mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx.vin[0].prevout.hash = hashFreeTx2; @@ -182,7 +186,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); - mempool.addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. @@ -195,7 +199,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& // as well. tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee - mempool.addUnchecked(entry.Fee(10000).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } @@ -252,7 +256,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) } LOCK(cs_main); - LOCK(::mempool.cs); + LOCK(m_node.mempool->cs); // Just to make sure we can still make simple blocks BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); @@ -276,12 +280,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); - mempool.clear(); + m_node.mempool->clear(); tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vout[0].nValue = BLOCKSUBSIDY; @@ -291,11 +295,11 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - mempool.clear(); + m_node.mempool->clear(); // block size > limit tx.vin[0].scriptSig = CScript(); @@ -311,24 +315,24 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - mempool.clear(); + m_node.mempool->clear(); - // orphan in mempool, template creation fails + // orphan in *m_node.mempool, template creation fails hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - mempool.clear(); + m_node.mempool->clear(); // child with higher feerate than parent tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin.resize(2); tx.vin[1].scriptSig = CScript() << OP_1; @@ -336,34 +340,34 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[1].prevout.n = 0; tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - mempool.clear(); + m_node.mempool->clear(); - // coinbase in mempool, template creation fails + // coinbase in *m_node.mempool, template creation fails tx.vin.resize(1); tx.vin[0].prevout.SetNull(); tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vout[0].nValue = 0; hash = tx.GetHash(); // give it a fee so it'll get mined - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw bad-cb-multiple BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); - mempool.clear(); + m_node.mempool->clear(); - // double spend txn pair in mempool, template creation fails + // double spend txn pair in *m_node.mempool, template creation fails tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vin[0].scriptSig = CScript() << OP_1; tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; tx.vout[0].scriptPubKey = CScript() << OP_1; hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - mempool.clear(); + m_node.mempool->clear(); // subsidy changing int nHeight = ::ChainActive().Height(); @@ -392,7 +396,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - // invalid p2sh txn in mempool, template creation fails + // invalid p2sh txn in *m_node.mempool, template creation fails tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vin[0].prevout.n = 0; tx.vin[0].scriptSig = CScript() << OP_1; @@ -400,15 +404,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw block-validation-failed BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); - mempool.clear(); + m_node.mempool->clear(); // Delete the dummy blocks again. while (::ChainActive().Tip()->nHeight > nHeight) { @@ -439,7 +443,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; hash = tx.GetHash(); - mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, &prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 2))); // Sequence locks pass on 2nd block @@ -449,7 +453,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((::ChainActive().Tip()->GetMedianTimePast()+1-::ChainActive()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); - mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail @@ -465,7 +469,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = baseheight + 3; tx.nLockTime = ::ChainActive().Tip()->nHeight + 1; hash = tx.GetHash(); - mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block @@ -476,7 +480,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); - mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later @@ -513,7 +517,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) ::ChainActive().Tip()->nHeight--; SetMockTime(0); - mempool.clear(); + m_node.mempool->clear(); TestPackageSelection(chainparams, scriptPubKey, txFirst); diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 245c03d774..cace75f093 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -34,17 +34,17 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) LOCK(cs_main); - unsigned int initialPoolSize = mempool.size(); + unsigned int initialPoolSize = m_node.mempool->size(); BOOST_CHECK_EQUAL( false, - AcceptToMemoryPool(mempool, state, MakeTransactionRef(coinbaseTx), + AcceptToMemoryPool(*m_node.mempool, state, MakeTransactionRef(coinbaseTx), nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */)); // Check that the transaction hasn't been added to mempool. - BOOST_CHECK_EQUAL(mempool.size(), initialPoolSize); + BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize); // Check that the validation state reflects the unsuccessful attempt. BOOST_CHECK(state.IsInvalid()); diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index a5bc15bb9f..67f45c4ed4 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -17,16 +17,6 @@ bool CheckInputs(const CTransaction& tx, TxValidationState &state, const CCoinsV BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) -static bool -ToMemPool(const CMutableTransaction& tx) -{ - LOCK(cs_main); - - TxValidationState state; - return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), - nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */); -} - BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) { // Make sure skipping validation of transactions that were @@ -35,6 +25,14 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; + const auto ToMemPool = [this](const CMutableTransaction& tx) { + LOCK(cs_main); + + TxValidationState state; + return AcceptToMemoryPool(*m_node.mempool, state, MakeTransactionRef(tx), + nullptr /* plTxnReplaced */, true /* bypass_limits */, 0 /* nAbsurdFee */); + }; + // Create a double-spend of mature coinbase txn: std::vector<CMutableTransaction> spends; spends.resize(2); @@ -72,7 +70,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) LOCK(cs_main); BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); } - mempool.clear(); + m_node.mempool->clear(); // Test 3: ... and should be rejected if spend2 is in the memory pool BOOST_CHECK(ToMemPool(spends[1])); @@ -81,9 +79,9 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) LOCK(cs_main); BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); } - mempool.clear(); + m_node.mempool->clear(); - // Final sanity test: first spend in mempool, second in block, that's OK: + // Final sanity test: first spend in *m_node.mempool, second in block, that's OK: std::vector<CMutableTransaction> oneSpend; oneSpend.push_back(spends[0]); BOOST_CHECK(ToMemPool(spends[1])); @@ -94,7 +92,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) } // spends[1] should have been removed from the mempool when the // block with spends[0] is accepted: - BOOST_CHECK_EQUAL(mempool.size(), 0U); + BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U); } // Run CheckInputs (using CoinsTip()) on the given transaction, for all script diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 0c6ecdf69d..86c355fdcd 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -107,7 +107,6 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); - mempool.setSanityCheck(1.0); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); g_chainstate = MakeUnique<CChainState>(); ::ChainstateActive().InitCoinsDB( @@ -131,6 +130,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha } g_parallel_script_checks = true; + m_node.mempool = &::mempool; + m_node.mempool->setSanityCheck(1.0); m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. } @@ -144,6 +145,7 @@ TestingSetup::~TestingSetup() g_rpc_node = nullptr; m_node.connman.reset(); m_node.banman.reset(); + m_node.mempool = nullptr; UnloadBlockIndex(); g_chainstate.reset(); pblocktree.reset(); diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index b7cf82906a..1fa48b325c 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -279,7 +279,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) std::list<CTransactionRef> plTxnReplaced; for (const auto& tx : txs) { BOOST_REQUIRE(AcceptToMemoryPool( - ::mempool, + *m_node.mempool, state, tx, &plTxnReplaced, @@ -290,8 +290,8 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // Check that all txs are in the pool { - LOCK(::mempool.cs); - BOOST_CHECK_EQUAL(::mempool.mapTx.size(), txs.size()); + LOCK(m_node.mempool->cs); + BOOST_CHECK_EQUAL(m_node.mempool->mapTx.size(), txs.size()); } // Run a thread that simulates an RPC caller that is polling while @@ -301,8 +301,8 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // the transactions invalidated by the reorg, or none of them, and // not some intermediate amount. while (true) { - LOCK(::mempool.cs); - if (::mempool.mapTx.size() == 0) { + LOCK(m_node.mempool->cs); + if (m_node.mempool->mapTx.size() == 0) { // We are done with the reorg break; } @@ -311,7 +311,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // be atomic. So the caller assumes that the returned mempool // is consistent. That is, it has all txs that were there // before the reorg. - assert(::mempool.mapTx.size() == txs.size()); + assert(m_node.mempool->mapTx.size() == txs.size()); continue; } LOCK(cs_main); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 08f935c24f..3c967a04c0 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -17,6 +17,7 @@ #include <util/system.h> #include <util/moneystr.h> #include <util/time.h> +#include <validationinterface.h> CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, unsigned int _entryHeight, @@ -403,7 +404,12 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { - NotifyEntryRemoved(it->GetSharedTx(), reason); + CTransactionRef ptx = it->GetSharedTx(); + NotifyEntryRemoved(ptx, reason); + if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) { + GetMainSignals().TransactionRemovedFromMempool(ptx); + } + const uint256 hash = it->GetTx().GetHash(); for (const CTxIn& txin : it->GetTx().vin) mapNextTx.erase(txin.prevout); diff --git a/src/validation.cpp b/src/validation.cpp index e744110371..8f5d333170 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -129,9 +129,6 @@ CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); CBlockPolicyEstimator feeEstimator; CTxMemPool mempool(&feeEstimator); -/** Constant stuff for coinbase transactions we create: */ -CScript COINBASE_FLAGS; - // Internal stuff namespace { CBlockIndex* pindexBestInvalid = nullptr; diff --git a/src/validation.h b/src/validation.h index 31721cfbf7..d87113c41f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -137,7 +137,6 @@ struct BlockHasher size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } }; -extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern CBlockPolicyEstimator feeEstimator; extern CTxMemPool mempool; diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 663308bae9..6c0f4d5edd 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -7,9 +7,9 @@ #include <primitives/block.h> #include <scheduler.h> -#include <txmempool.h> #include <future> +#include <unordered_map> #include <utility> #include <boost/signals2/signal.hpp> @@ -46,11 +46,6 @@ struct MainSignalsInstance { static CMainSignals g_signals; -// This map has to a separate global instead of a member of MainSignalsInstance, -// because RegisterWithMempoolSignals is currently called before RegisterBackgroundSignalScheduler, -// so MainSignalsInstance hasn't been created yet. -static std::unordered_map<CTxMemPool*, boost::signals2::scoped_connection> g_connNotifyEntryRemoved; - void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); m_internals.reset(new MainSignalsInstance(&scheduler)); @@ -71,17 +66,6 @@ size_t CMainSignals::CallbacksPending() { return m_internals->m_schedulerClient.CallbacksPending(); } -void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) { - g_connNotifyEntryRemoved.emplace(std::piecewise_construct, - std::forward_as_tuple(&pool), - std::forward_as_tuple(pool.NotifyEntryRemoved.connect(std::bind(&CMainSignals::MempoolEntryRemoved, this, std::placeholders::_1, std::placeholders::_2))) - ); -} - -void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool& pool) { - g_connNotifyEntryRemoved.erase(&pool); -} - CMainSignals& GetMainSignals() { return g_signals; @@ -126,13 +110,6 @@ void SyncWithValidationInterfaceQueue() { promise.get_future().wait(); } -void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, MemPoolRemovalReason reason) { - if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) { - m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] { - m_internals->TransactionRemovedFromMempool(ptx); - }); - } -} void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { // Dependencies exist that require UpdatedBlockTip events to be delivered in the order in which @@ -150,6 +127,12 @@ void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx) { }); } +void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef &ptx) { + m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] { + m_internals->TransactionRemovedFromMempool(ptx); + }); +} + void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>>& pvtxConflicted) { m_internals->m_schedulerClient.AddToProcessQueue([pblock, pindex, pvtxConflicted, this] { m_internals->BlockConnected(pblock, pindex, *pvtxConflicted); diff --git a/src/validationinterface.h b/src/validationinterface.h index 6a8059a6a0..63aad8661f 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -21,8 +21,6 @@ class CConnman; class CValidationInterface; class uint256; class CScheduler; -class CTxMemPool; -enum class MemPoolRemovalReason; // These functions dispatch to one or all registered wallets @@ -158,8 +156,6 @@ private: friend void ::UnregisterAllValidationInterfaces(); friend void ::CallFunctionInValidationInterfaceQueue(std::function<void ()> func); - void MempoolEntryRemoved(CTransactionRef tx, MemPoolRemovalReason reason); - public: /** Register a CScheduler to give callbacks which should run in the background (may only be called once) */ void RegisterBackgroundSignalScheduler(CScheduler& scheduler); @@ -170,13 +166,10 @@ public: size_t CallbacksPending(); - /** Register with mempool to call TransactionRemovedFromMempool callbacks */ - void RegisterWithMempoolSignals(CTxMemPool& pool); - /** Unregister with mempool */ - void UnregisterWithMempoolSignals(CTxMemPool& pool); void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void TransactionAddedToMempool(const CTransactionRef &); + void TransactionRemovedFromMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex); void ChainStateFlushed(const CBlockLocator &); diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index fca4b75c45..2893d0ab3d 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,14 +6,18 @@ #define BITCOIN_WALLET_COINCONTROL_H #include <optional.h> +#include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> -#include <wallet/wallet.h> +#include <script/standard.h> const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; +//! Default for -avoidpartialspends +static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false; + /** Coin Control Features. */ class CCoinControl { diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index a8b3df1f2e..dd0d2ffbd7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -8,9 +8,11 @@ #include <net.h> #include <node/context.h> #include <outputtype.h> +#include <ui_interface.h> #include <util/moneystr.h> #include <util/system.h> #include <util/translation.h> +#include <wallet/coincontrol.h> #include <wallet/wallet.h> #include <walletinitinterface.h> @@ -58,7 +60,7 @@ void WalletInit::AddWalletOptions() const gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); #if HAVE_SYSTEM gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp index aa13cacca4..96c1ad8d3f 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -39,12 +39,35 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps return TransactionError::SIGHASH_MISMATCH; } - complete &= SignPSBTInput(HidingSigningProvider(pwallet->GetSigningProvider(), !sign, !bip32derivs), psbtx, i, sighash_type); + // Get the scriptPubKey to know which SigningProvider to use + CScript script; + if (!input.witness_utxo.IsNull()) { + script = input.witness_utxo.scriptPubKey; + } else if (input.non_witness_utxo) { + script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey; + } else { + // There's no UTXO so we can just skip this now + complete = false; + continue; + } + SignatureData sigdata; + input.FillSignatureData(sigdata); + const SigningProvider* provider = pwallet->GetSigningProvider(script, sigdata); + if (!provider) { + complete = false; + continue; + } + + complete &= SignPSBTInput(HidingSigningProvider(provider, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(HidingSigningProvider(pwallet->GetSigningProvider(), true, !bip32derivs), psbtx, i); + const CTxOut& out = psbtx.tx->vout.at(i); + const SigningProvider* provider = pwallet->GetSigningProvider(out.scriptPubKey); + if (provider) { + UpdatePSBTOutput(HidingSigningProvider(provider, true, !bip32derivs), psbtx, i); + } } return TransactionError::OK; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 00742aed7a..3563b05c71 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -560,7 +560,11 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } - const SigningProvider* provider = pwallet->GetSigningProvider(); + CScript script_pub_key = GetScriptForDestination(*pkhash); + const SigningProvider* provider = pwallet->GetSigningProvider(script_pub_key); + if (!provider) { + throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + } CKey key; CKeyID keyID(*pkhash); @@ -2940,34 +2944,36 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("label", i->second.name); } - const SigningProvider* provider = pwallet->GetSigningProvider(); - if (scriptPubKey.IsPayToScriptHash()) { - const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); - CScript redeemScript; - if (provider->GetCScript(hash, redeemScript)) { - entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); - // Now check if the redeemScript is actually a P2WSH script - CTxDestination witness_destination; - if (redeemScript.IsPayToWitnessScriptHash()) { - bool extracted = ExtractDestination(redeemScript, witness_destination); - CHECK_NONFATAL(extracted); - // Also return the witness script - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + if (scriptPubKey.IsPayToScriptHash()) { + const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); + CScript redeemScript; + if (provider->GetCScript(hash, redeemScript)) { + entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + // Now check if the redeemScript is actually a P2WSH script + CTxDestination witness_destination; + if (redeemScript.IsPayToWitnessScriptHash()) { + bool extracted = ExtractDestination(redeemScript, witness_destination); + CHECK_NONFATAL(extracted); + // Also return the witness script + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } - } - } else if (scriptPubKey.IsPayToWitnessScriptHash()) { - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } else if (scriptPubKey.IsPayToWitnessScriptHash()) { + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } } @@ -2978,8 +2984,11 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { - auto descriptor = InferDescriptor(scriptPubKey, *pwallet->GetLegacyScriptPubKeyMan()); - entry.pushKV("desc", descriptor->ToString()); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + auto descriptor = InferDescriptor(scriptPubKey, *provider); + entry.pushKV("desc", descriptor->ToString()); + } } if (avoid_reuse) entry.pushKV("reused", reused); entry.pushKV("safe", out.fSafe); @@ -3288,7 +3297,23 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[1], nullptr, coins); - return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]); + std::set<const SigningProvider*> providers; + for (const std::pair<COutPoint, Coin> coin_pair : coins) { + const SigningProvider* provider = pwallet->GetSigningProvider(coin_pair.second.out.scriptPubKey); + if (provider) { + providers.insert(std::move(provider)); + } + } + if (providers.size() == 0) { + // When there are no available providers, use DUMMY_SIGNING_PROVIDER so we can check if the tx is complete + providers.insert(&DUMMY_SIGNING_PROVIDER); + } + + UniValue result(UniValue::VOBJ); + for (const SigningProvider* provider : providers) { + SignTransaction(mtx, provider, coins, request.params[2], result); + } + return result; } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3653,9 +3678,10 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); + CScript script = GetScriptForDestination(dest); const SigningProvider* provider = nullptr; if (pwallet) { - provider = pwallet->GetSigningProvider(); + provider = pwallet->GetSigningProvider(script); } ret.pushKVs(detail); ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider), dest)); @@ -3747,11 +3773,11 @@ UniValue getaddressinfo(const JSONRPCRequest& request) CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); - const SigningProvider* provider = pwallet->GetSigningProvider(); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = IsSolvable(*provider, scriptPubKey); + bool solvable = provider && IsSolvable(*provider, scriptPubKey); ret.pushKV("solvable", solvable); if (solvable) { ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); @@ -3764,7 +3790,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); - ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(); + ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); if (spk_man) { if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) { ret.pushKV("timestamp", meta->nCreateTime); diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index bc068f1499..7e9f88f6b7 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -55,7 +55,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set) set.emplace(MakeTransactionRef(tx), nInput); } -static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) { balance += nValue; static int nextLockTime = 0; @@ -63,21 +63,31 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; + if (spendable) { + CTxDestination dest; + std::string error; + assert(wallet.GetNewDestination(OutputType::BECH32, "", dest, error)); + tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest); + } if (fIsFromMe) { // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe() tx.vin.resize(1); } - std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); + std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx))); if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); - testWallet.AddToWallet(*wtx.get()); + wallet.AddToWallet(*wtx.get()); wtxn.emplace_back(std::move(wtx)); } +static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +{ + add_coin(testWallet, nValue, nAge, fIsFromMe, nInput, spendable); +} static void empty_wallet(void) { @@ -252,17 +262,33 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); - // Make sure that we aren't using BnB when there are preset inputs + // Test fees subtracted from output: + empty_wallet(); + add_coin(1 * CENT); + vCoins.at(0).nInputBytes = 40; + BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); + coin_selection_params_bnb.m_subtract_fee_outputs = true; + BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); + BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + + // Make sure that can use BnB when there are preset inputs empty_wallet(); - add_coin(5 * CENT); - add_coin(3 * CENT); - add_coin(2 * CENT); - CCoinControl coin_control; - coin_control.fAllowOtherInputs = true; - coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); - BOOST_CHECK(testWallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); - BOOST_CHECK(!bnb_used); - BOOST_CHECK(!coin_selection_params_bnb.use_bnb); + { + std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + bool firstRun; + wallet->LoadWallet(firstRun); + LOCK(wallet->cs_wallet); + add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true); + add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true); + add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true); + CCoinControl coin_control; + coin_control.fAllowOtherInputs = true; + coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); + coin_selection_params_bnb.effective_fee = CFeeRate(0); + BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); + BOOST_CHECK(bnb_used); + BOOST_CHECK(coin_selection_params_bnb.use_bnb); + } } BOOST_AUTO_TEST_CASE(knapsack_solver_test) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2f2931cef1..b1e1385ca3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1363,7 +1363,11 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); + if (!provider) { + // We don't know about this scriptpbuKey; + return false; + } if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; @@ -1444,11 +1448,9 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array, and the ability to sign. for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); + // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { return -1; } @@ -1463,8 +1465,6 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. return -1; } return GetVirtualTransactionSize(CTransaction(txNew)); @@ -2125,7 +2125,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(wtx.tx->vout[i].scriptPubKey); bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); @@ -2160,7 +2160,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch for (const COutput& coin : availableCoins) { CTxDestination address; - if (coin.fSpendable && + if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } @@ -2168,12 +2168,16 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch std::vector<COutPoint> lockedCoins; ListLockedCoins(lockedCoins); + // Include watch-only for wallets without private keys + const bool include_watch_only = IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { int depth = it->second.GetDepthInMainChain(); if (depth >= 0 && output.n < it->second.tx->vout.size() && - IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + IsMine(it->second.tx->vout[output.n]) == is_mine_filter + ) { CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( @@ -2234,7 +2238,11 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil if (effective_value > 0) { group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes); - group.effective_value += effective_value; + if (coin_selection_params.m_subtract_fee_outputs) { + group.effective_value += coin.txout.nValue; + } else { + group.effective_value += effective_value; + } ++it; } else { it = group.Discard(coin); @@ -2260,6 +2268,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const { std::vector<COutput> vCoins(vAvailableCoins); + CAmount value_to_select = nTargetValue; // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) @@ -2285,22 +2294,33 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm coin_control.ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { - // For now, don't use BnB if preset inputs are selected. TODO: Enable this later - bnb_used = false; - coin_selection_params.use_bnb = false; - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { const CWalletTx& wtx = it->second; // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) + if (wtx.tx->vout.size() <= outpoint.n) { + bnb_used = false; return false; + } // Just to calculate the marginal byte size - nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n)); - } else + CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + nValueFromPresetInputs += coin.txout.nValue; + if (coin.m_input_bytes <= 0) { + bnb_used = false; + return false; // Not solvable, can't estimate size for fee + } + coin.effective_value = coin.txout.nValue - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); + if (coin_selection_params.use_bnb) { + value_to_select -= coin.effective_value; + } else { + value_to_select -= coin.txout.nValue; + } + setPresetCoins.insert(coin); + } else { + bnb_used = false; return false; // TODO: Allow non-wallet inputs + } } // remove preset inputs from vCoins @@ -2329,14 +2349,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); - bool res = nTargetValue <= nValueFromPresetInputs || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); + bool res = value_to_select <= 0 || + SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset util::insert(setCoinsRet, setPresetCoins); @@ -2362,8 +2382,9 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider) { + // We don't know about this scriptpbuKey; return false; } @@ -2519,7 +2540,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; - ReserveDestination reservedest(this); + const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); + ReserveDestination reservedest(this, change_type); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto& recipient : vecSend) @@ -2578,8 +2600,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std return false; } CTxDestination dest; - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - bool ret = reservedest.GetReservedDestination(change_type, dest, true); + bool ret = reservedest.GetReservedDestination(dest, true); if (!ret) { strFailReason = "Keypool ran out, please call keypoolrefill first"; @@ -2602,7 +2623,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // BnB selector is the only selector used when this is true. // That should only happen on the first pass through the loop. - coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB + coin_selection_params.use_bnb = true; + coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values // Start with no fee and loop until there is enough fee while (true) { @@ -2616,7 +2638,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std nValueToSelect += nFeeRet; // vouts to the payees - coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + } for (const auto& recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); @@ -2633,7 +2657,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } // Include the fee cost for outputs. Note this is only used for BnB right now - coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + } if (IsDust(txout, chain().relayDustFee())) { @@ -2825,7 +2851,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; @@ -3098,8 +3124,8 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des m_spk_man->TopUp(); - ReserveDestination reservedest(this); - if (!reservedest.GetReservedDestination(type, dest, true)) { + ReserveDestination reservedest(this, type); + if (!reservedest.GetReservedDestination(dest, true)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } @@ -3265,7 +3291,7 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) +bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal) { m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); if (!m_spk_man) { @@ -3286,7 +3312,6 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); - m_spk_man->LearnRelatedScripts(vchPubKey, type); address = GetDestinationForKey(vchPubKey, type); dest = address; return true; @@ -3294,8 +3319,10 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin void ReserveDestination::KeepDestination() { - if (nIndex != -1) + if (nIndex != -1) { m_spk_man->KeepDestination(nIndex); + m_spk_man->LearnRelatedScripts(vchPubKey, type); + } nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); @@ -3651,9 +3678,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } if (auto spk_man = walletInstance->m_spk_man.get()) { - std::string error; if (!spk_man->Upgrade(prev_version, error)) { - chain.initError(error); return nullptr; } } @@ -4024,12 +4049,17 @@ bool CWallet::Lock() return true; } -ScriptPubKeyMan* CWallet::GetScriptPubKeyMan() const +ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const +{ + return m_spk_man.get(); +} + +const SigningProvider* CWallet::GetSigningProvider(const CScript& script) const { return m_spk_man.get(); } -const SigningProvider* CWallet::GetSigningProvider() const +const SigningProvider* CWallet::GetSigningProvider(const CScript& script, SignatureData& sigdata) const { return m_spk_man.get(); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a6b9c0131a..51df4a9a8b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -71,8 +71,6 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; -//! Default for -avoidpartialspends -static const bool DEFAULT_AVOIDPARTIALSPENDS = false; //! -txconfirmtarget default static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; //! -walletrbf default @@ -140,8 +138,9 @@ class ReserveDestination { protected: //! The wallet to reserve from - CWallet* pwallet; + CWallet* const pwallet; LegacyScriptPubKeyMan* m_spk_man{nullptr}; + OutputType const type; //! The index of the address's key in the keypool int64_t nIndex{-1}; //! The public key for the address @@ -153,10 +152,9 @@ protected: public: //! Construct a ReserveDestination object. This does NOT reserve an address yet - explicit ReserveDestination(CWallet* pwalletIn) - { - pwallet = pwalletIn; - } + explicit ReserveDestination(CWallet* pwallet, OutputType type) + : pwallet(pwallet) + , type(type) { } ReserveDestination(const ReserveDestination&) = delete; ReserveDestination& operator=(const ReserveDestination&) = delete; @@ -168,7 +166,7 @@ public: } //! Reserve an address - bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal); + bool GetReservedDestination(CTxDestination& pubkey, bool internal); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object goes out of scope @@ -584,6 +582,8 @@ struct CoinSelectionParams size_t change_spend_size = 0; CFeeRate effective_fee = CFeeRate(0); size_t tx_noinputs_size = 0; + //! Indicate that we are subtracting the fee from outputs + bool m_subtract_fee_outputs = false; CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {} CoinSelectionParams() {} @@ -1128,8 +1128,13 @@ public: LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; - ScriptPubKeyMan* GetScriptPubKeyMan() const; - const SigningProvider* GetSigningProvider() const; + //! Get the ScriptPubKeyMan for a script + ScriptPubKeyMan* GetScriptPubKeyMan(const CScript& script) const; + + //! Get the SigningProvider for a script + const SigningProvider* GetSigningProvider(const CScript& script) const; + const SigningProvider* GetSigningProvider(const CScript& script, SignatureData& sigdata) const; + LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; // Temporary LegacyScriptPubKeyMan accessors and aliases. diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index c466b120f2..0eef1d3ddf 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -267,6 +267,12 @@ class MempoolAcceptanceTest(BitcoinTestFramework): rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx.vin[0].scriptSig = CScript([b'a' * 1648]) # Some too large scriptSig (>1650 bytes) + self.check_mempool_result( + result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-size'}], + rawtxs=[tx.serialize().hex()], + ) + tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 0c7edbf434..fdb70097ee 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -1116,7 +1116,7 @@ class SegWitTest(BitcoinTestFramework): MAX_PROGRAM_LENGTH = 10000 # This program is 19 max pushes (9937 bytes), then 64 more opcode-bytes. - long_witness_program = CScript([b'a' * 520] * 19 + [OP_DROP] * 63 + [OP_TRUE]) + long_witness_program = CScript([b'a' * MAX_SCRIPT_ELEMENT_SIZE] * 19 + [OP_DROP] * 63 + [OP_TRUE]) assert len(long_witness_program) == MAX_PROGRAM_LENGTH + 1 long_witness_hash = sha256(long_witness_program) long_script_pubkey = CScript([OP_0, long_witness_hash]) @@ -1140,7 +1140,7 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0], self.test_node, block, accepted=False) # Try again with one less byte in the witness program - witness_program = CScript([b'a' * 520] * 19 + [OP_DROP] * 62 + [OP_TRUE]) + witness_program = CScript([b'a' * MAX_SCRIPT_ELEMENT_SIZE] * 19 + [OP_DROP] * 62 + [OP_TRUE]) assert len(witness_program) == MAX_PROGRAM_LENGTH witness_hash = sha256(witness_program) script_pubkey = CScript([OP_0, witness_hash]) diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 23748e5dd7..da795eac1f 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -675,7 +675,7 @@ class ImportMultiTest(BitcoinTestFramework): # Import pubkeys with key origin info self.log.info("Addresses should have hd keypath and master key id after import with key origin") pub_addr = self.nodes[1].getnewaddress() - pub_addr = self.nodes[1].getnewaddress() + pub_addr = self.nodes[1].getnewaddress(address_type="bech32") info = self.nodes[1].getaddressinfo(pub_addr) pub = info['pubkey'] pub_keypath = info['hdkeypath'] @@ -693,7 +693,7 @@ class ImportMultiTest(BitcoinTestFramework): assert_equal(pub_import_info['hdkeypath'], pub_keypath) # Import privkeys with key origin info - priv_addr = self.nodes[1].getnewaddress() + priv_addr = self.nodes[1].getnewaddress(address_type="bech32") info = self.nodes[1].getaddressinfo(priv_addr) priv = self.nodes[1].dumpprivkey(priv_addr) priv_keypath = info['hdkeypath'] @@ -742,8 +742,8 @@ class ImportMultiTest(BitcoinTestFramework): self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True) wrpc = self.nodes[1].get_wallet_rpc("noprivkeys") - addr1 = self.nodes[0].getnewaddress() - addr2 = self.nodes[0].getnewaddress() + addr1 = self.nodes[0].getnewaddress(address_type="bech32") + addr2 = self.nodes[0].getnewaddress(address_type="bech32") pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( @@ -761,15 +761,15 @@ class ImportMultiTest(BitcoinTestFramework): assert result[0]['success'] assert result[1]['success'] assert_equal(wrpc.getwalletinfo()["keypoolsize"], 2) - newaddr1 = wrpc.getnewaddress() + newaddr1 = wrpc.getnewaddress(address_type="bech32") assert_equal(addr1, newaddr1) - newaddr2 = wrpc.getnewaddress() + newaddr2 = wrpc.getnewaddress(address_type="bech32") assert_equal(addr2, newaddr2) # Import some public keys to the internal keypool of a no privkey wallet self.log.info("Adding pubkey to internal keypool of disableprivkey wallet") - addr1 = self.nodes[0].getnewaddress() - addr2 = self.nodes[0].getnewaddress() + addr1 = self.nodes[0].getnewaddress(address_type="bech32") + addr2 = self.nodes[0].getnewaddress(address_type="bech32") pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( @@ -789,15 +789,15 @@ class ImportMultiTest(BitcoinTestFramework): assert result[0]['success'] assert result[1]['success'] assert_equal(wrpc.getwalletinfo()["keypoolsize_hd_internal"], 2) - newaddr1 = wrpc.getrawchangeaddress() + newaddr1 = wrpc.getrawchangeaddress(address_type="bech32") assert_equal(addr1, newaddr1) - newaddr2 = wrpc.getrawchangeaddress() + newaddr2 = wrpc.getrawchangeaddress(address_type="bech32") assert_equal(addr2, newaddr2) # Import a multisig and make sure the keys don't go into the keypool self.log.info('Imported scripts with pubkeys should not have their pubkeys go into the keypool') - addr1 = self.nodes[0].getnewaddress() - addr2 = self.nodes[0].getnewaddress() + addr1 = self.nodes[0].getnewaddress(address_type="bech32") + addr2 = self.nodes[0].getnewaddress(address_type="bech32") pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index ccd12b5823..1538ad77b1 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -18,18 +18,13 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "qt/bitcoingui -> qt/walletframe -> qt/bitcoingui" "qt/bitcoingui -> qt/walletview -> qt/bitcoingui" "qt/clientmodel -> qt/peertablemodel -> qt/clientmodel" - "qt/paymentserver -> qt/walletmodel -> qt/paymentserver" "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel" "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog" "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel" - "qt/walletmodel -> qt/walletmodeltransaction -> qt/walletmodel" "txmempool -> validation -> txmempool" - "wallet/coincontrol -> wallet/wallet -> wallet/coincontrol" "wallet/fees -> wallet/wallet -> wallet/fees" "wallet/wallet -> wallet/walletdb -> wallet/wallet" "policy/fees -> txmempool -> validation -> policy/fees" - "qt/guiutil -> qt/walletmodel -> qt/optionsmodel -> qt/guiutil" - "txmempool -> validation -> validationinterface -> txmempool" "wallet/scriptpubkeyman -> wallet/wallet -> wallet/scriptpubkeyman" ) |