diff options
310 files changed, 8934 insertions, 4708 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index fb95876c36..3ca7818eca 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,9 +7,9 @@ clone_depth: 5 environment: PATH: 'C:\Python37-x64;C:\Python37-x64\Scripts;%PATH%' PYTHONUTF8: 1 - QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/qt51210x64_vs2019_1694/Qt5.12.10_x64_static_vs2019_1694.zip' - QT_DOWNLOAD_HASH: '3035a1307e8302bb3a76eba9bb3102979f945ab4022cc3bc2e1583edd44bdc99' - QT_LOCAL_PATH: 'C:\Qt5.12.10_x64_static_vs2019_1694' + QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/qt51211x64_static_vs2019_16101/Qt5.12.11_x64_static_vs2019_16101.zip' + QT_DOWNLOAD_HASH: 'cf1b58107fadbf0d9a957d14dab16cde6b6eb6936a1908472da1f967dda34a3a' + QT_LOCAL_PATH: 'C:\Qt5.12.11_x64_static_vs2019_16101' VCPKG_TAG: '75522bb1f2e7d863078bcd06322348f053a9e33f' install: # Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. diff --git a/Makefile.am b/Makefile.am index d84dab1842..3ac698b96f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,6 +58,7 @@ DIST_SHARE = \ BIN_CHECKS=$(top_srcdir)/contrib/devtools/symbol-check.py \ $(top_srcdir)/contrib/devtools/security-check.py \ + $(top_srcdir)/contrib/devtools/utils.py \ $(top_srcdir)/contrib/devtools/pixie.py WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \ @@ -366,14 +367,14 @@ clean-local: clean-docs test-security-check: if TARGET_DARWIN - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO + $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO + $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO endif if TARGET_WINDOWS - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE + $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE + $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE endif if TARGET_LINUX - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF - $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF + $(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF + $(AM_V_at) CC='$(CC)' CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF endif diff --git a/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj index 65ce1ee9da..a697c1dfb6 100644 --- a/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj +++ b/build_msvc/bitcoin-qt/bitcoin-qt.vcxproj @@ -55,6 +55,7 @@ <AdditionalIncludeDirectories>$(QtIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> + <SubSystem>Windows</SubSystem> <AdditionalDependencies>$(QtReleaseLibraries);%(AdditionalDependencies)</AdditionalDependencies> <AdditionalOptions>/ignore:4206 /LTCG:OFF</AdditionalOptions> </Link> diff --git a/build_msvc/bitcoin-util/bitcoin-util.vcxproj b/build_msvc/bitcoin-util/bitcoin-util.vcxproj new file mode 100644 index 0000000000..3a6aa4a837 --- /dev/null +++ b/build_msvc/bitcoin-util/bitcoin-util.vcxproj @@ -0,0 +1,37 @@ +<?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>{D3022AF6-AD33-4CE3-B358-87CB6A1B29CF}</ProjectGuid> + </PropertyGroup> + <PropertyGroup Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + </PropertyGroup> + <ItemGroup> + <ClCompile Include="..\..\src\bitcoin-util.cpp" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj"> + <Project>{2b384fa8-9ee1-4544-93cb-0d733c25e8ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_common\libbitcoin_common.vcxproj"> + <Project>{7c87e378-df58-482e-aa2f-1bc129bc19ce}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_crypto\libbitcoin_crypto.vcxproj"> + <Project>{6190199c-6cf4-4dad-bfbd-93fa72a760c1}</Project> + </ProjectReference> + <ProjectReference Include="..\libbitcoin_util\libbitcoin_util.vcxproj"> + <Project>{b53a5535-ee9d-4c6f-9a26-f79ee3bc3754}</Project> + </ProjectReference> + <ProjectReference Include="..\libunivalue\libunivalue.vcxproj"> + <Project>{5724ba7d-a09a-4ba8-800b-c4c1561b3d69}</Project> + </ProjectReference> + <ProjectReference Include="..\libsecp256k1\libsecp256k1.vcxproj"> + <Project>{bb493552-3b8c-4a8c-bf69-a6e7a51d2ea6}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <Import Project="..\common.vcxproj" /> +</Project> diff --git a/build_msvc/bitcoin.sln b/build_msvc/bitcoin.sln index 5e9715451f..7d8591c10b 100644 --- a/build_msvc/bitcoin.sln +++ b/build_msvc/bitcoin.sln @@ -32,6 +32,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bench_bitcoin", "bench_bitc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bitcoin-tx", "bitcoin-tx\bitcoin-tx.vcxproj", "{D3022AF6-AD33-4CE3-B358-87CB6A1B29CF}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bitcoin-util", "bitcoin-util\bitcoin-util.vcxproj", "{D3022AF6-AD33-4CE3-B358-87CB6A1B29CF}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bitcoin-wallet", "bitcoin-wallet\bitcoin-wallet.vcxproj", "{84DE8790-EDE3-4483-81AC-C32F15E861F4}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbitcoin_wallet_tool", "libbitcoin_wallet_tool\libbitcoin_wallet_tool.vcxproj", "{F91AC55E-6F5E-4C58-9AC5-B40DB7DEEF93}" diff --git a/build_msvc/common.qt.init.vcxproj b/build_msvc/common.qt.init.vcxproj index 68ad06c4ac..ce66a7ab34 100644 --- a/build_msvc/common.qt.init.vcxproj +++ b/build_msvc/common.qt.init.vcxproj @@ -2,7 +2,7 @@ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Label="QtGlobals"> - <QtBaseDir>C:\Qt5.12.10_x64_static_vs2019_1694</QtBaseDir> + <QtBaseDir>C:\Qt5.12.11_x64_static_vs2019_16101</QtBaseDir> <QtPluginsLibraryDir>$(QtBaseDir)\plugins</QtPluginsLibraryDir> <QtLibraryDir>$(QtBaseDir)\lib</QtLibraryDir> <QtIncludeDir>$(QtBaseDir)\include</QtIncludeDir> diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index fa4d0410fa..8a9d808f5d 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -47,7 +47,7 @@ export RUN_FUZZ_TESTS=${RUN_FUZZ_TESTS:-false} export EXPECTED_TESTS_DURATION_IN_SECONDS=${EXPECTED_TESTS_DURATION_IN_SECONDS:-1000} export CONTAINER_NAME=${CONTAINER_NAME:-ci_unnamed} -export DOCKER_NAME_TAG=${DOCKER_NAME_TAG:-ubuntu:18.04} +export DOCKER_NAME_TAG=${DOCKER_NAME_TAG:-ubuntu:20.04} # Randomize test order. # See https://www.boost.org/doc/libs/1_71_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/random.html export BOOST_TEST_RANDOM=${BOOST_TEST_RANDOM:-1} diff --git a/ci/test/00_setup_env_arm.sh b/ci/test/00_setup_env_arm.sh index 07f099b85c..8d2b70e549 100755 --- a/ci/test/00_setup_env_arm.sh +++ b/ci/test/00_setup_env_arm.sh @@ -25,4 +25,4 @@ export RUN_FUNCTIONAL_TESTS=false export GOAL="install" # -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1" # This could be removed once the ABI change warning does not show up by default -export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi --enable-external-signer" +export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi" diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 05c724fc0b..2ddb932907 100755 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -11,6 +11,6 @@ export CONTAINER_NAME=ci_i686_centos_8 export DOCKER_NAME_TAG=centos:8 export DOCKER_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-zmq which patch lbzip2 dash rsync coreutils bison" export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports --enable-external-signer" +export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports" export CONFIG_SHELL="/bin/dash" export TEST_RUNNER_ENV="LC_ALL=en_US.UTF-8" diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh index 196394e908..73ac09c1de 100755 --- a/ci/test/00_setup_env_mac.sh +++ b/ci/test/00_setup_env_mac.sh @@ -15,4 +15,4 @@ export XCODE_BUILD_ID=12A7403 export RUN_UNIT_TESTS=false export RUN_FUNCTIONAL_TESTS=false export GOAL="deploy" -export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-external-signer" +export BITCOIN_CONFIG="--with-gui --enable-reduce-exports" diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_host.sh index 898c1530a1..c0d951a041 100755 --- a/ci/test/00_setup_env_mac_host.sh +++ b/ci/test/00_setup_env_mac_host.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export HOST=x86_64-apple-darwin18 export PIP_PACKAGES="zmq lief" export GOAL="install" -export BITCOIN_CONFIG="--with-gui --enable-reduce-exports --enable-external-signer" +export BITCOIN_CONFIG="--with-gui --enable-reduce-exports" export CI_OS_NAME="macos" export NO_DEPENDS=1 export OSX_SDK="" diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh index 92af98aa9b..ab185b6e71 100755 --- a/ci/test/00_setup_env_native_asan.sh +++ b/ci/test/00_setup_env_native_asan.sh @@ -11,4 +11,4 @@ export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent- export DOCKER_NAME_TAG=ubuntu:hirsute export NO_DEPENDS=1 export GOAL="install" -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++ --enable-external-signer" +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_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh index bedd0cf9aa..58388fa928 100755 --- a/ci/test/00_setup_env_native_fuzz.sh +++ b/ci/test/00_setup_env_native_fuzz.sh @@ -14,5 +14,5 @@ export RUN_UNIT_TESTS=false export RUN_FUNCTIONAL_TESTS=false export RUN_FUZZ_TESTS=true export GOAL="install" -export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC=clang CXX=clang++ --enable-external-signer" +export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC=clang CXX=clang++" export CCACHE_SIZE=200M diff --git a/ci/test/00_setup_env_native_multiprocess.sh b/ci/test/00_setup_env_native_multiprocess.sh index 37d714400b..8869b2a083 100755 --- a/ci/test/00_setup_env_native_multiprocess.sh +++ b/ci/test/00_setup_env_native_multiprocess.sh @@ -11,7 +11,6 @@ export DOCKER_NAME_TAG=ubuntu:20.04 export PACKAGES="cmake python3 python3-pip llvm clang" export DEP_OPTS="DEBUG=1 MULTIPROCESS=1" export GOAL="install" -export BITCOIN_CONFIG="--enable-external-signer --enable-debug CC=clang CXX=clang++" # Use clang to avoid OOM +export BITCOIN_CONFIG="--enable-debug CC=clang CXX=clang++" # Use clang to avoid OOM export TEST_RUNNER_ENV="BITCOIND=bitcoin-node" -export RUN_SECURITY_TESTS="true" export PIP_PACKAGES="lief" diff --git a/ci/test/00_setup_env_native_nowallet.sh b/ci/test/00_setup_env_native_nowallet.sh index a496b5af6e..d167c9198a 100755 --- a/ci/test/00_setup_env_native_nowallet.sh +++ b/ci/test/00_setup_env_native_nowallet.sh @@ -11,4 +11,4 @@ export DOCKER_NAME_TAG=ubuntu:18.04 # Use bionic to have one config run the tes export PACKAGES="python3-zmq clang-5.0 llvm-5.0" # Use clang-5 to test C++17 compatibility, see doc/dependencies.md export DEP_OPTS="NO_WALLET=1" export GOAL="install" -export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CC=clang-5.0 CXX=clang++-5.0 --enable-external-signer" +export BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CC=clang-5.0 CXX=clang++-5.0" diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh index 61948ab221..b3e967c898 100755 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -14,6 +14,6 @@ export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude fe export RUN_UNIT_TESTS_SEQUENTIAL="true" export RUN_UNIT_TESTS="false" export GOAL="install" -export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1" +export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1 v0.20.1" export BITCOIN_CONFIG="--enable-zmq --with-libs=no --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports ---enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" --enable-external-signer" +--enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\"" diff --git a/ci/test/00_setup_env_native_tsan.sh b/ci/test/00_setup_env_native_tsan.sh index 33f63fa9ba..a5082bdaab 100755 --- a/ci/test/00_setup_env_native_tsan.sh +++ b/ci/test/00_setup_env_native_tsan.sh @@ -11,4 +11,4 @@ export DOCKER_NAME_TAG=ubuntu:hirsute export PACKAGES="clang llvm libc++abi-dev libc++-dev python3-zmq" export DEP_OPTS="CC=clang CXX='clang++ -stdlib=libc++'" export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++' --enable-external-signer" +export BITCOIN_CONFIG="--enable-zmq --with-gui=no CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++'" diff --git a/ci/test/00_setup_env_s390x.sh b/ci/test/00_setup_env_s390x.sh index 88b431f3c7..51a0fd9117 100755 --- a/ci/test/00_setup_env_s390x.sh +++ b/ci/test/00_setup_env_s390x.sh @@ -23,4 +23,4 @@ export RUN_UNIT_TESTS=true export TEST_RUNNER_ENV="LC_ALL=C" export RUN_FUNCTIONAL_TESTS=true export GOAL="install" -export BITCOIN_CONFIG="--enable-reduce-exports --with-incompatible-bdb --enable-external-signer" +export BITCOIN_CONFIG="--enable-reduce-exports --with-incompatible-bdb" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 01dbfe221b..2079d2ed2b 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -11,6 +11,7 @@ if [[ $QEMU_USER_CMD == qemu-s390* ]]; then fi if [ "$CI_OS_NAME" == "macos" ]; then + sudo -H pip3 install --upgrade pip IN_GETOPT_BIN="/usr/local/opt/gnu-getopt/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES fi @@ -83,11 +84,14 @@ fi DOCKER_EXEC echo "Free disk space:" DOCKER_EXEC df -h -if [ ! -d ${DIR_QA_ASSETS} ]; then - DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS} +if [ "$RUN_FUZZ_TESTS" = "true" ] || [ "$RUN_UNIT_TESTS" = "true" ] || [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then + if [ ! -d ${DIR_QA_ASSETS} ]; then + DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS} + fi + + export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/ + export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/ fi -export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/ -export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/ DOCKER_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/" diff --git a/configure.ac b/configure.ac index aaa932b093..6f78e8dec4 100644 --- a/configure.ac +++ b/configure.ac @@ -318,13 +318,6 @@ AC_ARG_ENABLE([gprof], [enable_gprof=$enableval], [enable_gprof=no]) -dnl Pass compiler & linker flags that make builds deterministic -AC_ARG_ENABLE([determinism], - [AS_HELP_STRING([--enable-determinism], - [Enable compilation flags that make builds deterministic (default is no)])], - [enable_determinism=$enableval], - [enable_determinism=no]) - dnl Turn warnings into errors AC_ARG_ENABLE([werror], [AS_HELP_STRING([--enable-werror], @@ -333,9 +326,9 @@ AC_ARG_ENABLE([werror], [enable_werror=no]) AC_ARG_ENABLE([external-signer], - [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is no, requires Boost::Process)])], + [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is yes, requires Boost::Process)])], [use_external_signer=$enableval], - [use_external_signer=no]) + [use_external_signer=yes]) AC_LANG_PUSH([C++]) @@ -475,7 +468,9 @@ if test "x$CXXFLAGS_overridden" = "xno"; then AX_CHECK_COMPILE_FLAG([-Wself-assign],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-self-assign"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wunused-local-typedef],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-unused-local-typedef"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wimplicit-fallthrough],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-implicit-fallthrough"],,[[$CXXFLAG_WERROR]]) - AX_CHECK_COMPILE_FLAG([-Wdeprecated-copy],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-deprecated-copy"],,[[$CXXFLAG_WERROR]]) + if test x$suppress_external_warnings != xyes ; then + AX_CHECK_COMPILE_FLAG([-Wdeprecated-copy],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-deprecated-copy"],,[[$CXXFLAG_WERROR]]) + fi fi dnl Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review. @@ -905,6 +900,7 @@ if test x$use_hardening != xno; then ]) fi + AX_CHECK_LINK_FLAG([[-Wl,--enable-reloc-section]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--enable-reloc-section"],, [[$LDFLAG_WERROR]]) AX_CHECK_LINK_FLAG([[-Wl,--dynamicbase]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--dynamicbase"],, [[$LDFLAG_WERROR]]) AX_CHECK_LINK_FLAG([[-Wl,--nxcompat]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--nxcompat"],, [[$LDFLAG_WERROR]]) AX_CHECK_LINK_FLAG([[-Wl,--high-entropy-va]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,--high-entropy-va"],, [[$LDFLAG_WERROR]]) @@ -929,12 +925,6 @@ if test x$TARGET_OS = xdarwin; then AX_CHECK_LINK_FLAG([[-Wl,-bind_at_load]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-bind_at_load"],, [[$LDFLAG_WERROR]]) fi -if test x$enable_determinism = xyes; then - if test x$TARGET_OS = xwindows; then - AX_CHECK_LINK_FLAG([[-Wl,--no-insert-timestamp]], [LDFLAGS="$LDFLAGS -Wl,--no-insert-timestamp"],, [[$LDFLAG_WERROR]]) - fi -fi - AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h strings.h sys/types.h sys/stat.h sys/select.h sys/prctl.h sys/sysctl.h vm/vm_param.h sys/vmmeter.h sys/resources.h]) AC_CHECK_DECLS([getifaddrs, freeifaddrs],[CHECK_SOCKET],, @@ -1357,13 +1347,15 @@ if test x$enable_wallet != xno; then fi if test x$use_ebpf != xno; then - AC_CHECK_HEADER([sys/sdt.h], [have_sdt=yes], [have_sdt=no]) -else - have_sdt=no -fi - -if test x$have_sdt = xyes; then - AC_DEFINE([ENABLE_TRACING], [1], [Define to 1 to enable eBPF user static defined tracepoints]) + AC_MSG_CHECKING([whether eBPF tracepoints are supported]) + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM( + [#include <sys/sdt.h>], + [DTRACE_PROBE("context", "event");] + )], + [AC_MSG_RESULT(yes); have_sdt=yes; AC_DEFINE([ENABLE_TRACING], [1], [Define to 1 to enable eBPF user static defined tracepoints])], + [AC_MSG_RESULT(no); have_sdt=no;] + ) fi dnl Check for libminiupnpc (optional) @@ -1411,37 +1403,23 @@ fi if test x$use_boost = xyes; then dnl Check for Boost headers - AX_BOOST_BASE([1.58.0],[],[AC_MSG_ERROR([Boost is not available!])]) + AX_BOOST_BASE([1.64.0],[],[AC_MSG_ERROR([Boost is not available!])]) if test x$want_boost = xno; then AC_MSG_ERROR([[only libbitcoinconsensus can be built without boost]]) fi AX_BOOST_SYSTEM AX_BOOST_FILESYSTEM - dnl Opt-in to Boost Process if external signer support is requested - if test "x$use_external_signer" != xno; then - AC_MSG_CHECKING(for Boost Process) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <boost/process.hpp>]], - [[ boost::process::child* child = new boost::process::child; delete child; ]])], - [ AC_MSG_RESULT(yes) - AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled]) - ], - [ AC_MSG_ERROR([Boost::Process is required for external signer support, but not available!])] - ) - fi - if test x$suppress_external_warnings != xno; then BOOST_CPPFLAGS=SUPPRESS_WARNINGS($BOOST_CPPFLAGS) fi - dnl Boost 1.56 through 1.62 allow using std::atomic instead of its own atomic - dnl counter implementations. In 1.63 and later the std::atomic approach is default. - m4_pattern_allow(DBOOST_AC_USE_STD_ATOMIC) dnl otherwise it's treated like a macro - BOOST_CPPFLAGS="-DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" - BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB" fi +if test "x$use_external_signer" != xno; then + AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled]) +fi AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "x$use_external_signer" = "xyes"]) dnl Check for reduced exports diff --git a/contrib/devtools/pixie.py b/contrib/devtools/pixie.py index 8cf06a799a..64660968ad 100644 --- a/contrib/devtools/pixie.py +++ b/contrib/devtools/pixie.py @@ -217,7 +217,7 @@ def _parse_verneed(section: Section, strings: bytes, eh: ELFHeader) -> Dict[int, result = {} while True: verneed = Verneed(data, ofs, eh) - aofs = verneed.vn_aux + aofs = ofs + verneed.vn_aux while True: vernaux = Vernaux(data, aofs, eh, strings) result[vernaux.vna_other] = vernaux.name diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index d740a94560..61f727fa63 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -3,21 +3,22 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' -A script to check that the executables produced by gitian only contain -certain symbols and are only linked against allowed libraries. +A script to check that release executables only contain certain symbols +and are only linked against allowed libraries. Example usage: - find ../gitian-builder/build -type f -executable | xargs python3 contrib/devtools/symbol-check.py + find ../path/to/binaries -type f -executable | xargs python3 contrib/devtools/symbol-check.py ''' import subprocess import sys -import os from typing import List, Optional import lief import pixie +from utils import determine_wellknown_cmd + # Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases # # - g++ version 4.9.2 (https://packages.debian.org/search?suite=jessie&arch=any&searchon=names&keywords=g%2B%2B) @@ -41,8 +42,16 @@ import pixie # MAX_VERSIONS = { 'GCC': (4,8,0), -'GLIBC': (2,17), -'LIBATOMIC': (1,0) +'GLIBC': { + pixie.EM_386: (2,17), + pixie.EM_X86_64: (2,17), + pixie.EM_ARM: (2,17), + pixie.EM_AARCH64:(2,17), + pixie.EM_PPC64: (2,17), + pixie.EM_RISCV: (2,27), +}, +'LIBATOMIC': (1,0), +'V': (0,5,0), # xkb (bitcoin-qt only) } # See here for a description of _IO_stdin_used: # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=634261#109 @@ -52,7 +61,6 @@ IGNORE_EXPORTS = { '_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', '__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', 'environ', '_environ', '__environ', } -CPPFILT_CMD = os.getenv('CPPFILT', '/usr/bin/c++filt') # Allowed NEEDED libraries ELF_ALLOWED_LIBRARIES = { @@ -78,14 +86,6 @@ ELF_ALLOWED_LIBRARIES = { 'libfreetype.so.6', # font parsing 'libdl.so.2' # programming interface to dynamic linker } -ARCH_MIN_GLIBC_VER = { -pixie.EM_386: (2,1), -pixie.EM_X86_64: (2,2,5), -pixie.EM_ARM: (2,4), -pixie.EM_AARCH64:(2,17), -pixie.EM_PPC64: (2,17), -pixie.EM_RISCV: (2,27) -} MACHO_ALLOWED_LIBRARIES = { # bitcoind and bitcoin-qt @@ -140,7 +140,7 @@ class CPPFilt(object): Use a pipe to the 'c++filt' command. ''' def __init__(self): - self.proc = subprocess.Popen(CPPFILT_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) + self.proc = subprocess.Popen(determine_wellknown_cmd('CPPFILT', 'c++filt'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) def __call__(self, mangled): self.proc.stdin.write(mangled + '\n') @@ -161,7 +161,10 @@ def check_version(max_versions, version, arch) -> bool: ver = tuple([int(x) for x in ver.split('.')]) if not lib in max_versions: return False - return ver <= max_versions[lib] or lib == 'GLIBC' and ver <= ARCH_MIN_GLIBC_VER[arch] + if isinstance(max_versions[lib], tuple): + return ver <= max_versions[lib] + else: + return ver <= max_versions[lib][arch] def check_imported_symbols(filename) -> bool: elf = pixie.load(filename) @@ -212,6 +215,18 @@ def check_MACHO_libraries(filename) -> bool: ok = False return ok +def check_MACHO_min_os(filename) -> bool: + binary = lief.parse(filename) + if binary.build_version.minos == [10,14,0]: + return True + return False + +def check_MACHO_sdk(filename) -> bool: + binary = lief.parse(filename) + if binary.build_version.sdk == [10, 15, 6]: + return True + return False + def check_PE_libraries(filename) -> bool: ok: bool = True binary = lief.parse(filename) @@ -221,6 +236,14 @@ def check_PE_libraries(filename) -> bool: ok = False return ok +def check_PE_subsystem_version(filename) -> bool: + binary = lief.parse(filename) + major: int = binary.optional_header.major_subsystem_version + minor: int = binary.optional_header.minor_subsystem_version + if major == 6 and minor == 1: + return True + return False + CHECKS = { 'ELF': [ ('IMPORTED_SYMBOLS', check_imported_symbols), @@ -228,10 +251,13 @@ CHECKS = { ('LIBRARY_DEPENDENCIES', check_ELF_libraries) ], 'MACHO': [ - ('DYNAMIC_LIBRARIES', check_MACHO_libraries) + ('DYNAMIC_LIBRARIES', check_MACHO_libraries), + ('MIN_OS', check_MACHO_min_os), + ('SDK', check_MACHO_sdk), ], 'PE' : [ - ('DYNAMIC_LIBRARIES', check_PE_libraries) + ('DYNAMIC_LIBRARIES', check_PE_libraries), + ('SUBSYSTEM_VERSION', check_PE_subsystem_version), ] } diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index c079fe5b4d..14058e2cc8 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -9,6 +9,8 @@ import os import subprocess import unittest +from utils import determine_wellknown_cmd + def write_testcode(filename): with open(filename, 'w', encoding="utf8") as f: f.write(''' @@ -25,7 +27,7 @@ def clean_files(source, executable): os.remove(executable) def call_security_check(cc, source, executable, options): - subprocess.run([cc,source,'-o',executable] + options, check=True) + subprocess.run([*cc,source,'-o',executable] + options, check=True) p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) return (p.returncode, p.stdout.rstrip()) @@ -33,7 +35,7 @@ class TestSecurityChecks(unittest.TestCase): def test_ELF(self): source = 'test1.c' executable = 'test1' - cc = 'gcc' + cc = determine_wellknown_cmd('CC', 'gcc') write_testcode(source) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']), @@ -54,18 +56,20 @@ class TestSecurityChecks(unittest.TestCase): def test_PE(self): source = 'test1.c' executable = 'test1.exe' - cc = 'x86_64-w64-mingw32-gcc' + cc = determine_wellknown_cmd('CC', 'x86_64-w64-mingw32-gcc') write_testcode(source) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed HIGH_ENTROPY_VA RELOC_SECTION')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-Wl,--high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed RELOC_SECTION')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE']), + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) # -pie -fPIE does nothing unless --dynamicbase is also supplied + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE']), + (1, executable+': failed HIGH_ENTROPY_VA')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE']), (0, '')) clean_files(source, executable) @@ -73,7 +77,7 @@ class TestSecurityChecks(unittest.TestCase): def test_MACHO(self): source = 'test1.c' executable = 'test1' - cc = 'clang' + cc = determine_wellknown_cmd('CC', 'clang') write_testcode(source) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector']), @@ -95,4 +99,3 @@ class TestSecurityChecks(unittest.TestCase): if __name__ == '__main__': unittest.main() - diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index 106dfd2c5a..7d83c5f751 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -7,41 +7,51 @@ Test script for symbol-check.py ''' import os import subprocess +from typing import List import unittest -def call_symbol_check(cc, source, executable, options): - subprocess.run([cc,source,'-o',executable] + options, check=True) +from utils import determine_wellknown_cmd + +def call_symbol_check(cc: List[str], source, executable, options): + subprocess.run([*cc,source,'-o',executable] + options, check=True) p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) os.remove(source) os.remove(executable) return (p.returncode, p.stdout.rstrip()) +def get_machine(cc: List[str]): + p = subprocess.run([*cc,'-dumpmachine'], stdout=subprocess.PIPE, universal_newlines=True) + return p.stdout.rstrip() + class TestSymbolChecks(unittest.TestCase): def test_ELF(self): source = 'test1.c' executable = 'test1' - cc = 'gcc' + cc = determine_wellknown_cmd('CC', 'gcc') - # renameat2 was introduced in GLIBC 2.28, so is newer than the upper limit - # of glibc for all platforms + # there's no way to do this test for RISC-V at the moment; we build for + # RISC-V in a glibc 2.27 envinonment and we allow all symbols from 2.27. + if 'riscv' in get_machine(cc): + self.skipTest("test not available for RISC-V") + + # nextup was introduced in GLIBC 2.24, so is newer than our supported + # glibc (2.17), and available in our release build environment (2.24). with open(source, 'w', encoding="utf8") as f: f.write(''' #define _GNU_SOURCE - #include <stdio.h> - #include <linux/fs.h> + #include <math.h> - int renameat2(int olddirfd, const char *oldpath, - int newdirfd, const char *newpath, unsigned int flags); + double nextup(double x); int main() { - renameat2(0, "test", 0, "test_", RENAME_EXCHANGE); + nextup(3.14); return 0; } ''') - self.assertEqual(call_symbol_check(cc, source, executable, []), - (1, executable + ': symbol renameat2 from unsupported version GLIBC_2.28\n' + + self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']), + (1, executable + ': symbol nextup from unsupported version GLIBC_2.24\n' + executable + ': failed IMPORTED_SYMBOLS')) # -lutil is part of the libc6 package so a safe bet that it's installed @@ -82,7 +92,7 @@ class TestSymbolChecks(unittest.TestCase): def test_MACHO(self): source = 'test1.c' executable = 'test1' - cc = 'clang' + cc = determine_wellknown_cmd('CC', 'clang') with open(source, 'w', encoding="utf8") as f: f.write(''' @@ -96,9 +106,9 @@ class TestSymbolChecks(unittest.TestCase): ''') - self.assertEqual(call_symbol_check(cc, source, executable, ['-lexpat']), + self.assertEqual(call_symbol_check(cc, source, executable, ['-lexpat', '-Wl,-platform_version','-Wl,macos', '-Wl,11.4', '-Wl,11.4']), (1, 'libexpat.1.dylib is not in ALLOWED_LIBRARIES!\n' + - executable + ': failed DYNAMIC_LIBRARIES')) + f'{executable}: failed DYNAMIC_LIBRARIES MIN_OS SDK')) source = 'test2.c' executable = 'test2' @@ -113,13 +123,26 @@ class TestSymbolChecks(unittest.TestCase): } ''') - self.assertEqual(call_symbol_check(cc, source, executable, ['-framework', 'CoreGraphics']), - (0, '')) + self.assertEqual(call_symbol_check(cc, source, executable, ['-framework', 'CoreGraphics', '-Wl,-platform_version','-Wl,macos', '-Wl,11.4', '-Wl,11.4']), + (1, f'{executable}: failed MIN_OS SDK')) + + source = 'test3.c' + executable = 'test3' + with open(source, 'w', encoding="utf8") as f: + f.write(''' + int main() + { + return 0; + } + ''') + + self.assertEqual(call_symbol_check(cc, source, executable, ['-Wl,-platform_version','-Wl,macos', '-Wl,10.14', '-Wl,11.4']), + (1, f'{executable}: failed SDK')) def test_PE(self): source = 'test1.c' executable = 'test1.exe' - cc = 'x86_64-w64-mingw32-gcc' + cc = determine_wellknown_cmd('CC', 'x86_64-w64-mingw32-gcc') with open(source, 'w', encoding="utf8") as f: f.write(''' @@ -132,12 +155,26 @@ class TestSymbolChecks(unittest.TestCase): } ''') - self.assertEqual(call_symbol_check(cc, source, executable, ['-lpdh']), + self.assertEqual(call_symbol_check(cc, source, executable, ['-lpdh', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']), (1, 'pdh.dll is not in ALLOWED_LIBRARIES!\n' + executable + ': failed DYNAMIC_LIBRARIES')) source = 'test2.c' executable = 'test2.exe' + + with open(source, 'w', encoding="utf8") as f: + f.write(''' + int main() + { + return 0; + } + ''') + + self.assertEqual(call_symbol_check(cc, source, executable, ['-Wl,--major-subsystem-version', '-Wl,9', '-Wl,--minor-subsystem-version', '-Wl,9']), + (1, executable + ': failed SUBSYSTEM_VERSION')) + + source = 'test3.c' + executable = 'test3.exe' with open(source, 'w', encoding="utf8") as f: f.write(''' #include <windows.h> @@ -149,10 +186,9 @@ class TestSymbolChecks(unittest.TestCase): } ''') - self.assertEqual(call_symbol_check(cc, source, executable, ['-lole32']), + self.assertEqual(call_symbol_check(cc, source, executable, ['-lole32', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']), (0, '')) if __name__ == '__main__': unittest.main() - diff --git a/contrib/devtools/utils.py b/contrib/devtools/utils.py new file mode 100755 index 0000000000..68ad1c3aba --- /dev/null +++ b/contrib/devtools/utils.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +''' +Common utility functions +''' +import shutil +import sys +import os +from typing import List + + +def determine_wellknown_cmd(envvar, progname) -> List[str]: + maybe_env = os.getenv(envvar) + maybe_which = shutil.which(progname) + if maybe_env: + return maybe_env.split(' ') # Well-known vars are often meant to be word-split + elif maybe_which: + return [ maybe_which ] + else: + sys.exit(f"{progname} not found") diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index bed3531720..e6dce7a8c6 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -99,7 +99,7 @@ script: | done } - pip3 install lief==0.11.4 + pip3 install lief==0.11.5 # Faketime for depends so intermediate results are comparable export PATH_orig=${PATH} diff --git a/contrib/gitian-descriptors/gitian-osx-signer.yml b/contrib/gitian-descriptors/gitian-osx-signer.yml index 3f0c0c3332..addad0a5d2 100644 --- a/contrib/gitian-descriptors/gitian-osx-signer.yml +++ b/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -14,7 +14,7 @@ remotes: "dir": "signature" - "url": "https://github.com/achow101/signapple.git" "dir": "signapple" - "commit": "c7e73aa27a7615ac9506559173f787e2906b25eb" + "commit": "b084cbbf44d5330448ffce0c7d118f75781b64bd" files: - "bitcoin-osx-unsigned.tar.gz" script: | diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index 1d4506e3c2..a39618adb7 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -78,7 +78,7 @@ script: | done } - pip3 install lief==0.11.4 + pip3 install lief==0.11.5 # Faketime for depends so intermediate results are comparable export PATH_orig=${PATH} diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 03eba71366..ffe228a032 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -86,7 +86,7 @@ script: | done } - pip3 install lief==0.11.4 + pip3 install lief==0.11.5 # Faketime for depends so intermediate results are comparable export PATH_orig=${PATH} diff --git a/contrib/guix/guix-attest b/contrib/guix/guix-attest index 081d1c0465..c8cf73d400 100755 --- a/contrib/guix/guix-attest +++ b/contrib/guix/guix-attest @@ -99,24 +99,34 @@ fi # We should be able to find at least one output ################ -echo "Looking for build output directories in ${OUTDIR_BASE}" +echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}" shopt -s nullglob -OUTDIRS=( "${OUTDIR_BASE}"/* ) # This expands to an array of directories... +sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories... shopt -u nullglob -if (( ${#OUTDIRS[@]} )); then - echo "Found build output directories:" - for outdir in "${OUTDIRS[@]}"; do +noncodesigned_fragments=() +codesigned_fragments=() + +if (( ${#sha256sum_fragments[@]} )); then + echo "Found build output SHA256SUMS fragments:" + for outdir in "${sha256sum_fragments[@]}"; do echo " '$outdir'" + case "$outdir" in + "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part) + codesigned_fragments+=("$outdir") + ;; + *) + noncodesigned_fragments+=("$outdir") + ;; + esac done echo else - echo "ERR: Could not find any build output directories in ${OUTDIR_BASE}" + echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}" exit 1 fi - ############## ## Attest ## ############## @@ -126,82 +136,105 @@ fi # HOST: The output directory being attested # out_name() { - basename "$1" + basename "$(dirname "$1")" } -# Usage: out_sig_dir $outdir -# -# outdir: The output directory being attested -# -out_sig_dir() { - echo "$GUIX_SIGS_REPO/$VERSION/$(out_name "$1")/$signer_name" -} +shasum_already_exists() { +cat <<EOF +-- -# Accumulate a list of signature directories that already exist... -outdirs_already_attested_to=() +ERR: An ${1} file already exists for '${VERSION}' and attests + differently. You likely previously attested to a partial build (e.g. one + where you specified the HOST environment variable). -echo "Attesting to build outputs for version: '${VERSION}'" -echo "" + See the diff above for more context. -# MAIN LOGIC: Loop through each output for VERSION and attest to output in -# GUIX_SIGS_REPO as SIGNER, if attestation does not exist -for outdir in "${OUTDIRS[@]}"; do - if [ -e "${outdir}/SKIPATTEST.TAG" ]; then - echo "${outname}: SKIPPING: Output directory marked with SKIPATTEST.TAG file" - continue - fi - outname="$(out_name "$outdir")" - outsigdir="$(out_sig_dir "$outdir")" - if [ -e "$outsigdir" ]; then - echo "${outname}: SKIPPING: Signature directory already exists in the specified guix.sigs repository" - outdirs_already_attested_to+=("$outdir") - else - # Clean up incomplete sigdir if something fails (likely gpg) - trap 'rm -rf "$outsigdir"' ERR +Hint: You may wish to remove the existing attestations and their signatures by + invoking: - mkdir -p "$outsigdir" + rm '${PWD}/${1}'{,.asc} - ( - cd "$outdir" + Then try running this script again. - if [ -e inputs.SHA256SUMS ]; then - echo "${outname}: Including existent input SHA256SUMS" - cat inputs.SHA256SUMS >> "$outsigdir"/SHA256SUMS - fi +EOF +} - echo "${outname}: Hashing build outputs to produce SHA256SUMS" - files="$(find -L . -type f ! -iname '*.SHA256SUMS')" - if [ -n "$files" ]; then - cut -c3- <<< "$files" | env LC_ALL=C sort | xargs sha256sum >> "$outsigdir"/SHA256SUMS +echo "Attesting to build outputs for version: '${VERSION}'" +echo "" + +outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name" +mkdir -p "$outsigdir" +( + cd "$outsigdir" + + temp_noncodesigned="$(mktemp)" + trap 'rm -rf -- "$temp_noncodesigned"' EXIT + + if (( ${#noncodesigned_fragments[@]} )); then + cat "${noncodesigned_fragments[@]}" \ + | sort -u \ + | sort -k2 \ + > "$temp_noncodesigned" + if [ -e noncodesigned.SHA256SUMS ]; then + # The SHA256SUMS already exists, make sure it's exactly what we + # expect, error out if not + if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then + echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date." else - echo "ERR: ${outname}: No outputs found in '${outdir}'" + shasum_already_exists noncodesigned.SHA256SUMS exit 1 fi - ) - if [ -z "$NO_SIGN" ]; then - echo "${outname}: Signing SHA256SUMS to produce SHA256SUMS.asc" - gpg --detach-sign --local-user "$gpg_key_name" --armor --output "$outsigdir"/SHA256SUMS.asc "$outsigdir"/SHA256SUMS else - echo "${outname}: Not signing SHA256SUMS as \$NO_SIGN is not empty" + mv "$temp_noncodesigned" noncodesigned.SHA256SUMS fi - echo "" - - trap - ERR # Reset ERR trap + else + echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..." + exit 1 fi -done - -if (( ${#outdirs_already_attested_to[@]} )); then -# ...so that we can print them out nicely in a warning message -cat << EOF -WARN: Signature directories from '$signer_name' already exist in the specified - guix.sigs repository for the following output directories and were - skipped: + temp_codesigned="$(mktemp)" + trap 'rm -rf -- "$temp_codesigned"' EXIT + + if (( ${#codesigned_fragments[@]} )); then + # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is + # not needed if there are no $codesigned_fragments + cat "${sha256sum_fragments[@]}" \ + | sort -u \ + | sort -k2 \ + > "$temp_codesigned" + if [ -e codesigned.SHA256SUMS ]; then + # The SHA256SUMS already exists, make sure it's exactly what we + # expect, error out if not + if diff -u all.SHA256SUMS "$temp_codesigned"; then + echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date." + else + shasum_already_exists all.SHA256SUMS + exit 1 + fi + else + mv "$temp_codesigned" codesigned.SHA256SUMS + fi + else + # It is fine to have the codesigned outputs be missing (perhaps the + # detached codesigs have not been published yet), just print a log + # message instead of erroring out + echo "INFO: No codesigned outputs found for '${VERSION}', skipping..." + fi -EOF -for outdir in "${outdirs_already_attested_to[@]}"; do - echo " '${outdir}'" - echo " Corresponds to: '$(out_sig_dir "$outdir")'" + if [ -z "$NO_SIGN" ]; then + echo "Signing SHA256SUMS to produce SHA256SUMS.asc" + for i in *.SHA256SUMS; do + if [ ! -e "$i".asc ]; then + gpg --detach-sign \ + --local-user "$gpg_key_name" \ + --armor \ + --output "$i".asc "$i" + else + echo "Signature already there" + fi + done + else + echo "Not signing SHA256SUMS as \$NO_SIGN is not empty" + fi echo "" -done -fi +) diff --git a/contrib/guix/guix-verify b/contrib/guix/guix-verify index 629050956c..a6e2c4065e 100755 --- a/contrib/guix/guix-verify +++ b/contrib/guix/guix-verify @@ -56,58 +56,87 @@ cmd_usage exit 1 fi -################ -# We should be able to find at least one output -################ +############## +## Verify ## +############## OUTSIGDIR_BASE="${GUIX_SIGS_REPO}/${VERSION}" -echo "Looking for output signature directories in '${OUTSIGDIR_BASE}'" +echo "Looking for signature directories in '${OUTSIGDIR_BASE}'" +echo "" + +# Usage: verify compare_manifest current_manifest +verify() { + local compare_manifest="$1" + local current_manifest="$2" + if ! gpg --quiet --batch --verify "$current_manifest".asc "$current_manifest" 1>&2; then + echo "ERR: Failed to verify GPG signature in '${current_manifest}'" + echo "" + echo "Hint: Either the signature is invalid or the public key is missing" + echo "" + elif ! diff --report-identical "$compare_manifest" "$current_manifest" 1>&2; then + echo "ERR: The SHA256SUMS attestation in these two directories differ:" + echo " '${compare_manifest}'" + echo " '${current_manifest}'" + echo "" + else + echo "Verified: '${current_manifest}'" + echo "" + fi +} shopt -s nullglob -OUTSIGDIRS=( "$OUTSIGDIR_BASE"/* ) # This expands to an array of directories... +all_noncodesigned=( "$OUTSIGDIR_BASE"/*/noncodesigned.SHA256SUMS ) shopt -u nullglob -if (( ${#OUTSIGDIRS[@]} )); then - echo "Found output signature directories:" - for outsigdir in "${OUTSIGDIRS[@]}"; do - echo " '$outsigdir'" +echo "--------------------" +echo "" +if (( ${#all_noncodesigned[@]} )); then + compare_noncodesigned="${all_noncodesigned[0]}" + + for current_manifest in "${all_noncodesigned[@]}"; do + verify "$compare_noncodesigned" "$current_manifest" done - echo + + echo "DONE: Checking output signatures for noncodesigned.SHA256SUMS" + echo "" else - echo "ERR: Could not find any output signature directories in ${OUTSIGDIR_BASE}" - exit 1 + echo "WARN: No signature directories with noncodesigned.SHA256SUMS found" + echo "" fi +shopt -s nullglob +all_all=( "$OUTSIGDIR_BASE"/*/all.SHA256SUMS ) +shopt -u nullglob -############## -## Verify ## -############## +echo "--------------------" +echo "" +if (( ${#all_all[@]} )); then + compare_all="${all_all[0]}" -# MAIN LOGIC: Loop through each output for VERSION and check that the SHA256SUMS -# and SHA256SUMS.asc file match between signers, using the first -# available signer as the arbitrary comparison base. -for outsigdir in "${OUTSIGDIRS[@]}"; do - echo "BEGIN: Checking output signatures for $(basename "$outsigdir")" - echo "" - signer_dirs=( "$outsigdir"/* ) # This expands to an array of directories... - compare_signer_dir="${signer_dirs[0]}" # ...we just want the first one - for current_signer_dir in "${signer_dirs[@]}"; do - if ! gpg --quiet --batch --verify "$current_signer_dir"/SHA256SUMS.asc "$current_signer_dir"/SHA256SUMS; then - echo "ERR: Failed to verify GPG signature in '${current_signer_dir}/SHA256SUMS.asc'" - echo "" - echo "Hint: Either the signature is invalid or the public key is missing" - echo "" - elif ! diff --report-identical "$compare_signer_dir"/SHA256SUMS "$current_signer_dir"/SHA256SUMS; then - echo "ERR: The SHA256SUMS attestation in these two directories differ:" - echo " '${compare_signer_dir}'" - echo " '${current_signer_dir}'" - echo "" - else - echo "Verified: '${current_signer_dir}'" - echo "" - fi + for current_manifest in "${all_all[@]}"; do + verify "$compare_all" "$current_manifest" done - echo "DONE: Checking output signatures for $(basename "$outsigdir")" + + # Sanity check: there should be no entries that exist in + # noncodesigned.SHA256SUMS that doesn't exist in all.SHA256SUMS + if [[ "$(comm -23 <(sort "$compare_noncodesigned") <(sort "$compare_all") | wc -c)" -ne 0 ]]; then + echo "ERR: There are unique lines in noncodesigned.SHA256SUMS which" + echo " do not exist in all.SHA256SUMS, something went very wrong." + exit 1 + fi + + echo "DONE: Checking output signatures for all.SHA256SUMS" echo "" +else + echo "WARN: No signature directories with all.SHA256SUMS found" + echo "" +fi + +echo "====================" +echo "" +if (( ${#all_noncodesigned[@]} + ${#all_all[@]} == 0 )); then + echo "ERR: Unable to perform any verifications as no signature directories" + echo " were found" echo "" -done + exit 1 +fi diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 3073b41baf..0b96949a6b 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -214,6 +214,7 @@ make -C depends --jobs="$JOBS" HOST="$HOST" \ x86_64_linux_NM=x86_64-linux-gnu-nm \ x86_64_linux_STRIP=x86_64-linux-gnu-strip \ qt_config_opts_i686_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \ + qt_config_opts_x86_64_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \ FORCE_USE_SYSTEM_CLANG=1 @@ -230,20 +231,7 @@ if [ ! -e "$GIT_ARCHIVE" ]; then git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD fi -# tmpdir="$(mktemp -d)" -# ( -# cd "$tmpdir" -# mkdir -p inputs -# ln -sf --target-directory=inputs "$GIT_ARCHIVE" - -# mkdir -p "$OUTDIR" -# find -L inputs -type f -print0 | xargs -0 sha256sum > "${OUTDIR}/inputs.SHA256SUMS" -# ) - mkdir -p "$OUTDIR" -cat << EOF > "$OUTDIR"/inputs.SHA256SUMS -$(sha256sum "$GIT_ARCHIVE" | cut -d' ' -f1) inputs/$(basename "$GIT_ARCHIVE") -EOF ########################### # Binary Tarball Building # @@ -252,7 +240,7 @@ EOF # CONFIGFLAGS CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests --disable-fuzz-binary" case "$HOST" in - *linux*) CONFIGFLAGS+=" --enable-glibc-back-compat" ;; + *linux*) CONFIGFLAGS+=" --disable-threadlocal" ;; esac # CFLAGS @@ -272,6 +260,13 @@ case "$HOST" in *mingw*) HOST_LDFLAGS="-Wl,--no-insert-timestamp" ;; esac +# Using --no-tls-get-addr-optimize retains compatibility with glibc 2.17, by +# avoiding a PowerPC64 optimisation available in glibc 2.22 and later. +# https://sourceware.org/binutils/docs-2.35/ld/PowerPC64-ELF64.html +case "$HOST" in + *powerpc64*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,--no-tls-get-addr-optimize" ;; +esac + case "$HOST" in powerpc64-linux-*|riscv64-linux-*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,-z,noexecstack" ;; esac @@ -304,10 +299,11 @@ mkdir -p "$DISTSRC" # Build Bitcoin Core make --jobs="$JOBS" ${V:+V=1} - # Perform basic ELF security checks on a series of executables. + # Check that symbol/security checks tools are sane. + make test-security-check ${V:+V=1} + # Perform basic security checks on a series of executables. make -C src --jobs=1 check-security ${V:+V=1} - # Check that executables only contain allowed gcc, glibc and libstdc++ - # version symbols for Linux distro back-compatibility. + # Check that executables only contain allowed version symbols. make -C src --jobs=1 check-symbols ${V:+V=1} mkdir -p "$OUTDIR" @@ -450,3 +446,14 @@ mkdir -p "$DISTSRC" rm -rf "$ACTUAL_OUTDIR" mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 ) + +( + cd /outdir-base + { + echo "$GIT_ARCHIVE" + find "$ACTUAL_OUTDIR" -type f + } | xargs realpath --relative-base="$PWD" \ + | xargs sha256sum \ + | sort -k2 \ + | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part +) diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh index 1822da7ca4..f484ac5774 100755 --- a/contrib/guix/libexec/codesign.sh +++ b/contrib/guix/libexec/codesign.sh @@ -55,10 +55,6 @@ if [ ! -e "$CODESIGNATURE_GIT_ARCHIVE" ]; then fi mkdir -p "$OUTDIR" -cat << EOF > "$OUTDIR"/inputs.SHA256SUMS -$(sha256sum "$UNSIGNED_TARBALL" | cut -d' ' -f1) inputs/$(basename "$UNSIGNED_TARBALL") -$(sha256sum "$CODESIGNATURE_GIT_ARCHIVE" | cut -d' ' -f1) inputs/$(basename "$CODESIGNATURE_GIT_ARCHIVE") -EOF mkdir -p "$DISTSRC" ( @@ -103,3 +99,15 @@ mkdir -p "$DISTSRC" rm -rf "$ACTUAL_OUTDIR" mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \ || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 ) + +( + cd /outdir-base + { + echo "$UNSIGNED_TARBALL" + echo "$CODESIGNATURE_GIT_ARCHIVE" + find "$ACTUAL_OUTDIR" -type f + } | xargs realpath --relative-base="$PWD" \ + | xargs sha256sum \ + | sort -k2 \ + | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part +) diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index d2bc789b60..e71cf52533 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -22,6 +22,7 @@ (gnu packages linux) (gnu packages llvm) (gnu packages mingw) + (gnu packages moreutils) (gnu packages perl) (gnu packages pkg-config) (gnu packages python) @@ -79,6 +80,10 @@ http://www.linuxfromscratch.org/hlfs/view/development/chapter05/gcc-pass1.html" (("-rpath=") "-rpath-link=")) #t)))))))) +(define (make-binutils-with-mingw-w64-disable-flags xbinutils) + (package-with-extra-patches xbinutils + (search-our-patches "binutils-mingw-w64-disable-flags.patch"))) + (define (make-cross-toolchain target base-gcc-for-libc base-kernel-headers @@ -134,11 +139,25 @@ chain for " target " development.")) (package-with-extra-patches gcc-8 (search-our-patches "gcc-8-sort-libtool-find-output.patch"))) +;; Building glibc with stack smashing protector first landed in glibc 2.25, use +;; this function to disable for older glibcs +;; +;; From glibc 2.25 changelog: +;; +;; * Most of glibc can now be built with the stack smashing protector enabled. +;; It is recommended to build glibc with --enable-stack-protector=strong. +;; Implemented by Nick Alcock (Oracle). +(define (make-glibc-without-ssp xglibc) + (package-with-extra-configure-variable + (package-with-extra-configure-variable + xglibc "libc_cv_ssp" "no") + "libc_cv_ssp_strong" "no")) + (define* (make-bitcoin-cross-toolchain target #:key (base-gcc-for-libc gcc-7) (base-kernel-headers linux-libre-headers-5.4) - (base-libc glibc) ; glibc 2.31 + (base-libc (make-glibc-without-ssp glibc-2.24)) (base-gcc (make-gcc-rpath-link base-gcc))) "Convenience wrapper around MAKE-CROSS-TOOLCHAIN with default values desirable for building Bitcoin Core release binaries." @@ -153,7 +172,7 @@ desirable for building Bitcoin Core release binaries." (define (make-mingw-pthreads-cross-toolchain target) "Create a cross-compilation toolchain package for TARGET" - (let* ((xbinutils (cross-binutils target)) + (let* ((xbinutils (make-binutils-with-mingw-w64-disable-flags (cross-binutils target))) (pthreads-xlibc mingw-w64-x86_64-winpthreads) (pthreads-xgcc (make-gcc-with-pthreads (cross-gcc target @@ -205,7 +224,7 @@ chain for " target " development.")) (define-public lief (package (name "python-lief") - (version "0.11.4") + (version "0.11.5") (source (origin (method git-fetch) @@ -215,7 +234,7 @@ chain for " target " development.")) (file-name (git-file-name name version)) (sha256 (base32 - "0h4kcwr9z478almjqhmils8imfpflzk0r7d05g4xbkdyknn162qf")))) + "0qahjfg1n0x76ps2mbyljvws1l3qhkqvmxqbahps4qgywl2hbdkj")))) (build-system python-build-system) (native-inputs `(("cmake" ,cmake))) @@ -524,7 +543,7 @@ and endian independent.") (license license:expat))) (define-public python-signapple - (let ((commit "4ff1c1754e37042c002a3f6375c47fd931f2030b")) + (let ((commit "b084cbbf44d5330448ffce0c7d118f75781b64bd")) (package (name "python-signapple") (version (git-version "0.1" "1" commit)) @@ -532,12 +551,12 @@ and endian independent.") (origin (method git-fetch) (uri (git-reference - (url "https://github.com/dongcarl/signapple") + (url "https://github.com/achow101/signapple") (commit commit))) (file-name (git-file-name name commit)) (sha256 (base32 - "043czyzfm04rcx5xsp59vsppla3vm5g45dbp1npy2hww4066rlnh")))) + "0k7inccl2mzac3wq4asbr0kl8s4cghm8982z54kfascqg45shv01")))) (build-system python-build-system) (propagated-inputs `(("python-asn1crypto" ,python-asn1crypto) @@ -556,6 +575,28 @@ and endian independent.") inspecting signatures in Mach-O binaries.") (license license:expat)))) +(define-public glibc-2.24 + (package + (inherit glibc) + (version "2.24") + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://sourceware.org/git/glibc.git") + (commit "0d7f1ed30969886c8dde62fbf7d2c79967d4bace"))) + (file-name (git-file-name "glibc" "0d7f1ed30969886c8dde62fbf7d2c79967d4bace")) + (sha256 + (base32 + "0g5hryia5v1k0qx97qffgwzrz4lr4jw3s5kj04yllhswsxyjbic3")) + (patches (search-our-patches "glibc-ldd-x86_64.patch" + "glibc-versioned-locpath.patch" + "glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch" + "glibc-2.24-no-build-time-cxx-header-run.patch")))))) + +(define glibc-2.27/bitcoin-patched + (package-with-extra-patches glibc-2.27 + (search-our-patches "glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch"))) + (packages->manifest (append (list ;; The Basics @@ -572,6 +613,7 @@ inspecting signatures in Mach-O binaries.") patch gawk sed + moreutils ;; Compression and archiving tar bzip2 @@ -604,7 +646,10 @@ inspecting signatures in Mach-O binaries.") (make-nsis-with-sde-support nsis-x86_64) osslsigncode)) ((string-contains target "-linux-") - (list (make-bitcoin-cross-toolchain target))) + (list (cond ((string-contains target "riscv64-") + (make-bitcoin-cross-toolchain target #:base-libc glibc-2.27/bitcoin-patched)) + (else + (make-bitcoin-cross-toolchain target))))) ((string-contains target "darwin") (list clang-toolchain-10 binutils imagemagick libtiff librsvg font-tuffy cmake xorriso python-signapple)) (else '()))))) diff --git a/contrib/guix/patches/binutils-mingw-w64-disable-flags.patch b/contrib/guix/patches/binutils-mingw-w64-disable-flags.patch new file mode 100644 index 0000000000..8f88eb9dfd --- /dev/null +++ b/contrib/guix/patches/binutils-mingw-w64-disable-flags.patch @@ -0,0 +1,171 @@ +Description: Add disable opposites to the security-related flags +Author: Stephen Kitt <skitt@debian.org> + +This patch adds "no-" variants to disable the various security flags: +"no-dynamicbase", "no-nxcompat", "no-high-entropy-va", "disable-reloc-section". + +--- a/ld/emultempl/pe.em ++++ b/ld/emultempl/pe.em +@@ -259,9 +261,11 @@ + (OPTION_ENABLE_LONG_SECTION_NAMES + 1) + /* DLLCharacteristics flags. */ + #define OPTION_DYNAMIC_BASE (OPTION_DISABLE_LONG_SECTION_NAMES + 1) +-#define OPTION_FORCE_INTEGRITY (OPTION_DYNAMIC_BASE + 1) ++#define OPTION_NO_DYNAMIC_BASE (OPTION_DYNAMIC_BASE + 1) ++#define OPTION_FORCE_INTEGRITY (OPTION_NO_DYNAMIC_BASE + 1) + #define OPTION_NX_COMPAT (OPTION_FORCE_INTEGRITY + 1) +-#define OPTION_NO_ISOLATION (OPTION_NX_COMPAT + 1) ++#define OPTION_NO_NX_COMPAT (OPTION_NX_COMPAT + 1) ++#define OPTION_NO_ISOLATION (OPTION_NO_NX_COMPAT + 1) + #define OPTION_NO_SEH (OPTION_NO_ISOLATION + 1) + #define OPTION_NO_BIND (OPTION_NO_SEH + 1) + #define OPTION_WDM_DRIVER (OPTION_NO_BIND + 1) +@@ -271,6 +275,7 @@ + #define OPTION_NO_INSERT_TIMESTAMP (OPTION_INSERT_TIMESTAMP + 1) + #define OPTION_BUILD_ID (OPTION_NO_INSERT_TIMESTAMP + 1) + #define OPTION_ENABLE_RELOC_SECTION (OPTION_BUILD_ID + 1) ++#define OPTION_DISABLE_RELOC_SECTION (OPTION_ENABLE_RELOC_SECTION + 1) + + static void + gld${EMULATION_NAME}_add_options +@@ -342,8 +347,10 @@ + {"enable-long-section-names", no_argument, NULL, OPTION_ENABLE_LONG_SECTION_NAMES}, + {"disable-long-section-names", no_argument, NULL, OPTION_DISABLE_LONG_SECTION_NAMES}, + {"dynamicbase",no_argument, NULL, OPTION_DYNAMIC_BASE}, ++ {"no-dynamicbase", no_argument, NULL, OPTION_NO_DYNAMIC_BASE}, + {"forceinteg", no_argument, NULL, OPTION_FORCE_INTEGRITY}, + {"nxcompat", no_argument, NULL, OPTION_NX_COMPAT}, ++ {"no-nxcompat", no_argument, NULL, OPTION_NO_NX_COMPAT}, + {"no-isolation", no_argument, NULL, OPTION_NO_ISOLATION}, + {"no-seh", no_argument, NULL, OPTION_NO_SEH}, + {"no-bind", no_argument, NULL, OPTION_NO_BIND}, +@@ -351,6 +358,7 @@ + {"tsaware", no_argument, NULL, OPTION_TERMINAL_SERVER_AWARE}, + {"build-id", optional_argument, NULL, OPTION_BUILD_ID}, + {"enable-reloc-section", no_argument, NULL, OPTION_ENABLE_RELOC_SECTION}, ++ {"disable-reloc-section", no_argument, NULL, OPTION_DISABLE_RELOC_SECTION}, + {NULL, no_argument, NULL, 0} + }; + +@@ -485,9 +494,12 @@ + in object files\n")); + fprintf (file, _(" --dynamicbase Image base address may be relocated using\n\ + address space layout randomization (ASLR)\n")); ++ fprintf (file, _(" --no-dynamicbase Image base address may not be relocated\n")); + fprintf (file, _(" --enable-reloc-section Create the base relocation table\n")); ++ fprintf (file, _(" --disable-reloc-section Disable the base relocation table\n")); + fprintf (file, _(" --forceinteg Code integrity checks are enforced\n")); + fprintf (file, _(" --nxcompat Image is compatible with data execution prevention\n")); ++ fprintf (file, _(" --no-nxcompat Image is not compatible with data execution prevention\n")); + fprintf (file, _(" --no-isolation Image understands isolation but do not isolate the image\n")); + fprintf (file, _(" --no-seh Image does not use SEH. No SE handler may\n\ + be called in this image\n")); +@@ -862,12 +874,21 @@ + case OPTION_ENABLE_RELOC_SECTION: + pe_dll_enable_reloc_section = 1; + break; ++ case OPTION_DISABLE_RELOC_SECTION: ++ pe_dll_enable_reloc_section = 0; ++ /* fall through */ ++ case OPTION_NO_DYNAMIC_BASE: ++ pe_dll_characteristics &= ~IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE; ++ break; + case OPTION_FORCE_INTEGRITY: + pe_dll_characteristics |= IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY; + break; + case OPTION_NX_COMPAT: + pe_dll_characteristics |= IMAGE_DLL_CHARACTERISTICS_NX_COMPAT; + break; ++ case OPTION_NO_NX_COMPAT: ++ pe_dll_characteristics &= ~IMAGE_DLL_CHARACTERISTICS_NX_COMPAT; ++ break; + case OPTION_NO_ISOLATION: + pe_dll_characteristics |= IMAGE_DLLCHARACTERISTICS_NO_ISOLATION; + break; +--- a/ld/emultempl/pep.em ++++ b/ld/emultempl/pep.em +@@ -237,9 +240,12 @@ + OPTION_ENABLE_LONG_SECTION_NAMES, + OPTION_DISABLE_LONG_SECTION_NAMES, + OPTION_HIGH_ENTROPY_VA, ++ OPTION_NO_HIGH_ENTROPY_VA, + OPTION_DYNAMIC_BASE, ++ OPTION_NO_DYNAMIC_BASE, + OPTION_FORCE_INTEGRITY, + OPTION_NX_COMPAT, ++ OPTION_NO_NX_COMPAT, + OPTION_NO_ISOLATION, + OPTION_NO_SEH, + OPTION_NO_BIND, +@@ -248,7 +254,8 @@ + OPTION_NO_INSERT_TIMESTAMP, + OPTION_TERMINAL_SERVER_AWARE, + OPTION_BUILD_ID, +- OPTION_ENABLE_RELOC_SECTION ++ OPTION_ENABLE_RELOC_SECTION, ++ OPTION_DISABLE_RELOC_SECTION + }; + + static void +@@ -315,9 +322,12 @@ + {"enable-long-section-names", no_argument, NULL, OPTION_ENABLE_LONG_SECTION_NAMES}, + {"disable-long-section-names", no_argument, NULL, OPTION_DISABLE_LONG_SECTION_NAMES}, + {"high-entropy-va", no_argument, NULL, OPTION_HIGH_ENTROPY_VA}, ++ {"no-high-entropy-va", no_argument, NULL, OPTION_NO_HIGH_ENTROPY_VA}, + {"dynamicbase",no_argument, NULL, OPTION_DYNAMIC_BASE}, ++ {"no-dynamicbase", no_argument, NULL, OPTION_NO_DYNAMIC_BASE}, + {"forceinteg", no_argument, NULL, OPTION_FORCE_INTEGRITY}, + {"nxcompat", no_argument, NULL, OPTION_NX_COMPAT}, ++ {"no-nxcompat", no_argument, NULL, OPTION_NO_NX_COMPAT}, + {"no-isolation", no_argument, NULL, OPTION_NO_ISOLATION}, + {"no-seh", no_argument, NULL, OPTION_NO_SEH}, + {"no-bind", no_argument, NULL, OPTION_NO_BIND}, +@@ -327,6 +337,7 @@ + {"no-insert-timestamp", no_argument, NULL, OPTION_NO_INSERT_TIMESTAMP}, + {"build-id", optional_argument, NULL, OPTION_BUILD_ID}, + {"enable-reloc-section", no_argument, NULL, OPTION_ENABLE_RELOC_SECTION}, ++ {"disable-reloc-section", no_argument, NULL, OPTION_DISABLE_RELOC_SECTION}, + {NULL, no_argument, NULL, 0} + }; + +@@ -448,11 +461,15 @@ + in object files\n")); + fprintf (file, _(" --high-entropy-va Image is compatible with 64-bit address space\n\ + layout randomization (ASLR)\n")); ++ fprintf (file, _(" --no-high-entropy-va Image is not compatible with 64-bit ASLR\n")); + fprintf (file, _(" --dynamicbase Image base address may be relocated using\n\ + address space layout randomization (ASLR)\n")); ++ fprintf (file, _(" --no-dynamicbase Image base address may not be relocated\n")); + fprintf (file, _(" --enable-reloc-section Create the base relocation table\n")); ++ fprintf (file, _(" --disable-reloc-section Disable the base relocation table\n")); + fprintf (file, _(" --forceinteg Code integrity checks are enforced\n")); + fprintf (file, _(" --nxcompat Image is compatible with data execution prevention\n")); ++ fprintf (file, _(" --no-nxcompat Image is not compatible with data execution prevention\n")); + fprintf (file, _(" --no-isolation Image understands isolation but do not isolate the image\n")); + fprintf (file, _(" --no-seh Image does not use SEH; no SE handler may\n\ + be called in this image\n")); +@@ -809,12 +826,24 @@ + case OPTION_ENABLE_RELOC_SECTION: + pep_dll_enable_reloc_section = 1; + break; ++ case OPTION_DISABLE_RELOC_SECTION: ++ pep_dll_enable_reloc_section = 0; ++ /* fall through */ ++ case OPTION_NO_DYNAMIC_BASE: ++ pe_dll_characteristics &= ~IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE; ++ /* fall through */ ++ case OPTION_NO_HIGH_ENTROPY_VA: ++ pe_dll_characteristics &= ~IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA; ++ break; + case OPTION_FORCE_INTEGRITY: + pe_dll_characteristics |= IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY; + break; + case OPTION_NX_COMPAT: + pe_dll_characteristics |= IMAGE_DLL_CHARACTERISTICS_NX_COMPAT; + break; ++ case OPTION_NO_NX_COMPAT: ++ pe_dll_characteristics &= ~IMAGE_DLL_CHARACTERISTICS_NX_COMPAT; ++ break; + case OPTION_NO_ISOLATION: + pe_dll_characteristics |= IMAGE_DLLCHARACTERISTICS_NO_ISOLATION; + break; diff --git a/contrib/guix/patches/gcc-8-sort-libtool-find-output.patch b/contrib/guix/patches/gcc-8-sort-libtool-find-output.patch index 1dfe3ba132..f327c464f3 100644 --- a/contrib/guix/patches/gcc-8-sort-libtool-find-output.patch +++ b/contrib/guix/patches/gcc-8-sort-libtool-find-output.patch @@ -1,3 +1,11 @@ +guix: repro: Sort find output in libtool for gcc-8 + +Otherwise the resulting .a static libraries (e.g. libstdc++.a) will not +be reproducible and end up making the Bitcoin binaries non-reproducible +as well. + +See: https://reproducible-builds.org/docs/archives/#gnu-libtool + diff --git a/gcc/configure b/gcc/configure index 97ba7d7d69c..e37a96f0c0c 100755 --- a/gcc/configure diff --git a/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch b/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch new file mode 100644 index 0000000000..5c4d0c6ebe --- /dev/null +++ b/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch @@ -0,0 +1,62 @@ +https://sourceware.org/git/?p=glibc.git;a=commit;h=a68ba2f3cd3cbe32c1f31e13c20ed13487727b32 + +commit 6b02af31e9a721bb15a11380cd22d53b621711f8 +Author: Szabolcs Nagy <szabolcs.nagy@arm.com> +Date: Wed Oct 18 17:26:23 2017 +0100 + + [AARCH64] Rewrite elf_machine_load_address using _DYNAMIC symbol + + This patch rewrites aarch64 elf_machine_load_address to use special _DYNAMIC + symbol instead of _dl_start. + + The static address of _DYNAMIC symbol is stored in the first GOT entry. + Here is the change which makes this solution work (part of binutils 2.24): + https://sourceware.org/ml/binutils/2013-06/msg00248.html + + i386, x86_64 targets use the same method to do this as well. + + The original implementation relies on a trick that R_AARCH64_ABS32 relocation + being resolved at link time and the static address fits in the 32bits. + However, in LP64, normally, the address is defined to be 64 bit. + + Here is the C version one which should be portable in all cases. + + * sysdeps/aarch64/dl-machine.h (elf_machine_load_address): Use + _DYNAMIC symbol to calculate load address. + +diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h +index e86d8b5b63..5a5b8a5de5 100644 +--- a/sysdeps/aarch64/dl-machine.h ++++ b/sysdeps/aarch64/dl-machine.h +@@ -49,26 +49,11 @@ elf_machine_load_address (void) + /* To figure out the load address we use the definition that for any symbol: + dynamic_addr(symbol) = static_addr(symbol) + load_addr + +- The choice of symbol is arbitrary. The static address we obtain +- by constructing a non GOT reference to the symbol, the dynamic +- address of the symbol we compute using adrp/add to compute the +- symbol's address relative to the PC. +- This depends on 32bit relocations being resolved at link time +- and that the static address fits in the 32bits. */ +- +- ElfW(Addr) static_addr; +- ElfW(Addr) dynamic_addr; +- +- asm (" \n" +-" adrp %1, _dl_start; \n" +-" add %1, %1, #:lo12:_dl_start \n" +-" ldr %w0, 1f \n" +-" b 2f \n" +-"1: \n" +-" .word _dl_start \n" +-"2: \n" +- : "=r" (static_addr), "=r" (dynamic_addr)); +- return dynamic_addr - static_addr; ++ _DYNAMIC sysmbol is used here as its link-time address stored in ++ the special unrelocated first GOT entry. */ ++ ++ extern ElfW(Dyn) _DYNAMIC[] attribute_hidden; ++ return (ElfW(Addr)) &_DYNAMIC - elf_machine_dynamic (); + } + + /* Set up the loaded object described by L so its unrelocated PLT diff --git a/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch b/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch new file mode 100644 index 0000000000..11fe7fdc99 --- /dev/null +++ b/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch @@ -0,0 +1,100 @@ +https://sourceware.org/git/?p=glibc.git;a=commit;h=fc3e1337be1c6935ab58bd13520f97a535cf70cc + +commit dc23a45db566095e83ff0b7a57afc87fb5ca89a1 +Author: Florian Weimer <fweimer@redhat.com> +Date: Wed Sep 21 10:45:32 2016 +0200 + + Avoid running $(CXX) during build to obtain header file paths + + This reduces the build time somewhat and is particularly noticeable + during rebuilds with few code changes. + +diff --git a/Makerules b/Makerules +index 7e4077ee50..c338850de5 100644 +--- a/Makerules ++++ b/Makerules +@@ -121,14 +121,10 @@ ifneq (,$(CXX)) + # will be used instead of /usr/include/stdlib.h and /usr/include/math.h. + before-compile := $(common-objpfx)cstdlib $(common-objpfx)cmath \ + $(before-compile) +-cstdlib=$(shell echo "\#include <cstdlib>" | $(CXX) -M -MP -x c++ - \ +- | sed -n "/cstdlib:/{s/:$$//;p}") +-$(common-objpfx)cstdlib: $(cstdlib) ++$(common-objpfx)cstdlib: $(c++-cstdlib-header) + $(INSTALL_DATA) $< $@T + $(move-if-change) $@T $@ +-cmath=$(shell echo "\#include <cmath>" | $(CXX) -M -MP -x c++ - \ +- | sed -n "/cmath:/{s/:$$//;p}") +-$(common-objpfx)cmath: $(cmath) ++$(common-objpfx)cmath: $(c++-cmath-header) + $(INSTALL_DATA) $< $@T + $(move-if-change) $@T $@ + endif +diff --git a/config.make.in b/config.make.in +index 95c6f36876..04a8b3ed7f 100644 +--- a/config.make.in ++++ b/config.make.in +@@ -45,6 +45,8 @@ defines = @DEFINES@ + sysheaders = @sysheaders@ + sysincludes = @SYSINCLUDES@ + c++-sysincludes = @CXX_SYSINCLUDES@ ++c++-cstdlib-header = @CXX_CSTDLIB_HEADER@ ++c++-cmath-header = @CXX_CMATH_HEADER@ + all-warnings = @all_warnings@ + enable-werror = @enable_werror@ + +diff --git a/configure b/configure +index 17625e1041..6ff252744b 100755 +--- a/configure ++++ b/configure +@@ -635,6 +635,8 @@ BISON + INSTALL_INFO + PERL + BASH_SHELL ++CXX_CMATH_HEADER ++CXX_CSTDLIB_HEADER + CXX_SYSINCLUDES + SYSINCLUDES + AUTOCONF +@@ -5054,6 +5056,18 @@ fi + + + ++# Obtain some C++ header file paths. This is used to make a local ++# copy of those headers in Makerules. ++if test -n "$CXX"; then ++ find_cxx_header () { ++ echo "#include <$1>" | $CXX -M -MP -x c++ - | sed -n "/$1:/{s/:\$//;p}" ++ } ++ CXX_CSTDLIB_HEADER="$(find_cxx_header cstdlib)" ++ CXX_CMATH_HEADER="$(find_cxx_header cmath)" ++fi ++ ++ ++ + # Test if LD_LIBRARY_PATH contains the notation for the current directory + # since this would lead to problems installing/building glibc. + # LD_LIBRARY_PATH contains the current directory if one of the following +diff --git a/configure.ac b/configure.ac +index 33bcd62180..9938ab0dc2 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1039,6 +1039,18 @@ fi + AC_SUBST(SYSINCLUDES) + AC_SUBST(CXX_SYSINCLUDES) + ++# Obtain some C++ header file paths. This is used to make a local ++# copy of those headers in Makerules. ++if test -n "$CXX"; then ++ find_cxx_header () { ++ echo "#include <$1>" | $CXX -M -MP -x c++ - | sed -n "/$1:/{s/:\$//;p}" ++ } ++ CXX_CSTDLIB_HEADER="$(find_cxx_header cstdlib)" ++ CXX_CMATH_HEADER="$(find_cxx_header cmath)" ++fi ++AC_SUBST(CXX_CSTDLIB_HEADER) ++AC_SUBST(CXX_CMATH_HEADER) ++ + # Test if LD_LIBRARY_PATH contains the notation for the current directory + # since this would lead to problems installing/building glibc. + # LD_LIBRARY_PATH contains the current directory if one of the following diff --git a/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch b/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch new file mode 100644 index 0000000000..d6217157ee --- /dev/null +++ b/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch @@ -0,0 +1,72 @@ +https://sourceware.org/git/?p=glibc.git;a=commit;h=0b9c84906f653978fb8768c7ebd0ee14a47e662e + +From 562c52cc81a4e456a62e6455feb32732049e9070 Mon Sep 17 00:00:00 2001 +From: "H.J. Lu" <hjl.tools@gmail.com> +Date: Mon, 31 Dec 2018 09:26:42 -0800 +Subject: [PATCH] riscv: Use __has_include__ to include <asm/syscalls.h> [BZ + #24022] + +<asm/syscalls.h> has been removed by + +commit 27f8899d6002e11a6e2d995e29b8deab5aa9cc25 +Author: David Abdurachmanov <david.abdurachmanov@gmail.com> +Date: Thu Nov 8 20:02:39 2018 +0100 + + riscv: add asm/unistd.h UAPI header + + Marcin Juszkiewicz reported issues while generating syscall table for riscv + using 4.20-rc1. The patch refactors our unistd.h files to match some other + architectures. + + - Add asm/unistd.h UAPI header, which has __ARCH_WANT_NEW_STAT only for 64-bit + - Remove asm/syscalls.h UAPI header and merge to asm/unistd.h + - Adjust kernel asm/unistd.h + + So now asm/unistd.h UAPI header should show all syscalls for riscv. + +<asm/syscalls.h> may be restored by + +Subject: [PATCH] riscv: restore asm/syscalls.h UAPI header +Date: Tue, 11 Dec 2018 09:09:35 +0100 + +UAPI header asm/syscalls.h was merged into UAPI asm/unistd.h header, +which did resolve issue with missing syscalls macros resulting in +glibc (2.28) build failure. It also broke glibc in a different way: +asm/syscalls.h is being used by glibc. I noticed this while doing +Fedora 30/Rawhide mass rebuild. + +The patch returns asm/syscalls.h header and incl. it into asm/unistd.h. +I plan to send a patch to glibc to use asm/unistd.h instead of +asm/syscalls.h + +In the meantime, we use __has_include__, which was added to GCC 5, to +check if <asm/syscalls.h> exists before including it. Tested with +build-many-glibcs.py for riscv against kernel 4.19.12 and 4.20-rc7. + + [BZ #24022] + * sysdeps/unix/sysv/linux/riscv/flush-icache.c: Check if + <asm/syscalls.h> exists with __has_include__ before including it. +--- + sysdeps/unix/sysv/linux/riscv/flush-icache.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/sysdeps/unix/sysv/linux/riscv/flush-icache.c b/sysdeps/unix/sysv/linux/riscv/flush-icache.c +index d612ef4c6c..0b2042620b 100644 +--- a/sysdeps/unix/sysv/linux/riscv/flush-icache.c ++++ b/sysdeps/unix/sysv/linux/riscv/flush-icache.c +@@ -21,7 +21,11 @@ + #include <stdlib.h> + #include <atomic.h> + #include <sys/cachectl.h> +-#include <asm/syscalls.h> ++#if __has_include__ (<asm/syscalls.h>) ++# include <asm/syscalls.h> ++#else ++# include <asm/unistd.h> ++#endif + + typedef int (*func_type) (void *, void *, unsigned long int); + +-- +2.31.1 + diff --git a/contrib/guix/patches/glibc-ldd-x86_64.patch b/contrib/guix/patches/glibc-ldd-x86_64.patch new file mode 100644 index 0000000000..b1b6d5a548 --- /dev/null +++ b/contrib/guix/patches/glibc-ldd-x86_64.patch @@ -0,0 +1,10 @@ +By default, 'RTDLLIST' in 'ldd' refers to 'lib64/ld-linux-x86-64.so', whereas +it's in 'lib/' for us. This patch fixes that. + +--- glibc-2.17/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed 2012-12-25 04:02:13.000000000 +0100 ++++ glibc-2.17/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed 2013-09-15 23:08:03.000000000 +0200 +@@ -1,3 +1,3 @@ + /LD_TRACE_LOADED_OBJECTS=1/a\ + add_env="$add_env LD_LIBRARY_VERSION=\\$verify_out" +-s_^\(RTLDLIST=\)\(.*lib\)\(\|64\|x32\)\(/[^/]*\)\(-x86-64\|-x32\)\(\.so\.[0-9.]*\)[ ]*$_\1"\2\4\6 \264\4-x86-64\6 \2x32\4-x32\6"_ ++s_^\(RTLDLIST=\)\(.*lib\)\(\|64\|x32\)\(/[^/]*\)\(-x86-64\|-x32\)\(\.so\.[0-9.]*\)[ ]*$_\1"\2\4\6 \2\4-x86-64\6 \2x32\4-x32\6"_ diff --git a/contrib/guix/patches/glibc-versioned-locpath.patch b/contrib/guix/patches/glibc-versioned-locpath.patch new file mode 100644 index 0000000000..bc7652127f --- /dev/null +++ b/contrib/guix/patches/glibc-versioned-locpath.patch @@ -0,0 +1,240 @@ +The format of locale data can be incompatible between libc versions, and +loading incompatible data can lead to 'setlocale' returning EINVAL at best +or triggering an assertion failure at worst. See +https://lists.gnu.org/archive/html/guix-devel/2015-09/msg00717.html +for background information. + +To address that, this patch changes libc to honor a new 'GUIX_LOCPATH' +variable, and to look for locale data in version-specific sub-directories of +that variable. So, if GUIX_LOCPATH=/foo:/bar, locale data is searched for in +/foo/X.Y and /bar/X.Y, where X.Y is the libc version number. + +That way, a single 'GUIX_LOCPATH' setting can work even if different libc +versions coexist on the system. + +--- a/locale/newlocale.c ++++ b/locale/newlocale.c +@@ -30,6 +30,7 @@ + /* Lock for protecting global data. */ + __libc_rwlock_define (extern , __libc_setlocale_lock attribute_hidden) + ++extern error_t compute_locale_search_path (char **, size_t *); + + /* Use this when we come along an error. */ + #define ERROR_RETURN \ +@@ -48,7 +49,6 @@ __newlocale (int category_mask, const char *locale, __locale_t base) + __locale_t result_ptr; + char *locale_path; + size_t locale_path_len; +- const char *locpath_var; + int cnt; + size_t names_len; + +@@ -102,17 +102,8 @@ __newlocale (int category_mask, const char *locale, __locale_t base) + locale_path = NULL; + locale_path_len = 0; + +- locpath_var = getenv ("LOCPATH"); +- if (locpath_var != NULL && locpath_var[0] != '\0') +- { +- if (__argz_create_sep (locpath_var, ':', +- &locale_path, &locale_path_len) != 0) +- return NULL; +- +- if (__argz_add_sep (&locale_path, &locale_path_len, +- _nl_default_locale_path, ':') != 0) +- return NULL; +- } ++ if (compute_locale_search_path (&locale_path, &locale_path_len) != 0) ++ return NULL; + + /* Get the names for the locales we are interested in. We either + allow a composite name or a single name. */ +diff --git a/locale/setlocale.c b/locale/setlocale.c +index ead030d..0c0e314 100644 +--- a/locale/setlocale.c ++++ b/locale/setlocale.c +@@ -215,12 +215,65 @@ setdata (int category, struct __locale_data *data) + } + } + ++/* Return in *LOCALE_PATH and *LOCALE_PATH_LEN the locale data search path as ++ a colon-separated list. Return ENOMEN on error, zero otherwise. */ ++error_t ++compute_locale_search_path (char **locale_path, size_t *locale_path_len) ++{ ++ char* guix_locpath_var = getenv ("GUIX_LOCPATH"); ++ char *locpath_var = getenv ("LOCPATH"); ++ ++ if (guix_locpath_var != NULL && guix_locpath_var[0] != '\0') ++ { ++ /* Entries in 'GUIX_LOCPATH' take precedence over 'LOCPATH'. These ++ entries are systematically prefixed with "/X.Y" where "X.Y" is the ++ libc version. */ ++ if (__argz_create_sep (guix_locpath_var, ':', ++ locale_path, locale_path_len) != 0 ++ || __argz_suffix_entries (locale_path, locale_path_len, ++ "/" VERSION) != 0) ++ goto bail_out; ++ } ++ ++ if (locpath_var != NULL && locpath_var[0] != '\0') ++ { ++ char *reg_locale_path = NULL; ++ size_t reg_locale_path_len = 0; ++ ++ if (__argz_create_sep (locpath_var, ':', ++ ®_locale_path, ®_locale_path_len) != 0) ++ goto bail_out; ++ ++ if (__argz_append (locale_path, locale_path_len, ++ reg_locale_path, reg_locale_path_len) != 0) ++ goto bail_out; ++ ++ free (reg_locale_path); ++ } ++ ++ if (*locale_path != NULL) ++ { ++ /* Append the system default locale directory. */ ++ if (__argz_add_sep (locale_path, locale_path_len, ++ _nl_default_locale_path, ':') != 0) ++ goto bail_out; ++ } ++ ++ return 0; ++ ++ bail_out: ++ free (*locale_path); ++ *locale_path = NULL; ++ *locale_path_len = 0; ++ ++ return ENOMEM; ++} ++ + char * + setlocale (int category, const char *locale) + { + char *locale_path; + size_t locale_path_len; +- const char *locpath_var; + char *composite; + + /* Sanity check for CATEGORY argument. */ +@@ -251,17 +304,10 @@ setlocale (int category, const char *locale) + locale_path = NULL; + locale_path_len = 0; + +- locpath_var = getenv ("LOCPATH"); +- if (locpath_var != NULL && locpath_var[0] != '\0') ++ if (compute_locale_search_path (&locale_path, &locale_path_len) != 0) + { +- if (__argz_create_sep (locpath_var, ':', +- &locale_path, &locale_path_len) != 0 +- || __argz_add_sep (&locale_path, &locale_path_len, +- _nl_default_locale_path, ':') != 0) +- { +- __libc_rwlock_unlock (__libc_setlocale_lock); +- return NULL; +- } ++ __libc_rwlock_unlock (__libc_setlocale_lock); ++ return NULL; + } + + if (category == LC_ALL) +diff --git a/string/Makefile b/string/Makefile +index 8424a61..f925503 100644 +--- a/string/Makefile ++++ b/string/Makefile +@@ -38,7 +38,7 @@ routines := strcat strchr strcmp strcoll strcpy strcspn \ + swab strfry memfrob memmem rawmemchr strchrnul \ + $(addprefix argz-,append count create ctsep next \ + delete extract insert stringify \ +- addsep replace) \ ++ addsep replace suffix) \ + envz basename \ + strcoll_l strxfrm_l string-inlines memrchr \ + xpg-strerror strerror_l +diff --git a/string/argz-suffix.c b/string/argz-suffix.c +new file mode 100644 +index 0000000..505b0f2 +--- /dev/null ++++ b/string/argz-suffix.c +@@ -0,0 +1,56 @@ ++/* Copyright (C) 2015 Free Software Foundation, Inc. ++ This file is part of the GNU C Library. ++ Contributed by Ludovic Courtès <ludo@gnu.org>. ++ ++ The GNU C Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Lesser General Public ++ License as published by the Free Software Foundation; either ++ version 2.1 of the License, or (at your option) any later version. ++ ++ The GNU C Library is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ Lesser General Public License for more details. ++ ++ You should have received a copy of the GNU Lesser General Public ++ License along with the GNU C Library; if not, see ++ <http://www.gnu.org/licenses/>. */ ++ ++#include <argz.h> ++#include <errno.h> ++#include <stdlib.h> ++#include <string.h> ++ ++ ++error_t ++__argz_suffix_entries (char **argz, size_t *argz_len, const char *suffix) ++ ++{ ++ size_t suffix_len = strlen (suffix); ++ size_t count = __argz_count (*argz, *argz_len); ++ size_t new_argz_len = *argz_len + count * suffix_len; ++ char *new_argz = malloc (new_argz_len); ++ ++ if (new_argz) ++ { ++ char *p = new_argz, *entry; ++ ++ for (entry = *argz; ++ entry != NULL; ++ entry = argz_next (*argz, *argz_len, entry)) ++ { ++ p = stpcpy (p, entry); ++ p = stpcpy (p, suffix); ++ p++; ++ } ++ ++ free (*argz); ++ *argz = new_argz; ++ *argz_len = new_argz_len; ++ ++ return 0; ++ } ++ else ++ return ENOMEM; ++} ++weak_alias (__argz_suffix_entries, argz_suffix_entries) +diff --git a/string/argz.h b/string/argz.h +index bb62a31..d276a35 100644 +--- a/string/argz.h ++++ b/string/argz.h +@@ -134,6 +134,16 @@ extern error_t argz_replace (char **__restrict __argz, + const char *__restrict __str, + const char *__restrict __with, + unsigned int *__restrict __replace_count); ++ ++/* Suffix each entry of ARGZ & ARGZ_LEN with SUFFIX. Return 0 on success, ++ and ENOMEN if memory cannot be allocated. */ ++extern error_t __argz_suffix_entries (char **__restrict __argz, ++ size_t *__restrict __argz_len, ++ const char *__restrict __suffix); ++extern error_t argz_suffix_entries (char **__restrict __argz, ++ size_t *__restrict __argz_len, ++ const char *__restrict __suffix); ++ + + /* Returns the next entry in ARGZ & ARGZ_LEN after ENTRY, or NULL if there + are no more. If entry is NULL, then the first entry is returned. This diff --git a/contrib/guix/patches/nsis-SConstruct-sde-support.patch b/contrib/guix/patches/nsis-SConstruct-sde-support.patch index 5edf1b7c8e..f58406a7a0 100644 --- a/contrib/guix/patches/nsis-SConstruct-sde-support.patch +++ b/contrib/guix/patches/nsis-SConstruct-sde-support.patch @@ -1,3 +1,6 @@ +https://github.com/kichik/nsis/pull/13 +https://sourceforge.net/p/nsis/code/7248/ + diff --git a/SConstruct b/SConstruct index e8252c9..41786f2 100755 --- a/SConstruct diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index a62150a930..f7bfb6eb0a 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -678,11 +678,11 @@ vi5bnbxkleeqi6hfccjochnn65lcxlfqs4uwgmhudph554zibiusqnad.onion:8333 xqt25cobm5zqucac3634zfght72he6u3eagfyej5ellbhcdgos7t2had.onion:8333 # manually added 2021-05 for minimal i2p bootstrap support -72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p:8333 -c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:8333 -gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p:8333 -h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:8333 -hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:8333 -pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p:8333 -wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:8333 -zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:8333 +72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p:0 +c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:0 +gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p:0 +h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0 +hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:0 +pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p:0 +wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:0 +zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:0 diff --git a/contrib/signet/README.md b/contrib/signet/README.md index 71dc2f9638..706b296c54 100644 --- a/contrib/signet/README.md +++ b/contrib/signet/README.md @@ -21,27 +21,28 @@ accept one claim per day. See `--password` above. miner ===== -To mine the first block in your custom chain, you can run: +You will first need to pick a difficulty target. Since signet chains are primarily protected by a signature rather than proof of work, there is no need to spend as much energy as possible mining, however you may wish to choose to spend more time than the absolute minimum. The calibrate subcommand can be used to pick a target appropriate for your hardware, eg: cd src/ - CLI="./bitcoin-cli -conf=mysignet.conf" - MINER="..contrib/signet/miner" + MINER="../contrib/signet/miner" GRIND="./bitcoin-util grind" - ADDR=$($CLI -signet getnewaddress) - $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --set-block-time=-1 - -This will mine a block with the current timestamp. If you want to backdate the chain, you can give a different timestamp to --set-block-time. - -You will then need to pick a difficulty target. Since signet chains are primarily protected by a signature rather than proof of work, there is no need to spend as much energy as possible mining, however you may wish to choose to spend more time than the absolute minimum. The calibrate subcommand can be used to pick a target, eg: - $MINER calibrate --grind-cmd="$GRIND" nbits=1e00f403 for 25s average mining time It defaults to estimating an nbits value resulting in 25s average time to find a block, but the --seconds parameter can be used to pick a different target, or the --nbits parameter can be used to estimate how long it will take for a given difficulty. -Using the --ongoing parameter will then cause the signet miner to create blocks indefinitely. It will pick the time between blocks so that difficulty is adjusted to match the provided --nbits value. +To mine the first block in your custom chain, you can run: - $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=1e00f403 --ongoing + CLI="./bitcoin-cli -conf=mysignet.conf" + ADDR=$($CLI -signet getnewaddress) + NBITS=1e00f403 + $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS + +This will mine a single block with a backdated timestamp designed to allow 100 blocks to be mined as quickly as possible, so that it is possible to do transactions. + +Adding the --ongoing parameter will then cause the signet miner to create blocks indefinitely. It will pick the time between blocks so that difficulty is adjusted to match the provided --nbits value. + + $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=$NBITS --ongoing Other options ------------- @@ -50,9 +51,11 @@ The --debug and --quiet options are available to control how noisy the signet mi Instead of specifying --ongoing, you can specify --max-blocks=N to mine N blocks and stop. -Instead of using a single address, a ranged descriptor may be provided instead (via the --descriptor parameter), with the reward for the block at height H being sent to the H'th address generated from the descriptor. +The --set-block-time option is available to manually move timestamps forward or backward (subject to the rules that blocktime must be greater than mediantime, and dates can't be more than two hours in the future). It can only be used when mining a single block (ie, not when using --ongoing or --max-blocks greater than 1). + +Instead of using a single address, a ranged descriptor may be provided via the --descriptor parameter, with the reward for the block at height H being sent to the H'th address generated from the descriptor. -Instead of calculating a specific nbits value, --min-nbits can be specified instead, in which case the mininmum signet difficulty will be targeted. +Instead of calculating a specific nbits value, --min-nbits can be specified instead, in which case the minimum signet difficulty will be targeted. Signet's minimum difficulty corresponds to --nbits=1e0377ae. By default, the signet miner mines blocks at fixed intervals with minimal variation. If you want blocks to appear more randomly, as they do in mainnet, specify the --poisson option. @@ -76,5 +79,5 @@ These steps can instead be done explicitly: $MINER --cli="$CLI" solvepsbt --grind-cmd="$GRIND" | $CLI -signet -stdin submitblock -This is intended to allow you to replace part of the pipeline for further experimentation, if desired. +This is intended to allow you to replace part of the pipeline for further experimentation (eg, to sign the block with a hardware wallet). diff --git a/contrib/signet/miner b/contrib/signet/miner index a3fba49d0e..78e1fa5ecd 100755 --- a/contrib/signet/miner +++ b/contrib/signet/miner @@ -23,7 +23,7 @@ PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNE sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL) from test_framework.blocktools import WITNESS_COMMITMENT_HEADER, script_BIP34_coinbase_height # noqa: E402 -from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, ToHex, deser_string, hash256, ser_compact_size, ser_string, ser_uint256, uint256_from_str # noqa: E402 +from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_hex, deser_string, hash256, ser_compact_size, ser_string, ser_uint256, tx_from_hex, uint256_from_str # noqa: E402 from test_framework.script import CScriptOp # noqa: E402 logging.basicConfig( @@ -37,7 +37,7 @@ RE_MULTIMINER = re.compile("^(\d+)(-(\d+))?/(\d+)$") # #### some helpers that could go into test_framework -# like FromHex, but without the hex part +# like from_hex, but without the hex part def FromBinary(cls, stream): """deserialize a binary stream (or bytes object) into an object""" # handle bytes object by turning it into a stream @@ -195,7 +195,7 @@ def finish_block(block, signet_solution, grind_cmd): headhex = CBlockHeader.serialize(block).hex() cmd = grind_cmd.split(" ") + [headhex] newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip() - newhead = FromHex(CBlockHeader(), newheadhex.decode('utf8')) + newhead = from_hex(CBlockHeader(), newheadhex.decode('utf8')) block.nNonce = newhead.nNonce block.rehash() return block @@ -216,7 +216,7 @@ def generate_psbt(tmpl, reward_spk, *, blocktime=None): block.nTime = tmpl["mintime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 - block.vtx = [cbtx] + [FromHex(CTransaction(), t["data"]) for t in tmpl["transactions"]] + block.vtx = [cbtx] + [tx_from_hex(t["data"]) for t in tmpl["transactions"]] witnonce = 0 witroot = block.calc_witness_merkle_root() @@ -274,7 +274,7 @@ def do_genpsbt(args): def do_solvepsbt(args): block, signet_solution = do_decode_psbt(sys.stdin.read()) block = finish_block(block, signet_solution, args.grind_cmd) - print(ToHex(block)) + print(block.serialize().hex()) def nbits_to_target(nbits): shift = (nbits >> 24) & 0xff @@ -428,10 +428,13 @@ def do_generate(args): action_time = now is_mine = True elif bestheader["height"] == 0: - logging.error("When mining first block in a new signet, must specify --set-block-time") - return 1 + time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) + time_delta *= 100 # 100 blocks + logging.info("Backdating time for first block to %d minutes ago" % (time_delta/60)) + mine_time = now - time_delta + action_time = now + is_mine = True else: - time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) mine_time = bestheader["time"] + time_delta @@ -500,7 +503,7 @@ def do_generate(args): block = finish_block(block, signet_solution, args.grind_cmd) # submit block - r = args.bcli("-stdin", "submitblock", input=ToHex(block).encode('utf8')) + r = args.bcli("-stdin", "submitblock", input=block.serialize().hex().encode('utf8')) # report bstr = "block" if is_mine else "backup block" @@ -520,12 +523,11 @@ def do_calibrate(args): sys.stderr.write("Can only specify one of --nbits or --seconds\n") return 1 if args.nbits is not None and len(args.nbits) != 8: - sys.stderr.write("Must specify 8 hex digits for --nbits") + sys.stderr.write("Must specify 8 hex digits for --nbits\n") return 1 TRIALS = 600 # gets variance down pretty low TRIAL_BITS = 0x1e3ea75f # takes about 5m to do 600 trials - #TRIAL_BITS = 0x1e7ea75f # XXX header = CBlockHeader() header.nBits = TRIAL_BITS @@ -533,23 +535,14 @@ def do_calibrate(args): start = time.time() count = 0 - #CHECKS=[] for i in range(TRIALS): header.nTime = i header.nNonce = 0 headhex = header.serialize().hex() cmd = args.grind_cmd.split(" ") + [headhex] newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip() - #newhead = FromHex(CBlockHeader(), newheadhex.decode('utf8')) - #count += newhead.nNonce - #if (i+1) % 100 == 0: - # CHECKS.append((i+1, count, time.time()-start)) - - #print("checks =", [c*1.0 / (b*targ*2**-256) for _,b,c in CHECKS]) avg = (time.time() - start) * 1.0 / TRIALS - #exp_count = 2**256 / targ * TRIALS - #print("avg =", avg, "count =", count, "exp_count =", exp_count) if args.nbits is not None: want_targ = nbits_to_target(int(args.nbits,16)) @@ -590,7 +583,6 @@ def main(): generate.add_argument("--nbits", default=None, type=str, help="Target nBits (specify difficulty)") generate.add_argument("--min-nbits", action="store_true", help="Target minimum nBits (use min difficulty)") generate.add_argument("--poisson", action="store_true", help="Simulate randomised block times") - #generate.add_argument("--signcmd", default=None, type=str, help="Alternative signing command") generate.add_argument("--multiminer", default=None, type=str, help="Specify which set of blocks to mine (eg: 1-40/100 for the first 40%%, 2/3 for the second 3rd)") generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)") generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)") @@ -605,7 +597,7 @@ def main(): sp.add_argument("--descriptor", default=None, type=str, help="Descriptor for block reward payment") for sp in [solvepsbt, generate, calibrate]: - sp.add_argument("--grind-cmd", default=None, type=str, help="Command to grind a block header for proof-of-work") + sp.add_argument("--grind-cmd", default=None, type=str, required=(sp==calibrate), help="Command to grind a block header for proof-of-work") args = parser.parse_args(sys.argv[1:]) diff --git a/depends/README.md b/depends/README.md index 50e1a32c70..4f3b6df487 100644 --- a/depends/README.md +++ b/depends/README.md @@ -87,6 +87,14 @@ For linux S390X cross compilation: sudo apt-get install g++-s390x-linux-gnu binutils-s390x-linux-gnu +### Install the required dependencies: M1-based macOS + +To be able to build the `qt` package, ensure that Rosetta 2 is installed: + +``` +softwareupdate --install-rosetta +``` + ### Dependency Options The following can be set when running make: `make FOO=bar` diff --git a/doc/README.md b/doc/README.md index c629c2ccfa..38f6b1d327 100644 --- a/doc/README.md +++ b/doc/README.md @@ -30,8 +30,8 @@ Drag Bitcoin Core to your applications folder, and then run Bitcoin Core. * See the documentation at the [Bitcoin Wiki](https://en.bitcoin.it/wiki/Main_Page) for help and more information. -* Ask for help on the [Bitcoin StackExchange](https://bitcoin.stackexchange.com) -* Ask for help on [#bitcoin](https://webchat.freenode.net/#bitcoin) on Freenode. If you don't have an IRC client, use [webchat here](https://webchat.freenode.net/#bitcoin). +* Ask for help on [Bitcoin StackExchange](https://bitcoin.stackexchange.com). +* Ask for help on #bitcoin on Libera Chat. If you don't have an IRC client, you can use [web.libera.chat](https://web.libera.chat/#bitcoin). * Ask for help on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Technical Support board](https://bitcointalk.org/index.php?board=4.0). Building @@ -68,20 +68,20 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th ### Resources * Discuss on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Development & Technical Discussion board](https://bitcointalk.org/index.php?board=6.0). -* Discuss project-specific development on #bitcoin-core-dev on Libera Chat. If you don't have an IRC client, use [webchat here](https://web.libera.chat/#bitcoin-core-dev). -* Discuss general Bitcoin development on #bitcoin-dev on Freenode. If you don't have an IRC client, use [webchat here](https://webchat.freenode.net/#bitcoin-dev). +* Discuss project-specific development on #bitcoin-core-dev on Libera Chat. If you don't have an IRC client, you can use [web.libera.chat](https://web.libera.chat/#bitcoin-core-dev). ### Miscellaneous - [Assets Attribution](assets-attribution.md) - [bitcoin.conf Configuration File](bitcoin-conf.md) - [Files](files.md) - [Fuzz-testing](fuzzing.md) +- [I2P Support](i2p.md) +- [Init Scripts (systemd/upstart/openrc)](init.md) +- [PSBT support](psbt.md) - [Reduce Memory](reduce-memory.md) - [Reduce Traffic](reduce-traffic.md) - [Tor Support](tor.md) -- [Init Scripts (systemd/upstart/openrc)](init.md) - [ZMQ](zmq.md) -- [PSBT support](psbt.md) License --------------------- diff --git a/doc/benchmarking.md b/doc/benchmarking.md index b6cd86eafe..84d5f2c444 100644 --- a/doc/benchmarking.md +++ b/doc/benchmarking.md @@ -8,8 +8,10 @@ thread queue, wallet balance. Running --------------------- -For benchmarks purposes you only need to compile `bitcoin_bench`. Beware of configuring without `--enable-debug` as this would impact -benchmarking by unlatching log printers and lock analysis. +For benchmarking, you only need to compile `bitcoin_bench`. The bench runner +warns if you configure with `--enable-debug`, but consider if building without +it will impact the benchmark(s) you are interested in by unlatching log printers +and lock analysis. make -C src bitcoin_bench @@ -19,19 +21,28 @@ After compiling bitcoin-core, the benchmarks can be run with: The output will look similar to: ``` -| ns/byte | byte/s | error % | benchmark -|--------------------:|--------------------:|--------:|:---------------------------------------------- -| 64.13 | 15,592,356.01 | 0.1% | `Base58CheckEncode` -| 24.56 | 40,722,672.68 | 0.2% | `Base58Decode` +| ns/op | op/s | err% | total | benchmark +|--------------------:|--------------------:|--------:|----------:|:---------- +| 57,927,463.00 | 17.26 | 3.6% | 0.66 | `AddrManAdd` +| 677,816.00 | 1,475.33 | 4.9% | 0.01 | `AddrManGetAddr` + +... + +| ns/byte | byte/s | err% | total | benchmark +|--------------------:|--------------------:|--------:|----------:|:---------- +| 127.32 | 7,854,302.69 | 0.3% | 0.00 | `Base58CheckEncode` +| 31.95 | 31,303,226.99 | 0.2% | 0.00 | `Base58Decode` + ... ``` Help --------------------- - src/bench/bench_bitcoin --help + src/bench/bench_bitcoin -? -To print options like scaling factor or per-benchmark filter. +To print the various options, like listing the benchmarks without running them +or using a regex filter to only run certain benchmarks. Notes --------------------- diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index 9a312bc33c..8c9035c45b 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -4,6 +4,8 @@ The configuration file is used by `bitcoind`, `bitcoin-qt` and `bitcoin-cli`. All command-line options (except for `-?`, `-help`, `-version` and `-conf`) may be specified in a configuration file, and all configuration file options (except for `includeconf`) may also be specified on the command line. Command-line options override values set in the configuration file and configuration file options override values set in the GUI. +Changes to the configuration file while `bitcoind` or `bitcoin-qt` is running only take effect after restarting. + ## Configuration File Format The configuration file is a plain text file and consists of `option=value` entries, one per line. Leading and trailing whitespaces are removed. diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md index 613aea438f..89fd506f13 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -1,6 +1,6 @@ OpenBSD build guide ====================== -(updated for OpenBSD 6.7) +(updated for OpenBSD 6.9) This guide describes how to build bitcoind, bitcoin-qt, and command-line utilities on OpenBSD. @@ -67,9 +67,16 @@ export AUTOMAKE_VERSION=1.16 ``` Make sure `BDB_PREFIX` is set to the appropriate path from the above steps. +Note that building with external signer support currently fails on OpenBSD, +hence you have to explicitely disable it by passing the parameter +`--disable-external-signer` to the configure script. +(Background: the feature requires the header-only library boost::process, which +is available on OpenBSD 6.9 via Boost 1.72.0, but contains certain system calls +and preprocessor defines like `waitid()` and `WEXITED` that are not available.) + To configure with wallet: ```bash -./configure --with-gui=no CC=cc CXX=c++ \ +./configure --with-gui=no --disable-external-signer CC=cc CXX=c++ \ BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ BDB_CFLAGS="-I${BDB_PREFIX}/include" \ MAKE=gmake @@ -77,12 +84,12 @@ To configure with wallet: To configure without wallet: ```bash -./configure --disable-wallet --with-gui=no CC=cc CC_FOR_BUILD=cc CXX=c++ MAKE=gmake +./configure --disable-wallet --with-gui=no --disable-external-signer CC=cc CC_FOR_BUILD=cc CXX=c++ MAKE=gmake ``` To configure with GUI: ```bash -./configure --with-gui=yes CC=cc CXX=c++ \ +./configure --with-gui=yes --disable-external-signer CC=cc CXX=c++ \ BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ BDB_CFLAGS="-I${BDB_PREFIX}/include" \ MAKE=gmake diff --git a/doc/dependencies.md b/doc/dependencies.md index 9754952221..66c5a76b3b 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -6,8 +6,8 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html#third-party-libraries) | | --- | --- | --- | --- | --- | --- | | Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | -| Boost | [1.71.0](https://www.boost.org/users/download/) | [1.58.0](https://github.com/bitcoin/bitcoin/pull/19667) | No | | | -| Clang | | [5.0+](https://releases.llvm.org/download.html) (C++17 support) | | | | +| Boost | [1.71.0](https://www.boost.org/users/download/) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | | | +| Clang<sup>[ \* ](#note1)</sup> | | [5.0+](https://releases.llvm.org/download.html) (C++17 support) | | | | | Expat | [2.2.7](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Android only) | @@ -28,6 +28,8 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | | zlib | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | +<a name="note1">Note \*</a> : When compiling with `-stdlib=libc++`, the minimum supported libc++ version is 7.0. + Controlling dependencies ------------------------ Some dependencies are not needed in all configurations. The following are some factors that affect the dependency list. diff --git a/doc/developer-notes.md b/doc/developer-notes.md index c3a63b3523..583c50a763 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -88,7 +88,7 @@ code. separate words (snake_case). - Class member variables have a `m_` prefix. - Global variables have a `g_` prefix. - - Compile-time constant names are all uppercase, and use `_` to separate words. + - 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 diff --git a/doc/files.md b/doc/files.md index 353efe348d..e670d77ae5 100644 --- a/doc/files.md +++ b/doc/files.md @@ -56,7 +56,8 @@ Subdirectory | File(s) | Description `indexes/coinstats/db/` | LevelDB database | Coinstats index; *optional*, used if `-coinstatsindex=1` `wallets/` | | [Contains wallets](#multi-wallet-environment); can be specified by `-walletdir` option; if `wallets/` subdirectory does not exist, wallets reside in the [data directory](#data-directory-location) `./` | `anchors.dat` | Anchor IP address database, created on shutdown and deleted at startup. Anchors are last known outgoing block-relay-only peers that are tried to re-connect to on startup -`./` | `banlist.dat` | Stores the IPs/subnets of banned nodes +`./` | `banlist.dat` | Stores the addresses/subnets of banned nodes (deprecated). `bitcoind` or `bitcoin-qt` no longer save the banlist to this file, but read it on startup if `banlist.json` is not present. +`./` | `banlist.json` | Stores the addresses/subnets of banned nodes. `./` | `bitcoin.conf` | User-defined [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`. File is not written to by the software and must be created manually. Path can be specified by `-conf` option `./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option `./` | `debug.log` | Contains debug information and general logging generated by `bitcoind` or `bitcoin-qt`; can be specified by `-debuglogfile` option @@ -109,7 +110,7 @@ Subdirectory | File | Description ## Legacy subdirectories and files -These subdirectories and files are no longer used by the Bitcoin Core: +These subdirectories and files are no longer used by Bitcoin Core: Path | Description | Repository notes ---------------|-------------|----------------- diff --git a/doc/i2p.md b/doc/i2p.md new file mode 100644 index 0000000000..27ef4d9d9f --- /dev/null +++ b/doc/i2p.md @@ -0,0 +1,87 @@ +# I2P support in Bitcoin Core + +It is possible to run Bitcoin Core as an +[I2P (Invisible Internet Project)](https://en.wikipedia.org/wiki/I2P) +service and connect to such services. + +This [glossary](https://geti2p.net/en/about/glossary) may be useful to get +started with I2P terminology. + +## Run Bitcoin Core with an I2P router (proxy) + +A running I2P router (proxy) with [SAM](https://geti2p.net/en/docs/api/samv3) +enabled is required (there is an [official one](https://geti2p.net) and +[a few alternatives](https://en.wikipedia.org/wiki/I2P#Routers)). Notice the IP +address and port the SAM proxy is listening to; usually, it is +`127.0.0.1:7656`. Once it is up and running with SAM enabled, use the following +Bitcoin Core options: + +``` +-i2psam=<ip:port> + I2P SAM proxy to reach I2P peers and accept I2P connections (default: + none) + +-i2pacceptincoming + If set and -i2psam is also set then incoming I2P connections are + accepted via the SAM proxy. If this is not set but -i2psam is set + then only outgoing connections will be made to the I2P network. + Ignored if -i2psam is not set. Listening for incoming I2P + connections is done through the SAM proxy, not by binding to a + local address and port (default: 1) +``` + +In a typical situation, this suffices: + +``` +bitcoind -i2psam=127.0.0.1:7656 +``` + +The first time Bitcoin Core connects to the I2P router, its I2P address (and +corresponding private key) will be automatically generated and saved in a file +named `i2p_private_key` in the Bitcoin Core data directory. + +## Additional configuration options related to I2P + +You may set the `debug=i2p` config logging option to have additional +information in the debug log about your I2P configuration and connections. Run +`bitcoin-cli help logging` for more information. + +It is possible to restrict outgoing connections in the usual way with +`onlynet=i2p`. I2P support was added to Bitcoin Core in version 22.0 (mid 2021) +and there may be fewer I2P peers than Tor or IP ones. Therefore, using +`onlynet=i2p` alone (without other `onlynet=`) may make a node more susceptible +to [Sybil attacks](https://en.bitcoin.it/wiki/Weaknesses#Sybil_attack). Use +`bitcoin-cli -addrinfo` to see the number of I2P addresses known to your node. + +## I2P related information in Bitcoin Core + +There are several ways to see your I2P address in Bitcoin Core: +- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`) +- in the output of the `getnetworkinfo` RPC in the "localaddresses" section +- in the output of `bitcoin-cli -netinfo` peer connections dashboard + +To see which I2P peers your node is connected to, use `bitcoin-cli -netinfo 4` +or the `getpeerinfo` RPC (e.g. `bitcoin-cli getpeerinfo`). + +To see which I2P addresses your node knows, use the `getnodeaddresses 0 i2p` +RPC. + +## Compatibility + +Bitcoin Core uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) protocol +to connect to the I2P network. Any I2P router that supports it can be used. + +## Ports in I2P and Bitcoin Core + +Bitcoin Core uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) +protocol. One particularity of SAM v3.1 is that it does not support ports, +unlike newer versions of SAM (v3.2 and up) that do support them and default the +port numbers to 0. From the point of view of peers that use newer versions of +SAM or other protocols that support ports, a SAM v3.1 peer is connecting to them +on port 0, from source port 0. + +To allow future upgrades to newer versions of SAM, Bitcoin Core sets its +listening port to 0 when listening for incoming I2P connections and advertises +its own I2P address with port 0. Furthermore, it will not attempt to connect to +I2P addresses with a non-zero port number because with SAM v3.1 the destination +port (`TO_PORT`) is always set to 0 and is not in the control of Bitcoin Core. diff --git a/doc/release-notes-20833.md b/doc/release-notes-20833.md deleted file mode 100644 index 9a02bbd275..0000000000 --- a/doc/release-notes-20833.md +++ /dev/null @@ -1,12 +0,0 @@ -Updated RPCs ------------- - -- The `testmempoolaccept` RPC now accepts multiple transactions (still experimental at the moment, - API may be unstable). This is intended for testing transaction packages with dependency - relationships; it is not recommended for batch-validating independent transactions. In addition to - mempool policy, package policies apply: the list cannot contain more than 25 transactions or have a - total size exceeding 101K virtual bytes, and cannot conflict with (spend the same inputs as) each other or - the mempool, even if it would be a valid BIP125 replace-by-fee. There are some known limitations to - the accuracy of the test accept: it's possible for `testmempoolaccept` to return "allowed"=True for a - group of transactions, but "too-long-mempool-chain" if they are actually submitted. (#20833) - diff --git a/doc/release-notes-20867.md b/doc/release-notes-20867.md deleted file mode 100644 index 60eed6838f..0000000000 --- a/doc/release-notes-20867.md +++ /dev/null @@ -1,11 +0,0 @@ -Wallet ------- - -- We now support up to 20 keys in `multi()` and `sortedmulti()` descriptors - under `wsh()`. (#20867) - -Updated RPCs ------------- - -- `addmultisigaddress` and `createmultisig` now support up to 20 keys for - Segwit addresses. diff --git a/doc/release-notes.md b/doc/release-notes.md index 53106c9f82..dc28ccb9ed 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,3 +1,5 @@ +# Release notes now being edited on https://github.com/bitcoin-core/bitcoin-devwiki/wiki/22.0-Release-Notes-draft + *After branching off for a major version release of Bitcoin Core, use this template to create the initial release notes draft.* @@ -59,6 +61,13 @@ Notable changes P2P and network changes ----------------------- +- This release removes support for Tor version 2 hidden services in favor of Tor + v3 only, as the Tor network [dropped support for Tor + v2](https://blog.torproject.org/v2-deprecation-timeline) with the release of + Tor version 0.4.6. Henceforth, Bitcoin Core ignores Tor v2 addresses; it + neither rumors them over the network to other peers, nor stores them in memory + or to `peers.dat`. (#22050) + - Added NAT-PMP port mapping support via [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html). (#18077) @@ -114,6 +123,18 @@ Updated RPCs - `getnodeaddresses` now also accepts a "network" argument (ipv4, ipv6, onion, or i2p) to return only addresses of the specified network. (#21843) +- The `testmempoolaccept` RPC now accepts multiple transactions (still experimental at the moment, + API may be unstable). This is intended for testing transaction packages with dependency + relationships; it is not recommended for batch-validating independent transactions. In addition to + mempool policy, package policies apply: the list cannot contain more than 25 transactions or have a + total size exceeding 101K virtual bytes, and cannot conflict with (spend the same inputs as) each other or + the mempool, even if it would be a valid BIP125 replace-by-fee. There are some known limitations to + the accuracy of the test accept: it's possible for `testmempoolaccept` to return "allowed"=True for a + group of transactions, but "too-long-mempool-chain" if they are actually submitted. (#20833) + +- `addmultisigaddress` and `createmultisig` now support up to 20 keys for + Segwit addresses. (#20867) + Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. New RPCs @@ -142,8 +163,12 @@ Tools and Utilities - A new CLI `-addrinfo` command returns the number of addresses known to the node per network type (including Tor v2 versus v3) and total. This can be useful to see if the node knows enough addresses in a network to use options - like `-onlynet=<network>` or to upgrade to current and future Tor releases - that support Tor v3 addresses only. (#21595) + like `-onlynet=<network>` or to upgrade to this release of Bitcoin Core 22.0 + that supports Tor v3 only. (#21595) + +- A new `-rpcwaittimeout` argument to `bitcoin-cli` sets the timeout + in seconds to use with `-rpcwait`. If the timeout expires, + `bitcoin-cli` will report a failure. (#21056) Wallet ------ @@ -160,6 +185,9 @@ Wallet Note that the resulting transaction may become invalid if one of the unsafe inputs disappears. If that happens, the transaction must be funded with different inputs and republished. (#21359) +- We now support up to 20 keys in `multi()` and `sortedmulti()` descriptors + under `wsh()`. (#20867) + GUI changes ----------- diff --git a/doc/release-process.md b/doc/release-process.md index 3ead1181b9..75a574ee31 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -37,6 +37,12 @@ Release Process - This update should be reviewed with a reindex-chainstate with assumevalid=0 to catch any defect that causes rejection of blocks in the past history. - Clear the release notes and move them to the wiki (see "Write the release notes" below). +- Translations on Transifex + - Create [a new resource](https://www.transifex.com/bitcoin/bitcoin/content/) named after the major version with the slug `[bitcoin.qt-translation-<RRR>x]`, where `RRR` is the major branch number padded with zeros. Use `src/qt/locale/bitcoin_en.xlf` to create it. + - In the project workflow settings, ensure that [Translation Memory Fill-up](https://docs.transifex.com/translation-memory/enabling-autofill) is enabled and that [Translation Memory Context Matching](https://docs.transifex.com/translation-memory/translation-memory-with-context) is disabled. + - Update the Transifex slug in [`.tx/config`](/.tx/config) to the slug of the resource created in the first step. This identifies which resource the translations will be synchronized from. + - Make an announcement that translators can start translating for the new version. You can use one of the [previous announcements](https://www.transifex.com/bitcoin/bitcoin/announcements/) as a template. + - Change the auto-update URL for the resource to `master`, e.g. `https://raw.githubusercontent.com/bitcoin/bitcoin/master/src/qt/locale/bitcoin_en.xlf`. (Do this only after the previous steps, to prevent an auto-update from interfering.) #### After branch-off (on master) @@ -46,6 +52,8 @@ Release Process - Update the versions. - Create a pinned meta-issue for testing the release candidate (see [this issue](https://github.com/bitcoin/bitcoin/issues/17079) for an example) and provide a link to it in the release announcements where useful. +- Translations on Transifex + - Change the auto-update URL for the new major version's resource away from `master` and to the branch, e.g. `https://raw.githubusercontent.com/bitcoin/bitcoin/<branch>/src/qt/locale/bitcoin_en.xlf`. Do not forget this or it will keep tracking the translations on master instead, drifting away from the specific major release. #### Before final release @@ -278,7 +286,7 @@ The `*-debug*` files generated by the gitian build contain debug symbols for troubleshooting by developers. It is assumed that anyone that is interested in debugging can run gitian to generate the files for themselves. To avoid end-user confusion about which file to pick, as well as save storage -space *do not upload these to the bitcoin.org server, nor put them in the torrent*. +space *do not upload these to the bitcoincore.org server, nor put them in the torrent*. - GPG-sign it, delete the unsigned file: ``` @@ -288,7 +296,7 @@ rm SHA256SUMS (the digest algorithm is forced to sha256 to avoid confusion of the `Hash:` header that GPG adds with the SHA256 used for the files) Note: check that SHA256SUMS itself doesn't end up in SHA256SUMS, which is a spurious/nonsensical entry. -- Upload zips and installers, as well as `SHA256SUMS.asc` from last step, to the bitcoin.org server +- Upload zips and installers, as well as `SHA256SUMS.asc` from last step, to the bitcoincore.org server into `/var/www/bin/bitcoin-core-${VERSION}` - A `.torrent` will appear in the directory after a few minutes. Optionally help seed this torrent. To get the `magnet:` URI use: @@ -296,24 +304,9 @@ Note: check that SHA256SUMS itself doesn't end up in SHA256SUMS, which is a spur transmission-show -m <torrent file> ``` Insert the magnet URI into the announcement sent to mailing lists. This permits -people without access to `bitcoin.org` to download the binary distribution. +people without access to `bitcoincore.org` to download the binary distribution. Also put it into the `optional_magnetlink:` slot in the YAML file for -bitcoin.org (see below for bitcoin.org update instructions). - -- Update bitcoin.org version - - - First, check to see if the Bitcoin.org maintainers have prepared a - release: https://github.com/bitcoin-dot-org/bitcoin.org/labels/Core - - - If they have, it will have previously failed their CI - checks because the final release files weren't uploaded. - Trigger a CI rebuild---if it passes, merge. - - - If they have not prepared a release, follow the Bitcoin.org release - instructions: https://github.com/bitcoin-dot-org/bitcoin.org/blob/master/docs/adding-events-release-notes-and-alerts.md#release-notes - - - After the pull request is merged, the website will automatically show the newest version within 15 minutes, as well - as update the OS download links. +bitcoincore.org. - Update other repositories and websites for new version diff --git a/doc/tor.md b/doc/tor.md index 2640a6109b..7d134b64e0 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -5,6 +5,14 @@ It is possible to run Bitcoin Core as a Tor onion service, and connect to such s The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on port 9150. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly configure Tor. +## Compatibility + +- Starting with version 22.0, Bitcoin Core only supports Tor version 3 hidden + services (Tor v3). Tor v2 addresses are ignored by Bitcoin Core and neither + relayed nor stored. + +- Tor removed v2 support beginning with version 0.4.6. + ## How to see information about your Tor configuration via Bitcoin Core There are several ways to see your local onion address in Bitcoin Core: @@ -18,7 +26,7 @@ information in the debug log about your Tor configuration. CLI `-addrinfo` returns the number of addresses known to your node per network type, including Tor v2 and v3. This is useful to see how many onion addresses are known to your node for `-onlynet=onion` and how many Tor v3 addresses it -knows when upgrading to current and future Tor releases that support Tor v3 only. +knows when upgrading to Bitcoin Core v22.0 and up that supports Tor v3 only. ## 1. Run Bitcoin Core behind a Tor proxy diff --git a/doc/translation_process.md b/doc/translation_process.md index f132693264..97a8fbfff2 100644 --- a/doc/translation_process.md +++ b/doc/translation_process.md @@ -63,17 +63,12 @@ username = USERNAME The Transifex Bitcoin project config file is included as part of the repo. It can be found at `.tx/config`, however you shouldn’t need to change anything. ### Synchronising translations -To assist in updating translations, a helper script is available in the [maintainer-tools repo](https://github.com/bitcoin-core/bitcoin-maintainer-tools). -1. `python3 ../bitcoin-maintainer-tools/update-translations.py` -2. `git add` new translations from `src/qt/locale/` -3. Update `src/qt/bitcoin_locale.qrc` manually or via -```bash -git ls-files src/qt/locale/*ts|xargs -n1 basename|sed 's/\(bitcoin_\(.*\)\).ts/ <file alias="\2">locale\/\1.qm<\/file>/' +To assist in updating translations, a helper script is available in the [maintainer-tools repo](https://github.com/bitcoin-core/bitcoin-maintainer-tools). To use it and commit the result, simply do: + ``` -4. Update `src/Makefile.qt_locale.include` manually or via -```bash -git ls-files src/qt/locale/*ts|xargs -n1 basename|sed 's/\(bitcoin_\(.*\)\).ts/ qt\/locale\/\1.ts \\/' +python3 ../bitcoin-maintainer-tools/update-translations.py +git commit -a ``` **Do not directly download translations** one by one from the Transifex website, as we do a few post-processing steps before committing the translations. diff --git a/src/Makefile.am b/src/Makefile.am index e2ed70556d..7de5fb36ed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -145,6 +145,8 @@ BITCOIN_CORE_H = \ core_memusage.h \ cuckoocache.h \ dbwrapper.h \ + deploymentinfo.h \ + deploymentstatus.h \ external_signer.h \ flatfile.h \ fs.h \ @@ -272,7 +274,6 @@ BITCOIN_CORE_H = \ validation.h \ validationinterface.h \ versionbits.h \ - versionbitsinfo.h \ wallet/bdb.h \ wallet/coincontrol.h \ wallet/coinselection.h \ @@ -328,6 +329,7 @@ libbitcoin_server_a_SOURCES = \ chain.cpp \ consensus/tx_verify.cpp \ dbwrapper.cpp \ + deploymentstatus.cpp \ flatfile.cpp \ httprpc.cpp \ httpserver.cpp \ @@ -540,6 +542,7 @@ libbitcoin_common_a_SOURCES = \ compressor.cpp \ core_read.cpp \ core_write.cpp \ + deploymentinfo.cpp \ external_signer.cpp \ init/common.cpp \ key.cpp \ @@ -561,7 +564,6 @@ libbitcoin_common_a_SOURCES = \ script/sign.cpp \ script/signingprovider.cpp \ script/standard.cpp \ - versionbitsinfo.cpp \ warnings.cpp \ $(BITCOIN_CORE_H) @@ -812,23 +814,23 @@ clean-local: check-symbols: $(bin_PROGRAMS) if TARGET_DARWIN @echo "Checking macOS dynamic libraries..." - $(AM_V_at) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) + $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) endif if TARGET_WINDOWS @echo "Checking Windows dynamic libraries..." - $(AM_V_at) OBJDUMP=$(OBJDUMP) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) + $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) endif -if GLIBC_BACK_COMPAT +if TARGET_LINUX @echo "Checking glibc back compat..." - $(AM_V_at) CPPFILT=$(CPPFILT) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) + $(AM_V_at) CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) endif check-security: $(bin_PROGRAMS) if HARDEN @echo "Checking binary security..." - $(AM_V_at) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS) + $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS) endif libbitcoin_ipc_mpgen_input = \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 56b8ca8ce6..2a8e4a0aac 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -35,6 +35,7 @@ bench_bench_bitcoin_SOURCES = \ bench/mempool_stress.cpp \ bench/nanobench.h \ bench/nanobench.cpp \ + bench/peer_eviction.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ bench/util_time.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 105d09f730..fc2fd80166 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -35,11 +35,12 @@ BITCOIN_TEST_SUITE = \ $(TEST_UTIL_H) FUZZ_SUITE_LD_COMMON = \ + $(LIBTEST_UTIL) \ + $(LIBTEST_FUZZ) \ $(LIBBITCOIN_SERVER) \ + $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ - $(LIBTEST_UTIL) \ - $(LIBTEST_FUZZ) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBBITCOIN_CLI) \ @@ -160,7 +161,6 @@ BITCOIN_TESTS += \ wallet/test/scriptpubkeyman_tests.cpp FUZZ_SUITE_LD_COMMON +=\ - $(LIBBITCOIN_WALLET) \ $(SQLITE_LIBS) \ $(BDB_LIBS) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index c376aced10..b8fd019bab 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -11,19 +11,78 @@ #include <cstdint> #include <hash.h> #include <logging/timer.h> +#include <netbase.h> #include <random.h> #include <streams.h> #include <tinyformat.h> +#include <univalue.h> +#include <util/settings.h> #include <util/system.h> +CBanEntry::CBanEntry(const UniValue& json) + : nVersion(json["version"].get_int()), nCreateTime(json["ban_created"].get_int64()), + nBanUntil(json["banned_until"].get_int64()) +{ +} + +UniValue CBanEntry::ToJson() const +{ + UniValue json(UniValue::VOBJ); + json.pushKV("version", nVersion); + json.pushKV("ban_created", nCreateTime); + json.pushKV("banned_until", nBanUntil); + return json; +} + namespace { +static const char* BANMAN_JSON_ADDR_KEY = "address"; + +/** + * Convert a `banmap_t` object to a JSON array. + * @param[in] bans Bans list to convert. + * @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for + * passing to `BanMapFromJson()`. + */ +UniValue BanMapToJson(const banmap_t& bans) +{ + UniValue bans_json(UniValue::VARR); + for (const auto& it : bans) { + const auto& address = it.first; + const auto& ban_entry = it.second; + UniValue j = ban_entry.ToJson(); + j.pushKV(BANMAN_JSON_ADDR_KEY, address.ToString()); + bans_json.push_back(j); + } + return bans_json; +} + +/** + * Convert a JSON array to a `banmap_t` object. + * @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`. + * @param[out] bans Bans list to create from the JSON. + * @throws std::runtime_error if the JSON does not have the expected fields or they contain + * unparsable values. + */ +void BanMapFromJson(const UniValue& bans_json, banmap_t& bans) +{ + for (const auto& ban_entry_json : bans_json.getValues()) { + CSubNet subnet; + const auto& subnet_str = ban_entry_json[BANMAN_JSON_ADDR_KEY].get_str(); + if (!LookupSubNet(subnet_str, subnet)) { + throw std::runtime_error( + strprintf("Cannot parse banned address or subnet: %s", subnet_str)); + } + bans.insert_or_assign(subnet, CBanEntry{ban_entry_json}); + } +} + template <typename Stream, typename Data> bool SerializeDB(Stream& stream, const Data& data) { // Write and commit header, data try { - CHashWriter hasher(SER_DISK, CLIENT_VERSION); + CHashWriter hasher(stream.GetType(), stream.GetVersion()); stream << Params().MessageStart() << data; hasher << Params().MessageStart() << data; stream << hasher.GetHash(); @@ -35,7 +94,7 @@ bool SerializeDB(Stream& stream, const Data& data) } template <typename Data> -bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) +bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version) { // Generate random temporary filename uint16_t randv = 0; @@ -45,7 +104,7 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data // open temp output file, and associate with CAutoFile fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn; FILE *file = fsbridge::fopen(pathTmp, "wb"); - CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + CAutoFile fileout(file, SER_DISK, version); if (fileout.IsNull()) { fileout.fclose(); remove(pathTmp); @@ -106,11 +165,11 @@ bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true) } template <typename Data> -bool DeserializeFileDB(const fs::path& path, Data& data) +bool DeserializeFileDB(const fs::path& path, Data& data, int version) { // open input file, and associate with CAutoFile FILE* file = fsbridge::fopen(path, "rb"); - CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + CAutoFile filein(file, SER_DISK, version); if (filein.IsNull()) { LogPrintf("Missing or invalid file %s\n", path.string()); return false; @@ -119,18 +178,54 @@ bool DeserializeFileDB(const fs::path& path, Data& data) } } // namespace -CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path)) +CBanDB::CBanDB(fs::path ban_list_path) + : m_banlist_dat(ban_list_path.string() + ".dat"), + m_banlist_json(ban_list_path.string() + ".json") { } bool CBanDB::Write(const banmap_t& banSet) { - return SerializeFileDB("banlist", m_ban_list_path, banSet); + std::vector<std::string> errors; + if (util::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) { + return true; + } + + for (const auto& err : errors) { + error("%s", err); + } + return false; } -bool CBanDB::Read(banmap_t& banSet) +bool CBanDB::Read(banmap_t& banSet, bool& dirty) { - return DeserializeFileDB(m_ban_list_path, banSet); + // If the JSON banlist does not exist, then try to read the non-upgraded banlist.dat. + if (!fs::exists(m_banlist_json)) { + // If this succeeds then we need to flush to disk in order to create the JSON banlist. + dirty = true; + return DeserializeFileDB(m_banlist_dat, banSet, CLIENT_VERSION); + } + + dirty = false; + + std::map<std::string, util::SettingsValue> settings; + std::vector<std::string> errors; + + if (!util::ReadSettings(m_banlist_json, settings, errors)) { + for (const auto& err : errors) { + LogPrintf("Cannot load banlist %s: %s\n", m_banlist_json.string(), err); + } + return false; + } + + try { + BanMapFromJson(settings[JSON_KEY], banSet); + } catch (const std::runtime_error& e) { + LogPrintf("Cannot parse banlist %s: %s\n", m_banlist_json.string(), e.what()); + return false; + } + + return true; } CAddrDB::CAddrDB() @@ -140,12 +235,12 @@ CAddrDB::CAddrDB() bool CAddrDB::Write(const CAddrMan& addr) { - return SerializeFileDB("peers", pathAddr, addr); + return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION); } bool CAddrDB::Read(CAddrMan& addr) { - return DeserializeFileDB(pathAddr, addr); + return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION); } bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) @@ -161,13 +256,13 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors) { LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size())); - SerializeFileDB("anchors", anchors_db_path, anchors); + SerializeFileDB("anchors", anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT); } std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path) { std::vector<CAddress> anchors; - if (DeserializeFileDB(anchors_db_path, anchors)) { + if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) { LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename()); } else { anchors.clear(); diff --git a/src/addrdb.h b/src/addrdb.h index 8953ebb169..399103c991 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -9,6 +9,7 @@ #include <fs.h> #include <net_types.h> // For banmap_t #include <serialize.h> +#include <univalue.h> #include <string> #include <vector> @@ -36,6 +37,13 @@ public: nCreateTime = nCreateTimeIn; } + /** + * Create a ban entry from JSON. + * @param[in] json A JSON representation of a ban entry, as created by `ToJson()`. + * @throw std::runtime_error if the JSON does not have the expected fields. + */ + explicit CBanEntry(const UniValue& json); + SERIALIZE_METHODS(CBanEntry, obj) { uint8_t ban_reason = 2; //! For backward compatibility @@ -48,6 +56,12 @@ public: nCreateTime = 0; nBanUntil = 0; } + + /** + * Generate a JSON representation of this ban entry. + * @return JSON suitable for passing to the `CBanEntry(const UniValue&)` constructor. + */ + UniValue ToJson() const; }; /** Access to the (IP) address database (peers.dat) */ @@ -62,15 +76,30 @@ public: static bool Read(CAddrMan& addr, CDataStream& ssPeers); }; -/** Access to the banlist database (banlist.dat) */ +/** Access to the banlist databases (banlist.json and banlist.dat) */ class CBanDB { private: - const fs::path m_ban_list_path; + /** + * JSON key under which the data is stored in the json database. + */ + static constexpr const char* JSON_KEY = "banned_nets"; + + const fs::path m_banlist_dat; + const fs::path m_banlist_json; public: explicit CBanDB(fs::path ban_list_path); bool Write(const banmap_t& banSet); - bool Read(banmap_t& banSet); + + /** + * Read the banlist from disk. + * @param[out] banSet The loaded list. Set if `true` is returned, otherwise it is left + * in an undefined state. + * @param[out] dirty Indicates whether the loaded list needs flushing to disk. Set if + * `true` is returned, otherwise it is left in an undefined state. + * @return true on success + */ + bool Read(banmap_t& banSet, bool& dirty); }; /** diff --git a/src/addrman.cpp b/src/addrman.cpp index 14b412a038..389d106164 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -6,12 +6,15 @@ #include <addrman.h> #include <hash.h> +#include <i2p.h> #include <logging.h> #include <netaddress.h> #include <serialize.h> #include <cmath> #include <optional> +#include <unordered_map> +#include <unordered_set> int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const { @@ -75,14 +78,48 @@ double CAddrInfo::GetChance(int64_t nNow) const return fChance; } +void CAddrMan::RemoveInvalid() +{ + for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { + for (size_t i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvNew[bucket][i]; + if (id != -1 && !mapInfo[id].IsValid()) { + ClearNew(bucket, i); + } + } + } + + for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; ++bucket) { + for (size_t i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvTried[bucket][i]; + if (id == -1) { + continue; + } + const auto& addr_info = mapInfo[id]; + if (addr_info.IsValid()) { + continue; + } + vvTried[bucket][i] = -1; + --nTried; + SwapRandom(addr_info.nRandomPos, vRandom.size() - 1); + vRandom.pop_back(); + mapAddr.erase(addr_info); + mapInfo.erase(id); + m_tried_collisions.erase(id); + } + } +} + CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId) { - std::map<CNetAddr, int>::iterator it = mapAddr.find(addr); + AssertLockHeld(cs); + + const auto it = mapAddr.find(addr); if (it == mapAddr.end()) return nullptr; if (pnId) *pnId = (*it).second; - std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second); + const auto it2 = mapInfo.find((*it).second); if (it2 != mapInfo.end()) return &(*it2).second; return nullptr; @@ -90,6 +127,8 @@ CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId) CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId) { + AssertLockHeld(cs); + int nId = nIdCount++; mapInfo[nId] = CAddrInfo(addr, addrSource); mapAddr[addr] = nId; @@ -102,6 +141,8 @@ CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, in void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) { + AssertLockHeld(cs); + if (nRndPos1 == nRndPos2) return; @@ -122,6 +163,8 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) void CAddrMan::Delete(int nId) { + AssertLockHeld(cs); + assert(mapInfo.count(nId) != 0); CAddrInfo& info = mapInfo[nId]; assert(!info.fInTried); @@ -136,6 +179,8 @@ void CAddrMan::Delete(int nId) void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) { + AssertLockHeld(cs); + // if there is an entry in the specified bucket, delete it. if (vvNew[nUBucket][nUBucketPos] != -1) { int nIdDelete = vvNew[nUBucket][nUBucketPos]; @@ -151,6 +196,8 @@ void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) void CAddrMan::MakeTried(CAddrInfo& info, int nId) { + AssertLockHeld(cs); + // remove the entry from all new buckets for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { int pos = info.GetBucketPosition(nKey, true, bucket); @@ -199,6 +246,8 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime) { + AssertLockHeld(cs); + int nId; nLastGood = nTime; @@ -265,6 +314,8 @@ void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) { + AssertLockHeld(cs); + if (!addr.IsRoutable()) return false; @@ -338,6 +389,8 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP void CAddrMan::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -360,7 +413,9 @@ void CAddrMan::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) CAddrInfo CAddrMan::Select_(bool newOnly) { - if (size() == 0) + AssertLockHeld(cs); + + if (vRandom.empty()) return CAddrInfo(); if (newOnly && nNew == 0) @@ -408,8 +463,10 @@ CAddrInfo CAddrMan::Select_(bool newOnly) #ifdef DEBUG_ADDRMAN int CAddrMan::Check_() { - std::set<int> setTried; - std::map<int, int> mapNew; + AssertLockHeld(cs); + + std::unordered_set<int> setTried; + std::unordered_map<int, int> mapNew; if (vRandom.size() != (size_t)(nTried + nNew)) return -7; @@ -485,6 +542,8 @@ int CAddrMan::Check_() void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size_t max_pct, std::optional<Network> network) { + AssertLockHeld(cs); + size_t nNodes = vRandom.size(); if (max_pct != 0) { nNodes = max_pct * nNodes / 100; @@ -517,6 +576,8 @@ void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size void CAddrMan::Connected_(const CService& addr, int64_t nTime) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -537,6 +598,8 @@ void CAddrMan::Connected_(const CService& addr, int64_t nTime) void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -555,6 +618,8 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) void CAddrMan::ResolveCollisions_() { + AssertLockHeld(cs); + for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) { int id_new = *it; @@ -614,6 +679,8 @@ void CAddrMan::ResolveCollisions_() CAddrInfo CAddrMan::SelectTriedCollision_() { + AssertLockHeld(cs); + if (m_tried_collisions.size() == 0) return CAddrInfo(); std::set<int>::iterator it = m_tried_collisions.begin(); @@ -665,3 +732,100 @@ std::vector<bool> CAddrMan::DecodeAsmap(fs::path path) } return bits; } + +void CAddrMan::ResetI2PPorts() +{ + for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvNew[bucket][i]; + if (id == -1) { + continue; + } + auto it = mapInfo.find(id); + if (it == mapInfo.end()) { + return; + } + auto& addr_info = it->second; + if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) { + continue; + } + + auto addr_info_newport = addr_info; + // The below changes addr_info_newport.GetKey(), which is used in finding a + // bucket and a position within that bucket. So a re-bucketing may be necessary. + addr_info_newport.port = I2P_SAM31_PORT; + + // Reposition entries of vvNew within the same bucket because we don't know the source + // address which led to the decision to store the entry in vvNew[bucket] so we can't + // re-evaluate that decision, but even if we could, CAddrInfo::GetNewBucket() does not + // use CAddrInfo::GetKey() so it would end up in the same bucket as before the port + // change. + const auto i_target = addr_info_newport.GetBucketPosition(nKey, true, bucket); + + if (i_target == i) { // No need to re-position. + addr_info = addr_info_newport; + continue; + } + + // Reposition from i to i_target, removing the entry from i_target (if any). + ClearNew(bucket, i_target); + vvNew[bucket][i_target] = id; + vvNew[bucket][i] = -1; + addr_info = addr_info_newport; + } + } + + for (int bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; ++bucket) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvTried[bucket][i]; + if (id == -1) { + continue; + } + auto it = mapInfo.find(id); + if (it == mapInfo.end()) { + return; + } + auto& addr_info = it->second; + if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) { + continue; + } + + auto addr_info_newport = addr_info; + // The below changes addr_info_newport.GetKey(), which is used in finding a + // bucket and a position within that bucket. So a re-bucketing may be necessary. + addr_info_newport.port = I2P_SAM31_PORT; + + const auto bucket_target = addr_info_newport.GetTriedBucket(nKey, m_asmap); + const auto i_target = addr_info_newport.GetBucketPosition(nKey, false, bucket_target); + + if (bucket_target == bucket && i_target == i) { // No need to re-position. + addr_info = addr_info_newport; + continue; + } + + // Reposition from (bucket, i) to (bucket_target, i_target). If the latter is + // occupied, then move the entry from there to vvNew. + + const auto old_target_id = vvTried[bucket_target][i_target]; + if (old_target_id != -1) { + CAddrInfo& old_target_info = mapInfo[old_target_id]; + + old_target_info.fInTried = false; + vvTried[bucket_target][i_target] = -1; + --nTried; + + const auto new_bucket = old_target_info.GetNewBucket(nKey, m_asmap); + const auto new_bucket_i = old_target_info.GetBucketPosition(nKey, true, new_bucket); + ClearNew(new_bucket, new_bucket_i); + + old_target_info.nRefCount = 1; + vvNew[new_bucket][new_bucket_i] = old_target_id; + ++nNew; + } + + vvTried[bucket_target][i_target] = id; + vvTried[bucket][i] = -1; + addr_info = addr_info_newport; + } + } +} diff --git a/src/addrman.h b/src/addrman.h index 41994288db..2a5c6c06b4 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -8,22 +8,22 @@ #include <clientversion.h> #include <config/bitcoin-config.h> +#include <fs.h> +#include <hash.h> #include <netaddress.h> #include <protocol.h> #include <random.h> +#include <streams.h> #include <sync.h> #include <timedata.h> #include <tinyformat.h> #include <util/system.h> -#include <fs.h> -#include <hash.h> #include <iostream> -#include <map> #include <optional> #include <set> #include <stdint.h> -#include <streams.h> +#include <unordered_map> #include <vector> /** @@ -231,6 +231,7 @@ public: */ template <typename Stream> void Serialize(Stream& s_) const + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); @@ -251,7 +252,7 @@ public: int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); s << nUBuckets; - std::map<int, int> mapUnkIds; + std::unordered_map<int, int> mapUnkIds; int nIds = 0; for (const auto& entry : mapInfo) { mapUnkIds[entry.first] = nIds; @@ -296,10 +297,11 @@ public: template <typename Stream> void Unserialize(Stream& s_) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); - Clear(); + assert(vRandom.empty()); Format format; s_ >> Using<CustomUintFormatter<1>>(format); @@ -435,23 +437,28 @@ public: // Prune new entries with refcount 0 (as a result of collisions). int nLostUnk = 0; - for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) { + for (auto it = mapInfo.cbegin(); it != mapInfo.cend(); ) { if (it->second.fInTried == false && it->second.nRefCount == 0) { - std::map<int, CAddrInfo>::const_iterator itCopy = it++; + const auto itCopy = it++; Delete(itCopy->first); - nLostUnk++; + ++nLostUnk; } else { - it++; + ++it; } } if (nLost + nLostUnk > 0) { LogPrint(BCLog::ADDRMAN, "addrman lost %i new and %i tried addresses due to collisions\n", nLostUnk, nLost); } + RemoveInvalid(); + + ResetI2PPorts(); + Check(); } void Clear() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); std::vector<int>().swap(vRandom); @@ -487,26 +494,15 @@ public: //! Return the number of (unique) addresses in all tables. size_t size() const + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); // TODO: Cache this in an atomic to avoid this overhead return vRandom.size(); } - //! Consistency check - void Check() - { -#ifdef DEBUG_ADDRMAN - { - LOCK(cs); - int err; - if ((err=Check_())) - LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err); - } -#endif - } - //! Add a single address. bool Add(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty = 0) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); bool fRet = false; @@ -521,6 +517,7 @@ public: //! Add multiple addresses. bool Add(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); int nAdd = 0; @@ -536,6 +533,7 @@ public: //! Mark an entry as accessible. void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -545,6 +543,7 @@ public: //! Mark an entry as connection attempted to. void Attempt(const CService &addr, bool fCountFailure, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -554,6 +553,7 @@ public: //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. void ResolveCollisions() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -563,14 +563,12 @@ public: //! Randomly select an address in tried that another address is attempting to evict. CAddrInfo SelectTriedCollision() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { - CAddrInfo ret; - { - LOCK(cs); - Check(); - ret = SelectTriedCollision_(); - Check(); - } + LOCK(cs); + Check(); + const CAddrInfo ret = SelectTriedCollision_(); + Check(); return ret; } @@ -578,14 +576,12 @@ public: * Choose an address to connect to. */ CAddrInfo Select(bool newOnly = false) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { - CAddrInfo addrRet; - { - LOCK(cs); - Check(); - addrRet = Select_(newOnly); - Check(); - } + LOCK(cs); + Check(); + const CAddrInfo addrRet = Select_(newOnly); + Check(); return addrRet; } @@ -597,19 +593,19 @@ public: * @param[in] network Select only addresses of this network (nullopt = all). */ std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { + LOCK(cs); Check(); std::vector<CAddress> vAddr; - { - LOCK(cs); - GetAddr_(vAddr, max_addresses, max_pct, network); - } + GetAddr_(vAddr, max_addresses, max_pct, network); Check(); return vAddr; } //! Outer function for Connected_() void Connected(const CService &addr, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -618,6 +614,7 @@ public: } void SetServices(const CService &addr, ServiceFlags nServices) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -633,8 +630,8 @@ protected: FastRandomContext insecure_rand; private: - //! critical section to protect the inner data structures - mutable RecursiveMutex cs; + //! A mutex to protect the inner data structures. + mutable Mutex cs; //! Serialization versions. enum Format : uint8_t { @@ -662,10 +659,10 @@ private: int nIdCount GUARDED_BY(cs); //! table with information about all nIds - std::map<int, CAddrInfo> mapInfo GUARDED_BY(cs); + std::unordered_map<int, CAddrInfo> mapInfo GUARDED_BY(cs); //! find an nId based on its network address - std::map<CNetAddr, int> mapAddr GUARDED_BY(cs); + std::unordered_map<CNetAddr, int, CNetAddrHash> mapAddr GUARDED_BY(cs); //! randomly-ordered vector of all nIds std::vector<int> vRandom GUARDED_BY(cs); @@ -725,6 +722,19 @@ private: //! Return a random to-be-evicted tried table address. CAddrInfo SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); + //! Consistency check + void Check() + EXCLUSIVE_LOCKS_REQUIRED(cs) + { +#ifdef DEBUG_ADDRMAN + AssertLockHeld(cs); + const int err = Check_(); + if (err) { + LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err); + } +#endif + } + #ifdef DEBUG_ADDRMAN //! Perform consistency check. Returns an error code or zero. int Check_() EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -756,6 +766,17 @@ private: //! Update an entry's service bits. void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs); + //! Remove invalid addresses. + void RemoveInvalid() EXCLUSIVE_LOCKS_REQUIRED(cs); + + /** + * Reset the ports of I2P peers to 0. + * This is needed as a temporary measure because now we enforce port 0 and + * only connect to I2P hosts if the port is 0, but in the early days some + * I2P addresses with port 8333 were rumoured and persisted into addrmans. + */ + void ResetI2PPorts() EXCLUSIVE_LOCKS_REQUIRED(cs); + friend class CAddrManTest; }; diff --git a/src/banman.cpp b/src/banman.cpp index bb97fc4809..d2437e6733 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -18,20 +18,18 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); int64_t n_start = GetTimeMillis(); - m_is_dirty = false; - banmap_t banmap; - if (m_ban_db.Read(banmap)) { - SetBanned(banmap); // thread save setter - SetBannedSetDirty(false); // no need to write down, just read data - SweepBanned(); // sweep out unused entries + if (m_ban_db.Read(m_banned, m_is_dirty)) { + SweepBanned(); // sweep out unused entries - LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", - m_banned.size(), GetTimeMillis() - n_start); + LogPrint(BCLog::NET, "Loaded %d banned node addresses/subnets %dms\n", m_banned.size(), + GetTimeMillis() - n_start); } else { - LogPrintf("Recreating banlist.dat\n"); - SetBannedSetDirty(true); // force write - DumpBanlist(); + LogPrintf("Recreating the banlist database\n"); + m_banned = {}; + m_is_dirty = true; } + + DumpBanlist(); } BanMan::~BanMan() @@ -53,8 +51,8 @@ void BanMan::DumpBanlist() SetBannedSetDirty(false); } - LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", - banmap.size(), GetTimeMillis() - n_start); + LogPrint(BCLog::NET, "Flushed %d banned node addresses/subnets to disk %dms\n", banmap.size(), + GetTimeMillis() - n_start); } void BanMan::ClearBanned() @@ -167,13 +165,6 @@ void BanMan::GetBanned(banmap_t& banmap) banmap = m_banned; //create a thread safe copy } -void BanMan::SetBanned(const banmap_t& banmap) -{ - LOCK(m_cs_banned); - m_banned = banmap; - m_is_dirty = true; -} - void BanMan::SweepBanned() { int64_t now = GetTime(); @@ -188,7 +179,7 @@ void BanMan::SweepBanned() m_banned.erase(it++); m_is_dirty = true; notify_ui = true; - LogPrint(BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, sub_net.ToString()); + LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); } else ++it; } diff --git a/src/banman.h b/src/banman.h index f6bfbd1e49..8c75d4037e 100644 --- a/src/banman.h +++ b/src/banman.h @@ -17,7 +17,8 @@ // NOTE: When adjusting this, update rpcnet:setban's help ("24h") static constexpr unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; // Default 24-hour ban -// How often to dump addresses to banlist.dat + +/// How often to dump banned addresses/subnets to disk. static constexpr std::chrono::minutes DUMP_BANS_INTERVAL{15}; class CClientUIInterface; @@ -30,7 +31,7 @@ class CSubNet; // If an address or subnet is banned, we never accept incoming connections from // it and never create outgoing connections to it. We won't gossip its address // to other peers in addr messages. Banned addresses and subnets are stored to -// banlist.dat on shutdown and reloaded on startup. Banning can be used to +// disk on shutdown and reloaded on startup. Banning can be used to // prevent connections with spy nodes or other griefers. // // 2. Discouragement. If a peer misbehaves enough (see Misbehaving() in @@ -79,7 +80,6 @@ public: void DumpBanlist(); private: - void SetBanned(const banmap_t& banmap); bool BannedSetIsDirty(); //!set the "dirty" flag for the banlist void SetBannedSetDirty(bool dirty = true); diff --git a/src/bench/bench.h b/src/bench/bench.h index 22f06d8cb8..c4fcd80e33 100644 --- a/src/bench/bench.h +++ b/src/bench/bench.h @@ -18,16 +18,19 @@ /* * Usage: -static void CODE_TO_TIME(benchmark::Bench& bench) +static void NameOfYourBenchmarkFunction(benchmark::Bench& bench) { - ... do any setup needed... - nanobench::Config().run([&] { - ... do stuff you want to time... + ...do any setup needed... + + bench.run([&] { + ...do stuff you want to time; refer to src/bench/nanobench.h + for more information and the options that can be passed here... }); - ... do any cleanup needed... + + ...do any cleanup needed... } -BENCHMARK(CODE_TO_TIME); +BENCHMARK(NameOfYourBenchmarkFunction); */ @@ -55,7 +58,8 @@ public: static void RunAll(const Args& args); }; -} +} // namespace benchmark + // BENCHMARK(foo) expands to: benchmark::BenchRunner bench_11foo("foo", foo); #define BENCHMARK(n) \ benchmark::BenchRunner PASTE2(bench_, PASTE2(__LINE__, n))(STRINGIZE(n), n); diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 135659f87f..aab777cac1 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -16,11 +16,11 @@ static void SetupBenchArgs(ArgsManager& argsman) { SetupHelpOptions(argsman); - argsman.AddArg("-list", "List benchmarks without executing them", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-asymptote=n1,n2,n3,...", "Test asymptotic growth of the runtime of an algorithm, if supported by the benchmark", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-asymptote=n1,n2,n3,...", strprintf("Test asymptotic growth of the runtime of an algorithm, if supported by the benchmark"), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-output_csv=<output.csv>", "Generate CSV file with the most important benchmark results.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-output_json=<output.json>", "Generate JSON file with all benchmark results.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-list", "List benchmarks without executing them", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-output_csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-output_json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } // parses a comma separated list like "10,20,30,50" diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 4f6e1122b8..8703a1cf94 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -25,7 +25,6 @@ static void DuplicateInputs(benchmark::Bench& bench) CMutableTransaction naughtyTx{}; LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(testing_setup->m_node.chainman->ActiveChain())); CBlockIndex* pindexPrev = testing_setup->m_node.chainman->ActiveChain().Tip(); assert(pindexPrev != nullptr); block.nBits = GetNextWorkRequired(pindexPrev, &block, chainparams.GetConsensus()); diff --git a/src/bench/peer_eviction.cpp b/src/bench/peer_eviction.cpp new file mode 100644 index 0000000000..0469f0cb4c --- /dev/null +++ b/src/bench/peer_eviction.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <net.h> +#include <netaddress.h> +#include <random.h> +#include <test/util/net.h> +#include <test/util/setup_common.h> + +#include <algorithm> +#include <functional> +#include <vector> + +static void EvictionProtectionCommon( + benchmark::Bench& bench, + int num_candidates, + std::function<void(NodeEvictionCandidate&)> candidate_setup_fn) +{ + using Candidates = std::vector<NodeEvictionCandidate>; + FastRandomContext random_context{true}; + bench.warmup(100).epochIterations(1100); + + Candidates candidates{GetRandomNodeEvictionCandidates(num_candidates, random_context)}; + for (auto& c : candidates) { + candidate_setup_fn(c); + } + + std::vector<Candidates> copies{bench.epochs() * bench.epochIterations(), candidates}; + size_t i{0}; + bench.run([&] { + ProtectEvictionCandidatesByRatio(copies.at(i)); + ++i; + }); +} + +/* Benchmarks */ + +static void EvictionProtection0Networks250Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 250 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_network = NET_IPV4; + }); +} + +static void EvictionProtection1Networks250Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 250 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = false; + if (c.id >= 130 && c.id < 240) { // 110 Tor + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }); +} + +static void EvictionProtection2Networks250Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 250 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = false; + if (c.id >= 90 && c.id < 160) { // 70 Tor + c.m_network = NET_ONION; + } else if (c.id >= 170 && c.id < 250) { // 80 I2P + c.m_network = NET_I2P; + } else { + c.m_network = NET_IPV4; + } + }); +} + +static void EvictionProtection3Networks050Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 50 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 28 || c.id == 47); // 2 localhost + if (c.id >= 30 && c.id < 47) { // 17 I2P + c.m_network = NET_I2P; + } else if (c.id >= 24 && c.id < 28) { // 4 Tor + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }); +} + +static void EvictionProtection3Networks100Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 100 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id >= 55 && c.id < 60); // 5 localhost + if (c.id >= 70 && c.id < 80) { // 10 I2P + c.m_network = NET_I2P; + } else if (c.id >= 80 && c.id < 96) { // 16 Tor + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }); +} + +static void EvictionProtection3Networks250Candidates(benchmark::Bench& bench) +{ + EvictionProtectionCommon( + bench, + 250 /* num_candidates */, + [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id >= 140 && c.id < 160); // 20 localhost + if (c.id >= 170 && c.id < 180) { // 10 I2P + c.m_network = NET_I2P; + } else if (c.id >= 190 && c.id < 240) { // 50 Tor + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }); +} + +// Candidate numbers used for the benchmarks: +// - 50 candidates simulates a possible use of -maxconnections +// - 100 candidates approximates an average node with default settings +// - 250 candidates is the number of peers reported by operators of busy nodes + +// No disadvantaged networks, with 250 eviction candidates. +BENCHMARK(EvictionProtection0Networks250Candidates); + +// 1 disadvantaged network (Tor) with 250 eviction candidates. +BENCHMARK(EvictionProtection1Networks250Candidates); + +// 2 disadvantaged networks (I2P, Tor) with 250 eviction candidates. +BENCHMARK(EvictionProtection2Networks250Candidates); + +// 3 disadvantaged networks (I2P/localhost/Tor) with 50/100/250 eviction candidates. +BENCHMARK(EvictionProtection3Networks050Candidates); +BENCHMARK(EvictionProtection3Networks100Candidates); +BENCHMARK(EvictionProtection3Networks250Candidates); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index c5840b2098..7a5f945511 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -40,6 +40,7 @@ UrlDecodeFn* const URL_DECODE = urlDecode; static const char DEFAULT_RPCCONNECT[] = "127.0.0.1"; static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900; +static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; static const bool DEFAULT_NAMED=false; static const int CONTINUE_EXECUTION=-1; static constexpr int8_t UNKNOWN_NETWORK{-1}; @@ -73,6 +74,7 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-rpcwaittimeout=<n>", strprintf("Timeout in seconds to wait for the RPC server to start, or 0 for no timeout. (default: %d)", DEFAULT_WAIT_CLIENT_TIMEOUT), ArgsManager::ALLOW_INT, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password. When combined with -stdinwalletpassphrase, -stdinrpcpass consumes the first line, and -stdinwalletpassphrase consumes the second.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -794,6 +796,9 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str UniValue response(UniValue::VOBJ); // Execute and handle connection failures with -rpcwait. const bool fWait = gArgs.GetBoolArg("-rpcwait", false); + const int timeout = gArgs.GetArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT); + const auto deadline{GetTime<std::chrono::microseconds>() + 1s * timeout}; + do { try { response = CallRPC(rh, strMethod, args, rpcwallet); @@ -804,11 +809,12 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str } } break; // Connection succeeded, no need to retry. - } catch (const CConnectionFailed&) { - if (fWait) { - UninterruptibleSleep(std::chrono::milliseconds{1000}); + } catch (const CConnectionFailed& e) { + const auto now{GetTime<std::chrono::microseconds>()}; + if (fWait && (timeout <= 0 || now < deadline)) { + UninterruptibleSleep(1s); } else { - throw; + throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what())); } } } while (fWait); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 93ac4b8f7e..3fc87ae1ff 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -506,11 +506,12 @@ static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOut tx.vout.erase(tx.vout.begin() + outIdx); } -static const unsigned int N_SIGHASH_OPTS = 6; +static const unsigned int N_SIGHASH_OPTS = 7; static const struct { const char *flagStr; int flags; } sighashOptions[N_SIGHASH_OPTS] = { + {"DEFAULT", SIGHASH_DEFAULT}, {"ALL", SIGHASH_ALL}, {"NONE", SIGHASH_NONE}, {"SINGLE", SIGHASH_SINGLE}, diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp index af07b28d3d..f534aecc19 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -7,28 +7,19 @@ #endif #include <arith_uint256.h> +#include <chain.h> +#include <chainparams.h> +#include <chainparamsbase.h> #include <clientversion.h> -#include <coins.h> -#include <consensus/consensus.h> #include <core_io.h> -#include <key_io.h> -#include <policy/rbf.h> -#include <primitives/transaction.h> -#include <script/script.h> -#include <script/sign.h> -#include <script/signingprovider.h> -#include <univalue.h> -#include <util/moneystr.h> -#include <util/rbf.h> -#include <util/strencodings.h> -#include <util/string.h> +#include <streams.h> #include <util/system.h> #include <util/translation.h> #include <atomic> +#include <cstdio> #include <functional> #include <memory> -#include <stdio.h> #include <thread> #include <boost/algorithm/string.hpp> @@ -43,35 +34,29 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman) argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddCommand("grind", "Perform proof of work on hex header string"); + SetupChainParamsBaseOptions(argsman); } // This function returns either one of EXIT_ codes when it's expected to stop the process or // CONTINUE_EXECUTION when it's expected to continue further. -static int AppInitUtil(int argc, char* argv[]) +static int AppInitUtil(ArgsManager& args, int argc, char* argv[]) { - SetupBitcoinUtilArgs(gArgs); + SetupBitcoinUtilArgs(args); std::string error; - if (!gArgs.ParseParameters(argc, argv, error)) { + if (!args.ParseParameters(argc, argv, error)) { tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); return EXIT_FAILURE; } - // Check for chain settings (Params() calls are only valid after this clause) - try { - SelectParams(gArgs.GetChainName()); - } catch (const std::exception& e) { - tfm::format(std::cerr, "Error: %s\n", e.what()); - return EXIT_FAILURE; - } - - if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { + if (HelpRequested(args) || args.IsArgSet("-version")) { // First part of help message is specific to this utility std::string strUsage = PACKAGE_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n"; - if (!gArgs.IsArgSet("-version")) { + if (!args.IsArgSet("-version")) { strUsage += "\n" "Usage: bitcoin-util [options] [commands] Do stuff\n"; - strUsage += "\n" + gArgs.GetHelpMessage(); + strUsage += "\n" + args.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage); @@ -82,6 +67,15 @@ static int AppInitUtil(int argc, char* argv[]) } return EXIT_SUCCESS; } + + // Check for chain settings (Params() calls are only valid after this clause) + try { + SelectParams(args.GetChainName()); + } catch (const std::exception& e) { + tfm::format(std::cerr, "Error: %s\n", e.what()); + return EXIT_FAILURE; + } + return CONTINUE_EXECUTION; } @@ -111,17 +105,17 @@ static void grind_task(uint32_t nBits, CBlockHeader& header_orig, uint32_t offse } } -static int Grind(int argc, char* argv[], std::string& strPrint) +static int Grind(const std::vector<std::string>& args, std::string& strPrint) { - if (argc != 1) { + if (args.size() != 1) { strPrint = "Must specify block header to grind"; - return 1; + return EXIT_FAILURE; } CBlockHeader header; - if (!DecodeHexBlockHeader(header, argv[0])) { + if (!DecodeHexBlockHeader(header, args[0])) { strPrint = "Could not decode block header"; - return 1; + return EXIT_FAILURE; } uint32_t nBits = header.nBits; @@ -137,49 +131,13 @@ static int Grind(int argc, char* argv[], std::string& strPrint) } if (!found) { strPrint = "Could not satisfy difficulty target"; - return 1; + return EXIT_FAILURE; } CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss << header; strPrint = HexStr(ss); - return 0; -} - -static int CommandLineUtil(int argc, char* argv[]) -{ - if (argc <= 1) return 1; - - std::string strPrint; - int nRet = 0; - - try { - while (argc > 1 && IsSwitchChar(argv[1][0]) && (argv[1][1] != 0)) { - --argc; - ++argv; - } - - char* command = argv[1]; - if (strcmp(command, "grind") == 0) { - nRet = Grind(argc-2, argv+2, strPrint); - } else { - strPrint = strprintf("Unknown command %s", command); - nRet = 1; - } - } - catch (const std::exception& e) { - strPrint = std::string("error: ") + e.what(); - nRet = EXIT_FAILURE; - } - catch (...) { - PrintExceptionContinue(nullptr, "CommandLineUtil()"); - throw; - } - - if (strPrint != "") { - tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint); - } - return nRet; + return EXIT_SUCCESS; } #ifdef WIN32 @@ -193,14 +151,15 @@ __declspec(dllexport) int main(int argc, char* argv[]) int main(int argc, char* argv[]) #endif { + ArgsManager& args = gArgs; SetupEnvironment(); try { - int ret = AppInitUtil(argc, argv); - if (ret != CONTINUE_EXECUTION) + int ret = AppInitUtil(args, argc, argv); + if (ret != CONTINUE_EXECUTION) { return ret; - } - catch (const std::exception& e) { + } + } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInitUtil()"); return EXIT_FAILURE; } catch (...) { @@ -208,14 +167,29 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } + const auto cmd = args.GetCommand(); + if (!cmd) { + tfm::format(std::cerr, "Error: must specify a command\n"); + return EXIT_FAILURE; + } + int ret = EXIT_FAILURE; + std::string strPrint; try { - ret = CommandLineUtil(argc, argv); - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, "CommandLineUtil()"); + if (cmd->command == "grind") { + ret = Grind(cmd->args, strPrint); + } else { + assert(false); // unknown command should be caught earlier + } + } catch (const std::exception& e) { + strPrint = std::string("error: ") + e.what(); } catch (...) { - PrintExceptionContinue(nullptr, "CommandLineUtil()"); + strPrint = "unknown error"; + } + + if (strPrint != "") { + tfm::format(ret == 0 ? std::cout : std::cerr, "%s\n", strPrint); } + return ret; } diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index b84d909b07..765954c92e 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -33,11 +33,11 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - argsman.AddCommand("info", "Get wallet info", OptionsCategory::COMMANDS); - argsman.AddCommand("create", "Create new wallet file", OptionsCategory::COMMANDS); - argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", OptionsCategory::COMMANDS); - argsman.AddCommand("dump", "Print out all of the wallet key-value records", OptionsCategory::COMMANDS); - argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", OptionsCategory::COMMANDS); + argsman.AddCommand("info", "Get wallet info"); + argsman.AddCommand("create", "Create new wallet file"); + argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental."); + argsman.AddCommand("dump", "Print out all of the wallet key-value records"); + argsman.AddCommand("createfromdump", "Create new wallet file from dumped records"); } static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index cf9e4fad44..654679af27 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -112,8 +112,8 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) util::ThreadSetInternalName("init"); // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() - SetupServerArgs(node); ArgsManager& args = *Assert(node.args); + SetupServerArgs(args); std::string error; if (!args.ParseParameters(argc, argv, error)) { return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error))); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index fdaadeed4a..58a27e053b 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,9 +7,9 @@ #include <chainparamsseeds.h> #include <consensus/merkle.h> +#include <deploymentinfo.h> #include <hash.h> // for signet block challenge hash #include <util/system.h> -#include <versionbitsinfo.h> #include <assert.h> diff --git a/src/chainparams.h b/src/chainparams.h index 5c2351eea6..4faa6f8d06 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -8,11 +8,13 @@ #include <chainparamsbase.h> #include <consensus/params.h> +#include <netaddress.h> #include <primitives/block.h> #include <protocol.h> #include <util/hash_type.h> #include <memory> +#include <string> #include <vector> typedef std::map<int, uint256> MapCheckpoints; @@ -80,6 +82,15 @@ public: const Consensus::Params& GetConsensus() const { return consensus; } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } uint16_t GetDefaultPort() const { return nDefaultPort; } + uint16_t GetDefaultPort(Network net) const + { + return net == NET_I2P ? I2P_SAM31_PORT : GetDefaultPort(); + } + uint16_t GetDefaultPort(const std::string& addr) const + { + CNetAddr a; + return a.SetSpecial(addr) ? GetDefaultPort(a.GetNetwork()) : GetDefaultPort(); + } const CBlock& GenesisBlock() const { return genesis; } /** Default value for -checkmempool and -checkblockindex argument */ diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 08587e2b37..a22529c386 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -683,14 +683,14 @@ static const uint8_t chainparams_seed_main[] = { 0x04,0x20,0x98,0xc6,0x44,0x27,0x90,0x41,0xa6,0x98,0xf9,0x25,0x6c,0x59,0x0f,0x06,0x6d,0x44,0x59,0x0e,0xb2,0x46,0xb0,0xa4,0x37,0x88,0x69,0x8f,0xc1,0x32,0xcd,0x9f,0x15,0xd7,0x20,0x8d, 0x04,0x20,0xaa,0x3a,0x16,0x86,0xea,0x59,0x09,0x04,0x78,0xe5,0x10,0x92,0xe1,0x1d,0xad,0xf7,0x56,0x2b,0xac,0xb0,0x97,0x29,0x63,0x30,0xf4,0x1b,0xcf,0xde,0xf3,0x28,0x0a,0x29,0x20,0x8d, 0x04,0x20,0xbc,0x27,0xae,0x89,0xc1,0x67,0x73,0x0a,0x08,0x02,0xdf,0xb7,0xcc,0x94,0xc7,0x9f,0xf4,0x72,0x7a,0x9b,0x20,0x0c,0x5c,0x11,0x3d,0x22,0xd6,0x13,0x88,0x66,0x74,0xbf,0x20,0x8d, - 0x05,0x20,0xfe,0x97,0xba,0x09,0x2a,0xa4,0x85,0x10,0xa1,0x04,0x7b,0x88,0x7a,0x5a,0x06,0x53,0x71,0x93,0x3b,0xf9,0xa2,0x2f,0xd9,0xe3,0x8f,0xa5,0xa2,0xac,0x1e,0x6c,0x6c,0x8c,0x20,0x8d, - 0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x20,0x8d, - 0x05,0x20,0x31,0x0f,0x30,0x0b,0x9d,0x70,0x0c,0x7c,0xf7,0x98,0x7e,0x1c,0xf4,0x33,0xdc,0x64,0x17,0xf7,0x00,0x7a,0x0c,0x04,0xb5,0x83,0xfc,0x5f,0xa6,0x52,0x39,0x79,0x63,0x87,0x20,0x8d, - 0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x20,0x8d, - 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x20,0x8d, - 0x05,0x20,0x7a,0x65,0xf7,0x47,0x42,0x9d,0x66,0x42,0x3b,0xb3,0xa7,0x03,0x6c,0x46,0x78,0x19,0x28,0x78,0x1e,0xa3,0x7c,0x67,0x44,0xb7,0x83,0x05,0xe3,0xfe,0xa5,0xe4,0x0a,0x6e,0x20,0x8d, - 0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x20,0x8d, - 0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x20,0x8d, + 0x05,0x20,0xfe,0x97,0xba,0x09,0x2a,0xa4,0x85,0x10,0xa1,0x04,0x7b,0x88,0x7a,0x5a,0x06,0x53,0x71,0x93,0x3b,0xf9,0xa2,0x2f,0xd9,0xe3,0x8f,0xa5,0xa2,0xac,0x1e,0x6c,0x6c,0x8c,0x00,0x00, + 0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x00,0x00, + 0x05,0x20,0x31,0x0f,0x30,0x0b,0x9d,0x70,0x0c,0x7c,0xf7,0x98,0x7e,0x1c,0xf4,0x33,0xdc,0x64,0x17,0xf7,0x00,0x7a,0x0c,0x04,0xb5,0x83,0xfc,0x5f,0xa6,0x52,0x39,0x79,0x63,0x87,0x00,0x00, + 0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x00,0x00, + 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x00,0x00, + 0x05,0x20,0x7a,0x65,0xf7,0x47,0x42,0x9d,0x66,0x42,0x3b,0xb3,0xa7,0x03,0x6c,0x46,0x78,0x19,0x28,0x78,0x1e,0xa3,0x7c,0x67,0x44,0xb7,0x83,0x05,0xe3,0xfe,0xa5,0xe4,0x0a,0x6e,0x00,0x00, + 0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x00,0x00, + 0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x00,0x00, }; static const uint8_t chainparams_seed_test[] = { diff --git a/src/coins.cpp b/src/coins.cpp index d52851cadd..ce0b131de6 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -13,7 +13,7 @@ bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return f uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } -CCoinsViewCursor *CCoinsView::Cursor() const { return nullptr; } +std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; } bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { @@ -28,7 +28,7 @@ uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } -CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } +std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {} diff --git a/src/coins.h b/src/coins.h index 816b4864a3..3151a260d9 100644 --- a/src/coins.h +++ b/src/coins.h @@ -180,7 +180,7 @@ public: virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); //! Get a cursor to iterate over the whole state - virtual CCoinsViewCursor *Cursor() const; + virtual std::unique_ptr<CCoinsViewCursor> Cursor() const; //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} @@ -204,7 +204,7 @@ public: std::vector<uint256> GetHeadBlocks() const override; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor *Cursor() const override; + std::unique_ptr<CCoinsViewCursor> Cursor() const override; size_t EstimateSize() const override; }; @@ -237,7 +237,7 @@ public: uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor* Cursor() const override { + std::unique_ptr<CCoinsViewCursor> Cursor() const override { throw std::logic_error("CCoinsViewCache cursor iteration not supported."); } diff --git a/src/consensus/params.h b/src/consensus/params.h index 28c95e0884..9205cfee87 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -11,13 +11,27 @@ namespace Consensus { -enum DeploymentPos -{ +/** + * A buried deployment is one where the height of the activation has been hardcoded into + * the client implementation long after the consensus change has activated. See BIP 90. + */ +enum BuriedDeployment : int16_t { + // buried deployments get negative values to avoid overlap with DeploymentPos + DEPLOYMENT_HEIGHTINCB = std::numeric_limits<int16_t>::min(), + DEPLOYMENT_CLTV, + DEPLOYMENT_DERSIG, + DEPLOYMENT_CSV, + DEPLOYMENT_SEGWIT, +}; +constexpr bool ValidDeployment(BuriedDeployment dep) { return DEPLOYMENT_HEIGHTINCB <= dep && dep <= DEPLOYMENT_SEGWIT; } + +enum DeploymentPos : uint16_t { DEPLOYMENT_TESTDUMMY, DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342) - // NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp + // NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp MAX_VERSION_BITS_DEPLOYMENTS }; +constexpr bool ValidDeployment(DeploymentPos dep) { return DEPLOYMENT_TESTDUMMY <= dep && dep <= DEPLOYMENT_TAPROOT; } /** * Struct for each individual consensus rule change using BIP9. @@ -100,7 +114,25 @@ struct Params { */ bool signet_blocks{false}; std::vector<uint8_t> signet_challenge; + + int DeploymentHeight(BuriedDeployment dep) const + { + switch (dep) { + case DEPLOYMENT_HEIGHTINCB: + return BIP34Height; + case DEPLOYMENT_CLTV: + return BIP65Height; + case DEPLOYMENT_DERSIG: + return BIP66Height; + case DEPLOYMENT_CSV: + return CSVHeight; + case DEPLOYMENT_SEGWIT: + return SegwitHeight; + } // no default case, so the compiler can warn about missing cases + return std::numeric_limits<int>::max(); + } }; + } // namespace Consensus #endif // BITCOIN_CONSENSUS_PARAMS_H diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index f595f16eab..88d8da6ed5 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -20,6 +20,15 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) return true; if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime)) return true; + + // Even if tx.nLockTime isn't satisfied by nBlockHeight/nBlockTime, a + // transaction is still considered final if all inputs' nSequence == + // SEQUENCE_FINAL (0xffffffff), in which case nLockTime is ignored. + // + // Because of this behavior OP_CHECKLOCKTIMEVERIFY/CheckLockTime() will + // also check that the spending input's nSequence != SEQUENCE_FINAL, + // ensuring that an unsatisfied nLockTime value will actually cause + // IsFinalTx() to return false here: for (const auto& txin : tx.vin) { if (!(txin.nSequence == CTxIn::SEQUENCE_FINAL)) return false; diff --git a/src/core_read.cpp b/src/core_read.cpp index b5fc93886d..6108961010 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -260,6 +260,7 @@ int ParseSighashString(const UniValue& sighash) int hash_type = SIGHASH_ALL; if (!sighash.isNull()) { static std::map<std::string, int> map_sighash_values = { + {std::string("DEFAULT"), int(SIGHASH_DEFAULT)}, {std::string("ALL"), int(SIGHASH_ALL)}, {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, {std::string("NONE"), int(SIGHASH_NONE)}, diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp new file mode 100644 index 0000000000..030a7806de --- /dev/null +++ b/src/deploymentinfo.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2016-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <deploymentinfo.h> + +#include <consensus/params.h> + +const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { + { + /*.name =*/ "testdummy", + /*.gbt_force =*/ true, + }, + { + /*.name =*/ "taproot", + /*.gbt_force =*/ true, + }, +}; + +std::string DeploymentName(Consensus::BuriedDeployment dep) +{ + assert(ValidDeployment(dep)); + switch (dep) { + case Consensus::DEPLOYMENT_HEIGHTINCB: + return "bip34"; + case Consensus::DEPLOYMENT_CLTV: + return "bip65"; + case Consensus::DEPLOYMENT_DERSIG: + return "bip66"; + case Consensus::DEPLOYMENT_CSV: + return "csv"; + case Consensus::DEPLOYMENT_SEGWIT: + return "segwit"; + } // no default case, so the compiler can warn about missing cases + return ""; +} diff --git a/src/deploymentinfo.h b/src/deploymentinfo.h new file mode 100644 index 0000000000..63d58a7da2 --- /dev/null +++ b/src/deploymentinfo.h @@ -0,0 +1,29 @@ +// Copyright (c) 2016-2018 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_DEPLOYMENTINFO_H +#define BITCOIN_DEPLOYMENTINFO_H + +#include <consensus/params.h> + +#include <string> + +struct VBDeploymentInfo { + /** Deployment name */ + const char *name; + /** Whether GBT clients can safely ignore this rule in simplified usage */ + bool gbt_force; +}; + +extern const VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; + +std::string DeploymentName(Consensus::BuriedDeployment dep); + +inline std::string DeploymentName(Consensus::DeploymentPos pos) +{ + assert(Consensus::ValidDeployment(pos)); + return VersionBitsDeploymentInfo[pos].name; +} + +#endif // BITCOIN_DEPLOYMENTINFO_H diff --git a/src/deploymentstatus.cpp b/src/deploymentstatus.cpp new file mode 100644 index 0000000000..9007800421 --- /dev/null +++ b/src/deploymentstatus.cpp @@ -0,0 +1,17 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <deploymentstatus.h> + +#include <consensus/params.h> +#include <versionbits.h> + +VersionBitsCache g_versionbitscache; + +/* Basic sanity checking for BuriedDeployment/DeploymentPos enums and + * ValidDeployment check */ + +static_assert(ValidDeployment(Consensus::DEPLOYMENT_TESTDUMMY), "sanity check of DeploymentPos failed (TESTDUMMY not valid)"); +static_assert(!ValidDeployment(Consensus::MAX_VERSION_BITS_DEPLOYMENTS), "sanity check of DeploymentPos failed (MAX value considered valid)"); +static_assert(!ValidDeployment(static_cast<Consensus::BuriedDeployment>(Consensus::DEPLOYMENT_TESTDUMMY)), "sanity check of BuriedDeployment failed (overlaps with DeploymentPos)"); diff --git a/src/deploymentstatus.h b/src/deploymentstatus.h new file mode 100644 index 0000000000..f95c5996f5 --- /dev/null +++ b/src/deploymentstatus.h @@ -0,0 +1,55 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_DEPLOYMENTSTATUS_H +#define BITCOIN_DEPLOYMENTSTATUS_H + +#include <chain.h> +#include <versionbits.h> + +#include <limits> + +/** Global cache for versionbits deployment status */ +extern VersionBitsCache g_versionbitscache; + +/** Determine if a deployment is active for the next block */ +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::BuriedDeployment dep) +{ + assert(Consensus::ValidDeployment(dep)); + return (pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1) >= params.DeploymentHeight(dep); +} + +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep) +{ + assert(Consensus::ValidDeployment(dep)); + return ThresholdState::ACTIVE == g_versionbitscache.State(pindexPrev, params, dep); +} + +/** Determine if a deployment is active for this block */ +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::BuriedDeployment dep) +{ + assert(Consensus::ValidDeployment(dep)); + return index.nHeight >= params.DeploymentHeight(dep); +} + +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::DeploymentPos dep) +{ + assert(Consensus::ValidDeployment(dep)); + return DeploymentActiveAfter(index.pprev, params, dep); +} + +/** Determine if a deployment is enabled (can ever be active) */ +inline bool DeploymentEnabled(const Consensus::Params& params, Consensus::BuriedDeployment dep) +{ + assert(Consensus::ValidDeployment(dep)); + return params.DeploymentHeight(dep) != std::numeric_limits<int>::max(); +} + +inline bool DeploymentEnabled(const Consensus::Params& params, Consensus::DeploymentPos dep) +{ + assert(Consensus::ValidDeployment(dep)); + return params.vDeployments[dep].nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE; +} + +#endif // BITCOIN_DEPLOYMENTSTATUS_H diff --git a/src/external_signer.cpp b/src/external_signer.cpp index f16d21fa60..d6388b759a 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -13,9 +13,7 @@ #include <string> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - -ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name): m_command(command), m_fingerprint(fingerprint), m_chain(chain), m_name(name) {} +ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name): m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name) {} const std::string ExternalSigner::NetworkArg() const { @@ -55,7 +53,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS if (model_field.isStr() && model_field.getValStr() != "") { name += model_field.getValStr(); } - signers.push_back(ExternalSigner(command, fingerprintStr, chain, name)); + signers.push_back(ExternalSigner(command, chain, fingerprintStr, name)); } return true; } @@ -116,5 +114,3 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str return true; } - -#endif // ENABLE_EXTERNAL_SIGNER diff --git a/src/external_signer.h b/src/external_signer.h index b3b202091a..e40fd7f010 100644 --- a/src/external_signer.h +++ b/src/external_signer.h @@ -11,8 +11,6 @@ #include <string> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - struct PartiallySignedTransaction; //! Enables interaction with an external signing device or service, such as @@ -23,24 +21,24 @@ private: //! The command which handles interaction with the external signer. std::string m_command; + //! Bitcoin mainnet, testnet, etc + std::string m_chain; + + const std::string NetworkArg() const; + public: //! @param[in] command the command which handles interaction with the external signer //! @param[in] fingerprint master key fingerprint of the signer //! @param[in] chain "main", "test", "regtest" or "signet" //! @param[in] name device name - ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name); + ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name); //! Master key fingerprint of the signer std::string m_fingerprint; - //! Bitcoin mainnet, testnet, etc - std::string m_chain; - //! Name of signer std::string m_name; - const std::string NetworkArg() const; - //! Obtain a list of signers. Calls `<command> enumerate`. //! @param[in] command the command which handles interaction with the external signer //! @param[in,out] signers vector to which new signers (with a unique master key fingerprint) are added @@ -65,6 +63,4 @@ public: bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error); }; -#endif // ENABLE_EXTERNAL_SIGNER - #endif // BITCOIN_EXTERNAL_SIGNER_H diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 45c049c3be..8741ad9b86 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -83,7 +83,7 @@ public: bool Enqueue(WorkItem* item) { LOCK(cs); - if (queue.size() >= maxDepth) { + if (!running || queue.size() >= maxDepth) { return false; } queue.emplace_back(std::unique_ptr<WorkItem>(item)); @@ -99,7 +99,7 @@ public: WAIT_LOCK(cs, lock); while (running && queue.empty()) cond.wait(lock); - if (!running) + if (!running && queue.empty()) break; i = std::move(queue.front()); queue.pop_front(); @@ -136,7 +136,7 @@ static struct evhttp* eventHTTP = nullptr; //! List of subnets to allow RPC connections from static std::vector<CSubNet> rpc_allow_subnets; //! Work queue for handling longer requests off the event loop thread -static WorkQueue<HTTPClosure>* workQueue = nullptr; +static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr}; //! Handlers for (sub)paths static std::vector<HTTPPathHandler> pathHandlers; //! Bound listening sockets @@ -256,10 +256,10 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Dispatch to worker thread if (i != iend) { std::unique_ptr<HTTPWorkItem> item(new HTTPWorkItem(std::move(hreq), path, i->handler)); - assert(workQueue); - if (workQueue->Enqueue(item.get())) + assert(g_work_queue); + if (g_work_queue->Enqueue(item.get())) { item.release(); /* if true, queue took ownership */ - else { + } else { LogPrintf("WARNING: request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting\n"); item->req->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded"); } @@ -392,7 +392,7 @@ bool InitHTTPServer() int workQueueDepth = std::max((long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); - workQueue = new WorkQueue<HTTPClosure>(workQueueDepth); + g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth); // transfer ownership to eventBase/HTTP via .release() eventBase = base_ctr.release(); eventHTTP = http_ctr.release(); @@ -424,7 +424,7 @@ void StartHTTPServer() g_thread_http = std::thread(ThreadHTTP, eventBase); for (int i = 0; i < rpcThreads; i++) { - g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue, i); + g_thread_http_workers.emplace_back(HTTPWorkQueueRun, g_work_queue.get(), i); } } @@ -435,21 +435,20 @@ void InterruptHTTPServer() // Reject requests on current connections evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr); } - if (workQueue) - workQueue->Interrupt(); + if (g_work_queue) { + g_work_queue->Interrupt(); + } } void StopHTTPServer() { LogPrint(BCLog::HTTP, "Stopping HTTP server\n"); - if (workQueue) { + if (g_work_queue) { LogPrint(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n"); - for (auto& thread: g_thread_http_workers) { + for (auto& thread : g_thread_http_workers) { thread.join(); } g_thread_http_workers.clear(); - delete workQueue; - workQueue = nullptr; } // Unlisten sockets, these are what make the event loop running, which means // that after this and all connections are closed the event loop will quit. @@ -469,6 +468,7 @@ void StopHTTPServer() event_base_free(eventBase); eventBase = nullptr; } + g_work_queue.reset(); LogPrint(BCLog::HTTP, "Stopped HTTP server\n"); } diff --git a/src/i2p.cpp b/src/i2p.cpp index 2ae164633b..5e7e42fb77 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -159,7 +159,7 @@ bool Session::Accept(Connection& conn) const std::string& peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); - conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); + conn.peer = CService(DestB64ToAddr(peer_dest), I2P_SAM31_PORT); return true; } @@ -172,6 +172,13 @@ bool Session::Accept(Connection& conn) bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) { + // Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy + // when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT. + if (to.GetPort() != I2P_SAM31_PORT) { + proxy_error = false; + return false; + } + proxy_error = true; std::string session_id; @@ -366,7 +373,7 @@ void Session::CreateIfNotCreatedAlready() SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", session_id, private_key_b64)); - m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); + m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT); m_session_id = session_id; m_control_sock = std::move(sock); diff --git a/src/index/base.cpp b/src/index/base.cpp index 3a61af28b7..6fd2701e2e 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -340,7 +340,6 @@ void BaseIndex::Interrupt() bool BaseIndex::Start(CChainState& active_chainstate) { - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); m_chainstate = &active_chainstate; // Need to register this ValidationInterface before running Init(), so that // callbacks are not missed if Init sets m_synced to true. diff --git a/src/init.cpp b/src/init.cpp index 7f64b1acfa..9afd76d62d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -16,6 +16,7 @@ #include <chain.h> #include <chainparams.h> #include <compat/sanity.h> +#include <deploymentstatus.h> #include <fs.h> #include <hash.h> #include <httprpc.h> @@ -283,7 +284,7 @@ void Shutdown(NodeContext& node) init::UnsetGlobals(); node.mempool.reset(); node.fee_estimator.reset(); - node.chainman = nullptr; + node.chainman.reset(); node.scheduler.reset(); try { @@ -347,12 +348,8 @@ static void OnRPCStopped() LogPrint(BCLog::RPC, "RPC stopped.\n"); } -void SetupServerArgs(NodeContext& node) +void SetupServerArgs(ArgsManager& argsman) { - assert(!node.args); - node.args = &gArgs; - ArgsManager& argsman = *node.args; - SetupHelpOptions(argsman); argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now @@ -445,7 +442,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1165,7 +1162,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.addrman); node.addrman = std::make_unique<CAddrMan>(); assert(!node.banman); - node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist.dat", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); + node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); node.connman = std::make_unique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), *node.addrman, args.GetBoolArg("-networkactive", true)); @@ -1179,8 +1176,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio); assert(!node.chainman); - node.chainman = &g_chainman; - ChainstateManager& chainman = *Assert(node.chainman); + node.chainman = std::make_unique<ChainstateManager>(); + ChainstateManager& chainman = *node.chainman; assert(!node.peerman); node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), @@ -1352,7 +1349,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const int64_t load_block_index_start_time = GetTimeMillis(); try { LOCK(cs_main); - chainman.InitializeChainstate(*Assert(node.mempool)); + chainman.InitializeChainstate(Assert(node.mempool.get())); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; @@ -1376,7 +1373,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // block file from disk. // Note that it also sets fReindex based on the disk flag! // From here on out fReindex and fReset mean something different! - if (!chainman.LoadBlockIndex(chainparams)) { + if (!chainman.LoadBlockIndex()) { if (ShutdownRequested()) break; strLoadError = _("Error loading block database"); break; @@ -1385,7 +1382,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). if (!chainman.BlockIndex().empty() && - !g_chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); } @@ -1400,7 +1397,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // If we're not mid-reindex (based on disk + args), add a genesis block on disk // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !::ChainstateActive().LoadGenesisBlock(chainparams)) { + if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { strLoadError = _("Error initializing block database"); break; } @@ -1431,7 +1428,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!chainstate->ReplayBlocks(chainparams)) { + if (!chainstate->ReplayBlocks()) { strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); failed_chainstate_init = true; break; @@ -1443,7 +1440,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (!is_coinsview_empty(chainstate)) { // LoadChainTip initializes the chain based on CoinsTip()'s best block - if (!chainstate->LoadChainTip(chainparams)) { + if (!chainstate->LoadChainTip()) { strLoadError = _("Error initializing block database"); failed_chainstate_init = true; break; // out of the per-chainstate loop @@ -1465,7 +1462,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LOCK(cs_main); auto chainstates{chainman.GetAll()}; if (std::any_of(chainstates.begin(), chainstates.end(), - [&chainparams](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(chainparams); })) { + [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), chainparams.GetConsensus().SegwitHeight); break; @@ -1549,21 +1546,21 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex); - if (!g_txindex->Start(::ChainstateActive())) { + if (!g_txindex->Start(chainman.ActiveChainstate())) { return false; } } for (const auto& filter_type : g_enabled_filter_types) { InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex); - if (!GetBlockFilterIndex(filter_type)->Start(::ChainstateActive())) { + if (!GetBlockFilterIndex(filter_type)->Start(chainman.ActiveChainstate())) { return false; } } if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { g_coin_stats_index = std::make_unique<CoinStatsIndex>(/* cache size */ 0, false, fReindex); - if (!g_coin_stats_index->Start(::ChainstateActive())) { + if (!g_coin_stats_index->Start(chainman.ActiveChainstate())) { return false; } } @@ -1591,7 +1588,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } - if (chainparams.GetConsensus().SegwitHeight != std::numeric_limits<int>::max()) { + if (DeploymentEnabled(chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { // Advertise witness capabilities. // The option to not set NODE_WITNESS is only used in the tests and should be removed. nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS); @@ -1611,7 +1608,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. boost::signals2::connection block_notify_genesis_wait_connection; - if (::ChainActive().Tip() == nullptr) { + if (chainman.ActiveChain().Tip() == nullptr) { block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2)); } else { fHaveGenesis = true; @@ -1720,18 +1717,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return InitError(ResolveErrMsg("bind", bind_arg)); } - if (connOptions.onion_binds.empty()) { - connOptions.onion_binds.push_back(DefaultOnionServiceTarget()); - } - - if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) { - const auto bind_addr = connOptions.onion_binds.front(); - if (connOptions.onion_binds.size() > 1) { - InitWarning(strprintf(_("More than one onion bind address is provided. Using %s for the automatically created Tor onion service."), bind_addr.ToStringIPPort())); - } - StartTorControl(bind_addr); - } - for (const std::string& strBind : args.GetArgs("-whitebind")) { NetWhitebindPermissions whitebind; bilingual_str error; @@ -1739,6 +1724,27 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) connOptions.vWhiteBinds.push_back(whitebind); } + // If the user did not specify -bind= or -whitebind= then we bind + // on any address - 0.0.0.0 (IPv4) and :: (IPv6). + connOptions.bind_on_any = args.GetArgs("-bind").empty() && args.GetArgs("-whitebind").empty(); + + CService onion_service_target; + if (!connOptions.onion_binds.empty()) { + onion_service_target = connOptions.onion_binds.front(); + } else { + onion_service_target = DefaultOnionServiceTarget(); + connOptions.onion_binds.push_back(onion_service_target); + } + + if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) { + if (connOptions.onion_binds.size() > 1) { + InitWarning(strprintf(_("More than one onion bind address is provided. Using %s " + "for the automatically created Tor onion service."), + onion_service_target.ToStringIPPort())); + } + StartTorControl(onion_service_target); + } + for (const auto& net : args.GetArgs("-whitelist")) { NetWhitelistPermissions subnet; bilingual_str error; diff --git a/src/init.h b/src/init.h index 328eda9c7e..5af6930a16 100644 --- a/src/init.h +++ b/src/init.h @@ -20,9 +20,6 @@ struct NodeContext; namespace interfaces { struct BlockAndHeaderTipInfo; } -namespace boost { -class thread_group; -} // namespace boost /** Interrupt threads */ void Interrupt(NodeContext& node); @@ -69,7 +66,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info /** * Register all arguments with the ArgsManager */ -void SetupServerArgs(NodeContext& node); +void SetupServerArgs(ArgsManager& argsman); /** Returns licensing information (for -version) */ std::string LicenseInfo(); diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 49684ede83..6b6157c139 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -6,6 +6,7 @@ #include <interfaces/init.h> #include <interfaces/ipc.h> #include <node/context.h> +#include <util/system.h> #include <memory> @@ -20,6 +21,7 @@ public: : m_node(node), m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) { + m_node.args = &gArgs; m_node.init = this; } std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } diff --git a/src/init/bitcoind.cpp b/src/init/bitcoind.cpp index 1e17ce4d3c..1d4504c24f 100644 --- a/src/init/bitcoind.cpp +++ b/src/init/bitcoind.cpp @@ -4,6 +4,7 @@ #include <interfaces/init.h> #include <node/context.h> +#include <util/system.h> #include <memory> @@ -14,6 +15,7 @@ class BitcoindInit : public interfaces::Init public: BitcoindInit(NodeContext& node) : m_node(node) { + m_node.args = &gArgs; m_node.init = this; } NodeContext& m_node; diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 3395741b1b..7cac435e96 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -277,6 +277,9 @@ public: //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications& notifications) = 0; + + //! Check if Taproot has activated + virtual bool isTaprootActive() const = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 35b6160cea..77129423db 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -111,10 +111,8 @@ public: //! Disconnect node by id. virtual bool disconnectById(NodeId id) = 0; -#ifdef ENABLE_EXTERNAL_SIGNER //! List external signers virtual std::vector<ExternalSigner> externalSigners() = 0; -#endif //! Get total bytes recv. virtual int64_t getTotalBytesRecv() = 0; diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index a0cb2787b7..fb1febc11b 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -198,9 +198,9 @@ public: virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, + size_t* n_signed, PartiallySignedTransaction& psbtx, - bool& complete, - size_t* n_signed) = 0; + bool& complete) = 0; //! Get balances. virtual WalletBalances getBalances() = 0; diff --git a/src/key.cpp b/src/key.cpp index 5666adebb8..dcad386e77 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -7,10 +7,13 @@ #include <crypto/common.h> #include <crypto/hmac_sha512.h> +#include <hash.h> #include <random.h> #include <secp256k1.h> +#include <secp256k1_extrakeys.h> #include <secp256k1_recovery.h> +#include <secp256k1_schnorrsig.h> static secp256k1_context* secp256k1_context_sign = nullptr; @@ -258,6 +261,24 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) return true; } +bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256* aux) const +{ + assert(sig.size() == 64); + secp256k1_keypair keypair; + if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) return false; + if (merkle_root) { + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false; + unsigned char pubkey_bytes[32]; + if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false; + uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); + if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false; + } + bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, secp256k1_nonce_function_bip340, aux ? (void*)aux->data() : nullptr); + memory_cleanse(&keypair, sizeof(keypair)); + return ret; +} + bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) { if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) return false; @@ -128,6 +128,18 @@ public: */ bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const; + /** + * Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this, + * optionally tweaked by *merkle_root. Additional nonce entropy can be provided through + * aux. + * + * When merkle_root is not nullptr, this results in a signature with a modified key as + * specified in BIP341: + * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G + * - Otherwise: key + H_TapTweak(pubkey || *merkle_root) + */ + bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const; + //! Derive BIP32 child key. bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; diff --git a/src/miner.cpp b/src/miner.cpp index eccddbb04f..d9186a5d6d 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -13,6 +13,7 @@ #include <consensus/merkle.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> +#include <deploymentstatus.h> #include <policy/feerate.h> #include <policy/policy.h> #include <pow.h> @@ -45,15 +46,7 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block)); block.vtx.at(0) = MakeTransactionRef(tx); - CBlockIndex* prev_block; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman.m_blockman)); - prev_block = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); - } + CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)); GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus()); block.hashMerkleRoot = BlockMerkleRoot(block); @@ -124,12 +117,11 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end LOCK2(cs_main, m_mempool.cs); - assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*m_chainstate.m_chain.Tip())); CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; - pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); + pblock->nVersion = g_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) @@ -146,12 +138,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc // This is only needed in case the witness softfork activation is reverted // (which would require a very deep reorganization). // Note that the mempool would accept transactions with witness data before - // IsWitnessEnabled, but we would only ever mine blocks after IsWitnessEnabled + // the deployment is active, but we would only ever mine blocks after activation // unless there is a massive block reorganization with the witness softfork // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). - fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()); + fIncludeWitness = DeploymentActiveAfter(pindexPrev, chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); int nPackagesSelected = 0; int nDescendantsUpdated = 0; @@ -184,7 +176,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(m_chainstate)); if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } diff --git a/src/net.cpp b/src/net.cpp index 6f9f17ed4e..70ba875c4b 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -42,6 +42,7 @@ #endif #include <algorithm> +#include <array> #include <cstdint> #include <functional> #include <optional> @@ -401,7 +402,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0); // Resolve - const uint16_t default_port{Params().GetDefaultPort()}; + const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) : + Params().GetDefaultPort()}; if (pszDest) { std::vector<CService> resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { @@ -841,18 +843,6 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons return a.nTimeConnected > b.nTimeConnected; } -static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - if (a.m_is_local != b.m_is_local) return b.m_is_local; - return a.nTimeConnected > b.nTimeConnected; -} - -static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) -{ - if (a.m_is_onion != b.m_is_onion) return b.m_is_onion; - return a.nTimeConnected > b.nTimeConnected; -} - static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } @@ -883,6 +873,26 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } +/** + * Sort eviction candidates by network/localhost and connection uptime. + * Candidates near the beginning are more likely to be evicted, and those + * near the end are more likely to be protected, e.g. less likely to be evicted. + * - First, nodes that are not `is_local` and that do not belong to `network`, + * sorted by increasing uptime (from most recently connected to connected longer). + * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime. + */ +struct CompareNodeNetworkTime { + const bool m_is_local; + const Network m_network; + CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {} + bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const + { + if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local; + if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network; + return a.nTimeConnected > b.nTimeConnected; + }; +}; + //! Sort an array by the specified comparator, then erase the last K elements where predicate is true. template <typename T, typename Comparator> static void EraseLastKElements( @@ -894,40 +904,77 @@ static void EraseLastKElements( elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); } -void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates) +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates) { // Protect the half of the remaining nodes which have been connected the longest. // This replicates the non-eviction implicit behavior, and precludes attacks that start later. - // To favorise the diversity of our peer connections, reserve up to (half + 2) of - // these protected spots for onion and localhost peers, if any, even if they're not - // longest uptime overall. This helps protect tor peers, which tend to be otherwise + // To favorise the diversity of our peer connections, reserve up to half of these protected + // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall. + // This helps protect these higher-latency peers that tend to be otherwise // disadvantaged under our eviction criteria. - const size_t initial_size = vEvictionCandidates.size(); - size_t total_protect_size = initial_size / 2; - const size_t onion_protect_size = total_protect_size / 2; - - if (onion_protect_size) { - // Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime. - EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size, - [](const NodeEvictionCandidate& n) { return n.m_is_onion; }); - } - - const size_t localhost_min_protect_size{2}; - if (onion_protect_size >= localhost_min_protect_size) { - // Allocate any remaining slots of the 1/4, or minimum 2 additional slots, - // to localhost peers, sorted by longest uptime, as manually configured - // hidden services not using `-bind=addr[:port]=onion` will not be detected - // as inbound onion connections. - const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())}; - const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)}; - EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size, - [](const NodeEvictionCandidate& n) { return n.m_is_local; }); + const size_t initial_size = eviction_candidates.size(); + const size_t total_protect_size{initial_size / 2}; + + // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier + // array members have first opportunity to recover unused slots from the previous iteration. + struct Net { bool is_local; Network id; size_t count; }; + std::array<Net, 3> networks{ + {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + + // Count and store the number of eviction candidates per network. + for (Net& n : networks) { + n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(), + [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + } + // Sort `networks` by ascending candidate count, to give networks having fewer candidates + // the first opportunity to recover unused protected slots from the previous iteration. + std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; }); + + // Protect up to 25% of the eviction candidates by disadvantaged network. + const size_t max_protect_by_network{total_protect_size / 2}; + size_t num_protected{0}; + + while (num_protected < max_protect_by_network) { + // Count the number of disadvantaged networks from which we have peers to protect. + auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; }); + if (num_networks == 0) { + break; + } + const size_t disadvantaged_to_protect{max_protect_by_network - num_protected}; + const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))}; + // Early exit flag if there are no remaining candidates by disadvantaged network. + bool protected_at_least_one{false}; + + for (Net& n : networks) { + if (n.count == 0) continue; + const size_t before = eviction_candidates.size(); + EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id), + protect_per_network, [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + const size_t after = eviction_candidates.size(); + if (before > after) { + protected_at_least_one = true; + const size_t delta{before - after}; + num_protected += delta; + if (num_protected >= max_protect_by_network) { + break; + } + n.count -= delta; + } + } + if (!protected_at_least_one) { + break; + } } // Calculate how many we removed, and update our total number of peers that // we want to protect based on uptime accordingly. - total_protect_size -= initial_size - vEvictionCandidates.size(); - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); + assert(num_protected == initial_size - eviction_candidates.size()); + const size_t remaining_to_protect{total_protect_size - num_protected}; + EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect); } [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) @@ -944,8 +991,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvict // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect up to 8 non-tx-relay peers that have sent us novel blocks. - const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); - EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, + EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; }); // Protect 4 nodes that most recently sent us novel blocks. @@ -1024,7 +1070,7 @@ bool CConnman::AttemptToEvictConnection() HasAllDesirableServiceFlags(node->nServices), peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(), - node->m_inbound_onion}; + node->ConnectedThroughNetwork()}; vEvictionCandidates.push_back(candidate); } } @@ -2019,8 +2065,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // from advertising themselves as a service on another host and // port, causing a DoS attack as nodes around the network attempt // to connect to it fruitlessly. - if (addr.GetPort() != Params().GetDefaultPort() && nTries < 50) + if (addr.GetPort() != Params().GetDefaultPort(addr.GetNetwork()) && nTries < 50) { continue; + } addrConnect = addr; break; @@ -2083,7 +2130,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const } for (const std::string& strAddNode : lAddresses) { - CService service(LookupNumeric(strAddNode, Params().GetDefaultPort())); + CService service(LookupNumeric(strAddNode, Params().GetDefaultPort(strAddNode))); AddedNodeInfo addedNode{strAddNode, CService(), false, false}; if (service.IsValid()) { // strAddNode is an IP:port @@ -2173,6 +2220,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai void CConnman::ThreadMessageHandler() { + FastRandomContext rng; while (!flagInterruptMsgProc) { std::vector<CNode*> vNodesCopy; @@ -2186,6 +2234,11 @@ void CConnman::ThreadMessageHandler() bool fMoreWork = false; + // Randomize the order in which we process messages from/to our peers. + // This prevents attacks in which an attacker exploits having multiple + // consecutive connections in the vNodes list. + Shuffle(vNodesCopy.begin(), vNodesCopy.end(), rng); + for (CNode* pnode : vNodesCopy) { if (pnode->fDisconnect) @@ -2419,30 +2472,25 @@ bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags return true; } -bool CConnman::InitBinds( - const std::vector<CService>& binds, - const std::vector<NetWhitebindPermissions>& whiteBinds, - const std::vector<CService>& onion_binds) +bool CConnman::InitBinds(const Options& options) { bool fBound = false; - for (const auto& addrBind : binds) { + for (const auto& addrBind : options.vBinds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::None); } - for (const auto& addrBind : whiteBinds) { + for (const auto& addrBind : options.vWhiteBinds) { fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); } - if (binds.empty() && whiteBinds.empty()) { + for (const auto& addr_bind : options.onion_binds) { + fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None); + } + if (options.bind_on_any) { struct in_addr inaddr_any; inaddr_any.s_addr = htonl(INADDR_ANY); struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::None); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::None); } - - for (const auto& addr_bind : onion_binds) { - fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None); - } - return fBound; } @@ -2450,7 +2498,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) { Init(connOptions); - if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds, connOptions.onion_binds)) { + if (fListen && !InitBinds(connOptions)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Failed to listen on any port. Use -listen=0 if you want this."), @@ -768,6 +768,9 @@ public: std::vector<NetWhitebindPermissions> vWhiteBinds; std::vector<CService> vBinds; std::vector<CService> onion_binds; + /// True if the user did not specify -bind= or -whitebind= and thus + /// we should bind on `0.0.0.0` (IPv4) and `::` (IPv6). + bool bind_on_any; bool m_use_addrman_outgoing = true; std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; @@ -962,10 +965,7 @@ private: bool BindListenPort(const CService& bindAddr, bilingual_str& strError, NetPermissionFlags permissions); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); - bool InitBinds( - const std::vector<CService>& binds, - const std::vector<NetWhitebindPermissions>& whiteBinds, - const std::vector<CService>& onion_binds); + bool InitBinds(const Options& options); void ThreadOpenAddedConnections(); void AddAddrFetch(const std::string& strDest); @@ -1209,7 +1209,7 @@ struct NodeEvictionCandidate uint64_t nKeyedNetGroup; bool prefer_evict; bool m_is_local; - bool m_is_onion; + Network m_network; }; /** @@ -1227,20 +1227,20 @@ struct NodeEvictionCandidate * longest, to replicate the non-eviction implicit behavior and preclude attacks * that start later. * - * Half of these protected spots (1/4 of the total) are reserved for onion peers - * connected via our tor control service, if any, sorted by longest uptime, even - * if they're not longest uptime overall. Any remaining slots of the 1/4 are - * then allocated to protect localhost peers, if any (or up to 2 localhost peers - * if no slots remain and 2 or more onion peers were protected), sorted by - * longest uptime, as manually configured hidden services not using - * `-bind=addr[:port]=onion` will not be detected as inbound onion connections. + * Half of these protected spots (1/4 of the total) are reserved for the + * following categories of peers, sorted by longest uptime, even if they're not + * longest uptime overall: + * + * - onion peers connected via our tor control service + * + * - localhost peers, as manually configured hidden services not using + * `-bind=addr[:port]=onion` will not be detected as inbound onion connections * - * This helps protect onion peers, which tend to be otherwise disadvantaged - * under our eviction criteria for their higher min ping times relative to IPv4 - * and IPv6 peers, and favorise the diversity of peer connections. + * - I2P peers * - * This function was extracted from SelectNodeToEvict() to be able to test the - * ratio-based protection logic deterministically. + * This helps protect these privacy network peers, which tend to be otherwise + * disadvantaged under our eviction criteria for their higher min ping times + * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. */ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 65224b4259..315d2ac5cd 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -11,6 +11,7 @@ #include <blockfilter.h> #include <chainparams.h> #include <consensus/validation.h> +#include <deploymentstatus.h> #include <hash.h> #include <index/blockfilterindex.h> #include <merkleblock.h> @@ -159,10 +160,10 @@ static constexpr size_t MAX_ADDR_TO_SEND{1000}; namespace { /** Blocks that are in flight, and that are in the queue to be downloaded. */ struct QueuedBlock { - uint256 hash; - const CBlockIndex* pindex; //!< Optional. - bool fValidatedHeaders; //!< Whether this block has validated headers at the time of request. - std::unique_ptr<PartiallyDownloadedBlock> partialBlock; //!< Optional, used for CMPCTBLOCK downloads + /** BlockIndex. We must have this since we only request blocks when we've already validated the header. */ + const CBlockIndex* pindex; + /** Optional, used for CMPCTBLOCK downloads */ + std::unique_ptr<PartiallyDownloadedBlock> partialBlock; }; /** @@ -463,16 +464,20 @@ private: Mutex m_recent_confirmed_transactions_mutex; std::unique_ptr<CRollingBloomFilter> m_recent_confirmed_transactions GUARDED_BY(m_recent_confirmed_transactions_mutex); - /* Returns a bool indicating whether we requested this block. - * Also used if a block was /not/ received and timed out or started with another peer + /** Have we requested this block from a peer */ + bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Remove this block from our tracked requested blocks. Called if: + * - the block has been received from a peer + * - the request for the block has timed out */ - bool MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void RemoveBlockRequest(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /* Mark a block as in flight * Returns false, still setting pit, if the block was already in flight from the same peer * pit will only be valid as long as the same cs_main lock is being held */ - bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list<QueuedBlock>::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool BlockRequested(NodeId nodeid, const CBlockIndex& block, std::list<QueuedBlock>::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool TipMayBeStale() EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -512,7 +517,7 @@ private: std::list<NodeId> lNodesAnnouncingHeaderAndIDs GUARDED_BY(cs_main); /** Number of peers from which we're downloading blocks. */ - int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; + int m_peers_downloading_from GUARDED_BY(cs_main) = 0; /** Storage for orphan information */ TxOrphanage m_orphanage; @@ -627,7 +632,6 @@ struct CNodeState { //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. std::chrono::microseconds m_downloading_since{0us}; int nBlocksInFlight{0}; - int nBlocksInFlightValidHeaders{0}; //! Whether we consider this a preferred download peer. bool fPreferredDownload{false}; //! Whether this peer wants invs or headers (when possible) for block announcements. @@ -758,32 +762,42 @@ static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUS nPreferredDownload += state->fPreferredDownload; } -bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) +bool PeerManagerImpl::IsBlockRequested(const uint256& hash) { - std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash); - if (itInFlight != mapBlocksInFlight.end()) { - CNodeState *state = State(itInFlight->second.first); - assert(state != nullptr); - state->nBlocksInFlightValidHeaders -= itInFlight->second.second->fValidatedHeaders; - if (state->nBlocksInFlightValidHeaders == 0 && itInFlight->second.second->fValidatedHeaders) { - // Last validated block on the queue was received. - nPeersWithValidatedDownloads--; - } - if (state->vBlocksInFlight.begin() == itInFlight->second.second) { - // First block on the queue was received, update the start download time for the next one - state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); - } - state->vBlocksInFlight.erase(itInFlight->second.second); - state->nBlocksInFlight--; - state->m_stalling_since = 0us; - mapBlocksInFlight.erase(itInFlight); - return true; + return mapBlocksInFlight.find(hash) != mapBlocksInFlight.end(); +} + +void PeerManagerImpl::RemoveBlockRequest(const uint256& hash) +{ + auto it = mapBlocksInFlight.find(hash); + if (it == mapBlocksInFlight.end()) { + // Block was not requested + return; } - return false; + + auto [node_id, list_it] = it->second; + CNodeState *state = State(node_id); + assert(state != nullptr); + + if (state->vBlocksInFlight.begin() == list_it) { + // First block on the queue was received, update the start download time for the next one + state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); + } + state->vBlocksInFlight.erase(list_it); + + state->nBlocksInFlight--; + if (state->nBlocksInFlight == 0) { + // Last validated block on the queue was received. + m_peers_downloading_from--; + } + state->m_stalling_since = 0us; + mapBlocksInFlight.erase(it); } -bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex, std::list<QueuedBlock>::iterator** pit) +bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, std::list<QueuedBlock>::iterator** pit) { + const uint256& hash{block.GetBlockHash()}; + CNodeState *state = State(nodeid); assert(state != nullptr); @@ -797,22 +811,20 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co } // Make sure it's not listed somewhere already. - MarkBlockAsReceived(hash); + RemoveBlockRequest(hash); std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), - {hash, pindex, pindex != nullptr, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)}); + {&block, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)}); state->nBlocksInFlight++; - state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { // We're starting a block download (batch) from this peer. state->m_downloading_since = GetTime<std::chrono::microseconds>(); - } - if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) { - nPeersWithValidatedDownloads++; + m_peers_downloading_from++; } itInFlight = mapBlocksInFlight.insert(std::make_pair(hash, std::make_pair(nodeid, it))).first; - if (pit) + if (pit) { *pit = &itInFlight->second.second; + } return true; } @@ -825,12 +837,27 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) return; } if (nodestate->fProvidesHeaderAndIDs) { + int num_outbound_hb_peers = 0; for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) { if (*it == nodeid) { lNodesAnnouncingHeaderAndIDs.erase(it); lNodesAnnouncingHeaderAndIDs.push_back(nodeid); return; } + CNodeState *state = State(*it); + if (state != nullptr && !state->m_is_inbound) ++num_outbound_hb_peers; + } + if (nodestate->m_is_inbound) { + // If we're adding an inbound HB peer, make sure we're not removing + // our last outbound HB peer in the process. + if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { + CNodeState *remove_node = State(lNodesAnnouncingHeaderAndIDs.front()); + if (remove_node != nullptr && !remove_node->m_is_inbound) { + // Put the HB outbound peer in the second slot, so that it + // doesn't get removed. + std::swap(lNodesAnnouncingHeaderAndIDs.front(), *std::next(lNodesAnnouncingHeaderAndIDs.begin())); + } + } } m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); @@ -971,14 +998,14 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // We consider the chain that this peer is on invalid. return; } - if (!State(nodeid)->fHaveWitness && IsWitnessEnabled(pindex->pprev, consensusParams)) { + if (!State(nodeid)->fHaveWitness && DeploymentActiveAt(*pindex, consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { // We wouldn't download this block or its descendants from this peer. return; } if (pindex->nStatus & BLOCK_HAVE_DATA || m_chainman.ActiveChain().Contains(pindex)) { if (pindex->HaveTxsDownloaded()) state->pindexLastCommonBlock = pindex; - } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { + } else if (!IsBlockRequested(pindex->GetBlockHash())) { // The block is not already downloaded, and not yet in flight. if (pindex->nHeight > nWindowEnd) { // We reached the end of the window. @@ -1129,13 +1156,13 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) nSyncStarted--; for (const QueuedBlock& entry : state->vBlocksInFlight) { - mapBlocksInFlight.erase(entry.hash); + mapBlocksInFlight.erase(entry.pindex->GetBlockHash()); } WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); nPreferredDownload -= state->fPreferredDownload; - nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); - assert(nPeersWithValidatedDownloads >= 0); + m_peers_downloading_from -= (state->nBlocksInFlight != 0); + assert(m_peers_downloading_from >= 0); m_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect; assert(m_outbound_peers_with_protect_from_disconnect >= 0); m_wtxid_relay_peers -= state->m_wtxid_relay; @@ -1147,7 +1174,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) // Do a consistency check after the last peer is removed. assert(mapBlocksInFlight.empty()); assert(nPreferredDownload == 0); - assert(nPeersWithValidatedDownloads == 0); + assert(m_peers_downloading_from == 0); assert(m_outbound_peers_with_protect_from_disconnect == 0); assert(m_wtxid_relay_peers == 0); assert(m_txrequest.Size() == 0); @@ -1350,7 +1377,6 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn m_stale_tip_check_time(0), m_ignore_incoming_txs(ignore_incoming_txs) { - assert(std::addressof(g_chainman) == std::addressof(m_chainman)); // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -1442,7 +1468,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha return; nHighestFastAnnounce = pindex->nHeight; - bool fWitnessEnabled = IsWitnessEnabled(pindex->pprev, m_chainparams.GetConsensus()); + bool fWitnessEnabled = DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); uint256 hashBlock(pblock->GetHash()); { @@ -1684,7 +1710,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& } // release cs_main before calling ActivateBestChain if (need_activate_chain) { BlockValidationState state; - if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } @@ -2056,8 +2082,8 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // Calculate all the blocks we'd need to switch to pindexLast, up to a limit. while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && - !mapBlocksInFlight.count(pindexWalk->GetBlockHash()) && - (!IsWitnessEnabled(pindexWalk->pprev, m_chainparams.GetConsensus()) || State(pfrom.GetId())->fHaveWitness)) { + !IsBlockRequested(pindexWalk->GetBlockHash()) && + (!DeploymentActiveAt(*pindexWalk, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) { // We don't have this block, and it's not yet in flight. vToFetch.push_back(pindexWalk); } @@ -2081,7 +2107,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } uint32_t nFetchFlags = GetFetchFlags(pfrom); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex); + BlockRequested(pfrom.GetId(), *pindex); LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", pindex->GetBlockHash().ToString(), pfrom.GetId()); } @@ -2827,7 +2853,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); UpdateBlockAvailability(pfrom.GetId(), inv.hash); - if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) { + if (!fAlreadyHave && !fImporting && !fReindex && !IsBlockRequested(inv.hash)) { // Headers-first is the primary method of announcement on // the network. If a node fell back to sending blocks by inv, // it's probably for a re-org. The final block hash @@ -2910,7 +2936,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, a_recent_block = most_recent_block; } BlockValidationState state; - if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } @@ -3372,7 +3398,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - if (IsWitnessEnabled(pindex->pprev, m_chainparams.GetConsensus()) && !nodestate->fSupportsDesiredCmpctVersion) { + if (DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT) && !nodestate->fSupportsDesiredCmpctVersion) { // Don't bother trying to process compact blocks from v1 peers // after segwit activates. return; @@ -3384,7 +3410,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if ((!fAlreadyInFlight && nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) || (fAlreadyInFlight && blockInFlightIt->second.first == pfrom.GetId())) { std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr; - if (!MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex, &queuedBlockIt)) { + if (!BlockRequested(pfrom.GetId(), *pindex, &queuedBlockIt)) { if (!(*queuedBlockIt)->partialBlock) (*queuedBlockIt)->partialBlock.reset(new PartiallyDownloadedBlock(&m_mempool)); else { @@ -3397,7 +3423,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, PartiallyDownloadedBlock& partialBlock = *(*queuedBlockIt)->partialBlock; ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect + RemoveBlockRequest(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect Misbehaving(pfrom.GetId(), 100, "invalid compact block"); return; } else if (status == READ_STATUS_FAILED) { @@ -3492,7 +3518,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // process from some other peer. We do this after calling // ProcessNewBlock so that a malleated cmpctblock announcement // can't be used to interfere with block relay. - MarkBlockAsReceived(pblock->GetHash()); + RemoveBlockRequest(pblock->GetHash()); } } return; @@ -3524,7 +3550,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock; ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect + RemoveBlockRequest(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions"); return; } else if (status == READ_STATUS_FAILED) { @@ -3550,7 +3576,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // though the block was successfully read, and rely on the // handling in ProcessNewBlock to ensure the block index is // updated, etc. - MarkBlockAsReceived(resp.blockhash); // it is now an empty pointer + RemoveBlockRequest(resp.blockhash); // it is now an empty pointer fBlockRead = true; // mapBlockSource is used for potentially punishing peers and // updating which peers send us compact blocks, so the race @@ -3615,9 +3641,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const uint256 hash(pblock->GetHash()); { LOCK(cs_main); - // Also always process if we requested the block explicitly, as we may - // need it even though it is not a candidate for a new best tip. - forceProcessing |= MarkBlockAsReceived(hash); + // Always process the block if we requested it, since we may + // need it even when it's not a candidate for a new best tip. + forceProcessing = IsBlockRequested(hash); + RemoveBlockRequest(hash); // mapBlockSource is only used for punishing peers and setting // which peers send us compact blocks, so the race between here and // cs_main in ProcessNewBlock is fine. @@ -4712,9 +4739,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // to unreasonably increase our timeout. if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); - int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0); + int nOtherPeersWithValidatedDownloads = m_peers_downloading_from - 1; if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.nPowTargetSpacing} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { - LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->GetId()); + LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.pindex->GetBlockHash().ToString(), pto->GetId()); pto->fDisconnect = true; return true; } @@ -4767,7 +4794,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) for (const CBlockIndex *pindex : vToDownload) { uint32_t nFetchFlags = GetFetchFlags(*pto); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex); + BlockRequested(pto->GetId(), *pindex); LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->nHeight, pto->GetId()); } diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 1ea3969978..e7b3377475 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -489,7 +489,7 @@ bool CNetAddr::IsValid() const */ bool CNetAddr::IsRoutable() const { - return IsValid() && !(IsRFC1918() || IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || IsRFC4843() || IsRFC7343() || IsLocal() || IsInternal()); + return IsValid() && !(IsRFC1918() || IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || IsRFC4193() || IsRFC4843() || IsRFC7343() || IsLocal() || IsInternal()); } /** diff --git a/src/netaddress.h b/src/netaddress.h index e0d056b626..5e1d9d2a6f 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -11,7 +11,9 @@ #include <attributes.h> #include <compat.h> +#include <crypto/siphash.h> #include <prevector.h> +#include <random.h> #include <serialize.h> #include <tinyformat.h> #include <util/strencodings.h> @@ -110,6 +112,9 @@ static constexpr size_t ADDR_CJDNS_SIZE = 16; /// Size of "internal" (NET_INTERNAL) address (in bytes). static constexpr size_t ADDR_INTERNAL_SIZE = 10; +/// SAM 3.1 and earlier do not support specifying ports and force the port to 0. +static constexpr uint16_t I2P_SAM31_PORT{0}; + /** * Network address. */ @@ -251,6 +256,7 @@ class CNetAddr } } + friend class CNetAddrHash; friend class CSubNet; private: @@ -464,6 +470,22 @@ class CNetAddr } }; +class CNetAddrHash +{ +public: + size_t operator()(const CNetAddr& a) const noexcept + { + CSipHasher hasher(m_salt_k0, m_salt_k1); + hasher.Write(a.m_net); + hasher.Write(a.m_addr.data(), a.m_addr.size()); + return static_cast<size_t>(hasher.Finalize()); + } + +private: + const uint64_t m_salt_k0 = GetRand(std::numeric_limits<uint64_t>::max()); + const uint64_t m_salt_k1 = GetRand(std::numeric_limits<uint64_t>::max()); +}; + class CSubNet { protected: diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 6c66c565ad..0083b74b33 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -248,7 +248,6 @@ bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, // when the undo file is keeping up with the block file, we want to flush it explicitly // when it is lagging behind (more blocks arrive than are being connected), we let the // undo block write case handle it - assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight); nFile++; if (vinfoBlockFile.size() <= nFile) { @@ -494,7 +493,6 @@ struct CImportingNow { void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args) { - const CChainParams& chainparams = Params(); ScheduleBatchPriority(); { @@ -513,7 +511,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile break; // This error is logged in OpenBlockFile } LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); - chainman.ActiveChainstate().LoadExternalBlockFile(chainparams, file, &pos); + chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; @@ -524,7 +522,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): - chainman.ActiveChainstate().LoadGenesisBlock(chainparams); + chainman.ActiveChainstate().LoadGenesisBlock(); } // -loadblock= @@ -532,7 +530,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile FILE* file = fsbridge::fopen(path, "rb"); if (file) { LogPrintf("Importing blocks file %s...\n", path.string()); - chainman.ActiveChainstate().LoadExternalBlockFile(chainparams, file); + chainman.ActiveChainstate().LoadExternalBlockFile(file); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; @@ -549,7 +547,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile // the relevant pointers before the ABC call. for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; - if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) { + if (!chainstate->ActivateBestChain(state, nullptr)) { LogPrintf("Failed to connect best block (%s)\n", state.ToString()); StartShutdown(); return; diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index faf99dea81..7c7bf68178 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -5,12 +5,13 @@ #ifndef BITCOIN_NODE_BLOCKSTORAGE_H #define BITCOIN_NODE_BLOCKSTORAGE_H -#include <cstdint> -#include <vector> - #include <fs.h> #include <protocol.h> // For CMessageHeader::MessageStartChars +#include <atomic> +#include <cstdint> +#include <vector> + class ArgsManager; class BlockValidationState; class CBlock; diff --git a/src/node/coin.cpp b/src/node/coin.cpp index 23d4fa2aae..50fddf3ab0 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -13,7 +13,6 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) assert(node.mempool); assert(node.chainman); LOCK2(cs_main, node.mempool->cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 38c1d29250..67e497c218 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -97,7 +97,6 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& if (!pindex) { { LOCK(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); pindex = blockman.LookupBlockIndex(view->GetBestBlock()); } } diff --git a/src/node/context.cpp b/src/node/context.cpp index 6d22a6b110..9afadd09a9 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -12,6 +12,7 @@ #include <policy/fees.h> #include <scheduler.h> #include <txmempool.h> +#include <validation.h> NodeContext::NodeContext() {} NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h index 06adb33a80..135f9ea1c6 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -44,7 +44,7 @@ struct NodeContext { std::unique_ptr<CTxMemPool> mempool; std::unique_ptr<CBlockPolicyEstimator> fee_estimator; std::unique_ptr<PeerManager> peerman; - ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct + std::unique_ptr<ChainstateManager> chainman; std::unique_ptr<BanMan> banman; ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct std::unique_ptr<interfaces::Chain> chain; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 171f15d4fb..183b5a5d91 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -6,6 +6,8 @@ #include <banman.h> #include <chain.h> #include <chainparams.h> +#include <deploymentstatus.h> +#include <external_signer.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/handler.h> @@ -170,16 +172,24 @@ public: } return false; } -#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> externalSigners() override { +#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> signers = {}; const std::string command = gArgs.GetArg("-signer", ""); if (command == "") return signers; ExternalSigner::Enumerate(command, signers, Params().NetworkIDString()); return signers; +#else + // This result is indistinguishable from a successful call that returns + // no signers. For the current GUI this doesn't matter, because the wallet + // creation dialog disables the external signer checkbox in both + // cases. The return type could be changed to std::optional<std::vector> + // (or something that also includes error messages) if this distinction + // becomes important. + return {}; +#endif // ENABLE_EXTERNAL_SIGNER } -#endif int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; } int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } @@ -197,26 +207,16 @@ public: int getNumBlocks() override { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); return chainman().ActiveChain().Height(); } uint256 getBestBlockHash() override { - const CBlockIndex* tip; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); - tip = chainman().ActiveChain().Tip(); - } + const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } @@ -227,22 +227,12 @@ public: const CBlockIndex* tip; { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); tip = chainman().ActiveChain().Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { - const CChainState* active_chainstate; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - active_chainstate = &m_context->chainman->ActiveChainstate(); - assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); - } - return active_chainstate->IsInitialBlockDownload(); + return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } @@ -269,7 +259,6 @@ public: bool getUnspentOutput(const COutPoint& output, Coin& coin) override { LOCK(::cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainman().ActiveChainstate())); return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient& walletClient() override @@ -476,14 +465,12 @@ public: bool checkFinalTx(const CTransaction& tx) override { LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); return CheckFinalTx(chainman().ActiveChain().Tip(), tx); } std::optional<int> findLocatorFork(const CBlockLocator& locator) override { LOCK(cs_main); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) { return fork->nHeight; } @@ -493,7 +480,6 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override @@ -506,7 +492,6 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); @@ -518,9 +503,7 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; return FillBlock(ancestor, ancestor_out, lock, active); @@ -529,9 +512,7 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving @@ -542,7 +523,6 @@ public: double guessVerificationProgress(const uint256& block_hash) override { LOCK(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override @@ -555,7 +535,6 @@ public: // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { @@ -647,16 +626,7 @@ public: } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { - const CChainState* active_chainstate; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - active_chainstate = &chainman().ActiveChainstate(); - assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); - } - return active_chainstate->IsInitialBlockDownload(); + return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } @@ -719,6 +689,12 @@ public: notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); } } + bool isTaprootActive() const override + { + LOCK(::cs_main); + const CBlockIndex* tip = Assert(m_node.chainman)->ActiveChain().Tip(); + return DeploymentActiveAfter(tip, Params().GetConsensus(), Consensus::DEPLOYMENT_TAPROOT); + } NodeContext& m_node; }; } // namespace diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index c189018268..b013b6d579 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -23,6 +23,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.tx->vin.size()); + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -61,7 +63,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); // Things are missing if (!complete) { @@ -121,7 +123,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index a1e7a71e2c..f21b390915 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -40,7 +40,6 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t { // cs_main scope assert(node.chainman); LOCK(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); // If the transaction is already confirmed in the chain, don't do anything // and return early. CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip(); diff --git a/src/outputtype.cpp b/src/outputtype.cpp index d96fb282c5..8ede7b9974 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -18,6 +18,7 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; +static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m"; bool ParseOutputType(const std::string& type, OutputType& output_type) { @@ -30,6 +31,9 @@ bool ParseOutputType(const std::string& type, OutputType& output_type) } else if (type == OUTPUT_TYPE_STRING_BECH32) { output_type = OutputType::BECH32; return true; + } else if (type == OUTPUT_TYPE_STRING_BECH32M) { + output_type = OutputType::BECH32M; + return true; } return false; } @@ -40,6 +44,7 @@ const std::string& FormatOutputType(OutputType type) case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; + case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M; } // no default case, so the compiler can warn about missing cases assert(false); } @@ -59,6 +64,7 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) return witdest; } } + case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } @@ -98,6 +104,23 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, return ScriptHash(witprog); } } + case OutputType::BECH32M: {} // This function should not be used for BECH32M, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } + +std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) { + if (std::holds_alternative<PKHash>(dest) || + std::holds_alternative<ScriptHash>(dest)) { + return OutputType::LEGACY; + } + if (std::holds_alternative<WitnessV0KeyHash>(dest) || + std::holds_alternative<WitnessV0ScriptHash>(dest)) { + return OutputType::BECH32; + } + if (std::holds_alternative<WitnessV1Taproot>(dest) || + std::holds_alternative<WitnessUnknown>(dest)) { + return OutputType::BECH32M; + } + return std::nullopt; +} diff --git a/src/outputtype.h b/src/outputtype.h index 88422e5824..2b83235cd0 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -18,12 +18,14 @@ enum class OutputType { LEGACY, P2SH_SEGWIT, BECH32, + BECH32M, }; static constexpr auto OUTPUT_TYPES = std::array{ OutputType::LEGACY, OutputType::P2SH_SEGWIT, OutputType::BECH32, + OutputType::BECH32M, }; [[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type); @@ -45,4 +47,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); */ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType); +/** Get the OutputType for a CTxDestination */ +std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest); + #endif // BITCOIN_OUTPUTTYPE_H diff --git a/src/protocol.h b/src/protocol.h index aaa9f1df40..f9248899dc 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -13,6 +13,7 @@ #include <netaddress.h> #include <primitives/transaction.h> #include <serialize.h> +#include <streams.h> #include <uint256.h> #include <version.h> @@ -358,6 +359,31 @@ class CAddress : public CService { static constexpr uint32_t TIME_INIT{100000000}; + /** Historically, CAddress disk serialization stored the CLIENT_VERSION, optionally OR'ed with + * the ADDRV2_FORMAT flag to indicate V2 serialization. The first field has since been + * disentangled from client versioning, and now instead: + * - The low bits (masked by DISK_VERSION_IGNORE_MASK) store the fixed value DISK_VERSION_INIT, + * (in case any code exists that treats it as a client version) but are ignored on + * deserialization. + * - The high bits (masked by ~DISK_VERSION_IGNORE_MASK) store actual serialization information. + * Only 0 or DISK_VERSION_ADDRV2 (equal to the historical ADDRV2_FORMAT) are valid now, and + * any other value triggers a deserialization failure. Other values can be added later if + * needed. + * + * For disk deserialization, ADDRV2_FORMAT in the stream version signals that ADDRV2 + * deserialization is permitted, but the actual format is determined by the high bits in the + * stored version field. For network serialization, the stream version having ADDRV2_FORMAT or + * not determines the actual format used (as it has no embedded version number). + */ + static constexpr uint32_t DISK_VERSION_INIT{220000}; + static constexpr uint32_t DISK_VERSION_IGNORE_MASK{0b00000000'00000111'11111111'11111111}; + /** The version number written in disk serialized addresses to indicate V2 serializations. + * It must be exactly 1<<29, as that is the value that historical versions used for this + * (they used their internal ADDRV2_FORMAT flag here). */ + static constexpr uint32_t DISK_VERSION_ADDRV2{1 << 29}; + static_assert((DISK_VERSION_INIT & ~DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_INIT must be covered by DISK_VERSION_IGNORE_MASK"); + static_assert((DISK_VERSION_ADDRV2 & DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_ADDRV2 must not be covered by DISK_VERSION_IGNORE_MASK"); + public: CAddress() : CService{} {}; CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {}; @@ -365,22 +391,48 @@ public: SERIALIZE_METHODS(CAddress, obj) { - SER_READ(obj, obj.nTime = TIME_INIT); - int nVersion = s.GetVersion(); + // CAddress has a distinct network serialization and a disk serialization, but it should never + // be hashed (except through CHashWriter in addrdb.cpp, which sets SER_DISK), and it's + // ambiguous what that would mean. Make sure no code relying on that is introduced: + assert(!(s.GetType() & SER_GETHASH)); + bool use_v2; + bool store_time; if (s.GetType() & SER_DISK) { - READWRITE(nVersion); - } - if ((s.GetType() & SER_DISK) || - (nVersion != INIT_PROTO_VERSION && !(s.GetType() & SER_GETHASH))) { + // In the disk serialization format, the encoding (v1 or v2) is determined by a flag version + // that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines + // whether V2 is chosen/permitted at all. + uint32_t stored_format_version = DISK_VERSION_INIT; + if (s.GetVersion() & ADDRV2_FORMAT) stored_format_version |= DISK_VERSION_ADDRV2; + READWRITE(stored_format_version); + stored_format_version &= ~DISK_VERSION_IGNORE_MASK; // ignore low bits + if (stored_format_version == 0) { + use_v2 = false; + } else if (stored_format_version == DISK_VERSION_ADDRV2 && (s.GetVersion() & ADDRV2_FORMAT)) { + // Only support v2 deserialization if ADDRV2_FORMAT is set. + use_v2 = true; + } else { + throw std::ios_base::failure("Unsupported CAddress disk format version"); + } + store_time = true; + } else { + // In the network serialization format, the encoding (v1 or v2) is determined directly by + // the value of ADDRV2_FORMAT in the stream version, as no explicitly encoded version + // exists in the stream. + assert(s.GetType() & SER_NETWORK); + use_v2 = s.GetVersion() & ADDRV2_FORMAT; // The only time we serialize a CAddress object without nTime is in // the initial VERSION messages which contain two CAddress records. // At that point, the serialization version is INIT_PROTO_VERSION. // After the version handshake, serialization version is >= // MIN_PEER_PROTO_VERSION and all ADDR messages are serialized with // nTime. - READWRITE(obj.nTime); + store_time = s.GetVersion() != INIT_PROTO_VERSION; } - if (nVersion & ADDRV2_FORMAT) { + + SER_READ(obj, obj.nTime = TIME_INIT); + if (store_time) READWRITE(obj.nTime); + // nServices is serialized as CompactSize in V2; as uint64_t in V1. + if (use_v2) { uint64_t services_tmp; SER_WRITE(obj, services_tmp = obj.nServices); READWRITE(Using<CompactSizeFormatter<false>>(services_tmp)); @@ -388,13 +440,22 @@ public: } else { READWRITE(Using<CustomUintFormatter<8>>(obj.nServices)); } - READWRITEAS(CService, obj); + // Invoke V1/V2 serializer for CService parent object. + OverrideStream<Stream> os(&s, s.GetType(), use_v2 ? ADDRV2_FORMAT : 0); + SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj)); } - // disk and network only + //! Always included in serialization, except in the network format on INIT_PROTO_VERSION. uint32_t nTime{TIME_INIT}; - + //! Serialized as uint64_t in V1, and as CompactSize in V2. ServiceFlags nServices{NODE_NONE}; + + friend bool operator==(const CAddress& a, const CAddress& b) + { + return a.nTime == b.nTime && + a.nServices == b.nServices && + static_cast<const CService&>(a) == static_cast<const CService&>(b); + } }; /** getdata message type flags */ diff --git a/src/psbt.cpp b/src/psbt.cpp index a849b2ea53..5445bc8aa1 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -59,12 +59,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const { - PSBTInput input = inputs[input_index]; + const PSBTInput& input = inputs[input_index]; uint32_t prevout_index = tx->vin[input_index].prevout.n; if (input.non_witness_utxo) { if (prevout_index >= input.non_witness_utxo->vout.size()) { return false; } + if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) { + return false; + } utxo = input.non_witness_utxo->vout[prevout_index]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; @@ -227,7 +230,24 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio psbt_out.FromSignatureData(sigdata); } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) +{ + const CMutableTransaction& tx = *psbt.tx; + bool have_all_spent_outputs = true; + std::vector<CTxOut> utxos(tx.vin.size()); + for (size_t idx = 0; idx < tx.vin.size(); ++idx) { + if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false; + } + PrecomputedTransactionData txdata; + if (have_all_spent_outputs) { + txdata.Init(tx, std::move(utxos), true); + } else { + txdata.Init(tx, {}, true); + } + return txdata; +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata) { PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; @@ -267,10 +287,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& sigdata.witness = false; bool sig_complete; - if (use_dummy) { + if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. @@ -302,8 +322,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) // PartiallySignedTransaction did not understand them), this will combine them into a final // script. bool complete = true; + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); } return complete; diff --git a/src/psbt.h b/src/psbt.h index 96ae39fdb8..f6b82b43de 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -567,11 +567,18 @@ enum class PSBTRole { std::string PSBTRoleName(PSBTRole role); +/** Compute a PrecomputedTransactionData object from a psbt. */ +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); + /** Checks whether a PSBTInput is already signed. */ bool PSBTInputSigned(const PSBTInput& input); -/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Signs a PSBTInput, verifying that all provided data matches what is being signed. + * + * txdata should be the output of PrecomputePSBTData (which can be shared across + * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. + **/ +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); /** Counts the unsigned inputs of a PSBT. */ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 51cc826b00..175a39b805 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -373,3 +373,7 @@ ECCVerifyHandle::~ECCVerifyHandle() secp256k1_context_verify = nullptr; } } + +const secp256k1_context* GetVerifyContext() { + return secp256k1_context_verify; +} diff --git a/src/pubkey.h b/src/pubkey.h index 152a48dd18..eec34a89c2 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -234,6 +234,10 @@ public: * fail. */ bool IsFullyValid() const; + /** Test whether this is the 0 key (the result of default construction). This implies + * !IsFullyValid(). */ + bool IsNull() const { return m_keydata.IsNull(); } + /** Construct an x-only pubkey from exactly 32 bytes. */ explicit XOnlyPubKey(Span<const unsigned char> bytes); @@ -312,4 +316,10 @@ public: ~ECCVerifyHandle(); }; +typedef struct secp256k1_context_struct secp256k1_context; + +/** Access to the internal secp256k1 context used for verification. Only intended to be used + * by key.cpp. */ +const secp256k1_context* GetVerifyContext(); + #endif // BITCOIN_PUBKEY_H diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 7024fc7654..c31f0aceea 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -114,12 +114,12 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, // Build context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy Address"), this, &AddressBookPage::on_copyAddress_clicked); - contextMenu->addAction(tr("Copy Label"), this, &AddressBookPage::onCopyLabelAction); - contextMenu->addAction(tr("Edit"), this, &AddressBookPage::onEditAction); + contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked); + contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction); + contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction); if (tab == SendingTab) { - contextMenu->addAction(tr("Delete"), this, &AddressBookPage::on_deleteAddress_clicked); + contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked); } connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 9e6cf56d31..442c813a5a 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -490,7 +490,8 @@ int GuiMain(int argc, char* argv[]) /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: - SetupServerArgs(node_context); + node_context.args = &gArgs; + SetupServerArgs(gArgs); SetupUIArgs(gArgs); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 50f6e739e8..f8aeb01659 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -211,11 +211,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); -#ifdef ENABLE_WALLET - if(enableWallet) { - connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay); - } -#endif #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; @@ -690,7 +685,7 @@ void BitcoinGUI::addWallet(WalletModel* walletModel) const QString display_name = walletModel->getDisplayName(); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); - connect(wallet_view, &WalletView::outOfSyncWarningClicked, walletFrame, &WalletFrame::outOfSyncWarningClicked); + connect(wallet_view, &WalletView::outOfSyncWarningClicked, this, &BitcoinGUI::showModalOverlay); connect(wallet_view, &WalletView::transactionClicked, this, &BitcoinGUI::gotoHistoryPage); connect(wallet_view, &WalletView::coinsSent, this, &BitcoinGUI::gotoHistoryPage); connect(wallet_view, &WalletView::message, [this](const QString& title, const QString& message, unsigned int style) { @@ -1178,14 +1173,12 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty void BitcoinGUI::changeEvent(QEvent *e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { overviewAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/overview"))); sendCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/send"))); receiveCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/receiving_addresses"))); historyAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/history"))); } -#endif QMainWindow::changeEvent(e); @@ -1516,14 +1509,14 @@ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) void UnitDisplayStatusBarControl::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { QString style = QString("QLabel { color : %1 }").arg(m_platform_style->SingleColor().name()); if (style != styleSheet()) { setStyleSheet(style); } } -#endif + + QLabel::changeEvent(e); } /** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */ diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index bff253e58a..d2d4079ea9 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -42,6 +42,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile version is not supported. This version of bitcoin-wallet " "only supports version 1 dumpfiles. Got dumpfile with version %s"), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and " +"\"bech32\" address types"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Listening for incoming connections failed (listen returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "" "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -" @@ -103,9 +106,6 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Total length of network version string (%i) exceeds maximum length (%i). " "Reduce the number or size of uacomments."), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Transaction needs a change address, but we can't generate it. Please call " -"keypoolrefill first."), -QT_TRANSLATE_NOOP("bitcoin-core", "" "Unable to replay blocks. You will need to rebuild the database using -" "reindex-chainstate."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -160,6 +160,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got key that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got value that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Keypool ran out, please call keypoolrefill first"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Missing checksum"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: No %s addresses available."), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to parse version %u as a uint32_t"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to write record to new wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to listen on any port. Use -listen=0 if you want this."), @@ -216,6 +217,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Transaction amount too small"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction amounts must not be negative"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction has too long of a mempool chain"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction must have at least one recipient"), +QT_TRANSLATE_NOOP("bitcoin-core", "Transaction needs a change address, but we can't generate it. %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer. %s is probably already running."), @@ -228,6 +230,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown address type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown change type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unknown new rules activated (versionbit %i)"), QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."), QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading UTXO database"), QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading txindex database"), @@ -235,5 +238,4 @@ QT_TRANSLATE_NOOP("bitcoin-core", "User Agent comment (%s) contains unsafe chara QT_TRANSLATE_NOOP("bitcoin-core", "Verifying blocks…"), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying wallet(s)…"), QT_TRANSLATE_NOOP("bitcoin-core", "Wallet needed to be rewritten: restart %s to complete"), -QT_TRANSLATE_NOOP("bitcoin-core", "Warning: unknown new rules activated (versionbit %i)"), }; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 8ae0648141..d2a9365890 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -52,13 +52,13 @@ CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _m // context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy address"), this, &CoinControlDialog::copyAddress); - contextMenu->addAction(tr("Copy label"), this, &CoinControlDialog::copyLabel); - contextMenu->addAction(tr("Copy amount"), this, &CoinControlDialog::copyAmount); - copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction ID"), this, &CoinControlDialog::copyTransactionHash); + contextMenu->addAction(tr("&Copy address"), this, &CoinControlDialog::copyAddress); + contextMenu->addAction(tr("Copy &label"), this, &CoinControlDialog::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &CoinControlDialog::copyAmount); + copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction &ID"), this, &CoinControlDialog::copyTransactionHash); contextMenu->addSeparator(); - lockAction = contextMenu->addAction(tr("Lock unspent"), this, &CoinControlDialog::lockCoin); - unlockAction = contextMenu->addAction(tr("Unlock unspent"), this, &CoinControlDialog::unlockCoin); + lockAction = contextMenu->addAction(tr("L&ock unspent"), this, &CoinControlDialog::lockCoin); + unlockAction = contextMenu->addAction(tr("&Unlock unspent"), this, &CoinControlDialog::unlockCoin); connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu); // clipboard actions @@ -564,11 +564,11 @@ void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel * void CoinControlDialog::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { updateView(); } -#endif + + QDialog::changeEvent(e); } void CoinControlDialog::updateView() diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp index e593697b46..dc24bbc6a6 100644 --- a/src/qt/createwalletdialog.cpp +++ b/src/qt/createwalletdialog.cpp @@ -31,8 +31,9 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) : // Disable the disable_privkeys_checkbox and external_signer_checkbox when isEncryptWalletChecked is // set to true, enable it when isEncryptWalletChecked is false. ui->disable_privkeys_checkbox->setEnabled(!checked); +#ifdef ENABLE_EXTERNAL_SIGNER ui->external_signer_checkbox->setEnabled(!checked); - +#endif // When the disable_privkeys_checkbox is disabled, uncheck it. if (!ui->disable_privkeys_checkbox->isEnabled()) { ui->disable_privkeys_checkbox->setChecked(false); @@ -112,8 +113,7 @@ CreateWalletDialog::~CreateWalletDialog() delete ui; } -#ifdef ENABLE_EXTERNAL_SIGNER -void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers) +void CreateWalletDialog::setSigners(const std::vector<ExternalSigner>& signers) { if (!signers.empty()) { ui->external_signer_checkbox->setEnabled(true); @@ -132,7 +132,6 @@ void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers) ui->external_signer_checkbox->setEnabled(false); } } -#endif QString CreateWalletDialog::walletName() const { diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h index 585b1461f7..25ddf97585 100644 --- a/src/qt/createwalletdialog.h +++ b/src/qt/createwalletdialog.h @@ -7,11 +7,8 @@ #include <QDialog> -class WalletModel; - -#ifdef ENABLE_EXTERNAL_SIGNER class ExternalSigner; -#endif +class WalletModel; namespace Ui { class CreateWalletDialog; @@ -27,9 +24,7 @@ public: explicit CreateWalletDialog(QWidget* parent); virtual ~CreateWalletDialog(); -#ifdef ENABLE_EXTERNAL_SIGNER - void setSigners(std::vector<ExternalSigner>& signers); -#endif + void setSigners(const std::vector<ExternalSigner>& signers); QString walletName() const; bool isEncryptWalletChecked() const; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 393dca8ccd..ecdfce2f5a 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -809,11 +809,10 @@ void ThemedLabel::setThemedPixmap(const QString& image_filename, int width, int void ThemedLabel::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { updateThemedPixmap(); } -#endif + QLabel::changeEvent(e); } diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index b146489ba8..7026f49c01 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -55,11 +55,12 @@ </message> <message> <location line="-30"/> + <location filename="../addressbookpage.cpp" line="+122"/> <source>&Delete</source> <translation>&Delete</translation> </message> <message> - <location filename="../addressbookpage.cpp" line="+84"/> + <location filename="../addressbookpage.cpp" line="-38"/> <source>Choose the address to send coins to</source> <translation type="unfinished"></translation> </message> @@ -96,26 +97,21 @@ Signing is only possible with addresses of the type 'legacy'.</source> </message> <message> <location line="+8"/> - <source>Copy Address</source> + <source>&Copy Address</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy Label</source> + <source>Copy &Label</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Edit</source> + <source>&Edit</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+161"/> + <location line="+164"/> <source>Export Address List</source> <translation type="unfinished"></translation> </message> @@ -317,7 +313,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>BitcoinApplication</name> <message> - <location filename="../bitcoin.cpp" line="+420"/> + <location filename="../bitcoin.cpp" line="+421"/> <source>Runaway exception</source> <translation type="unfinished"></translation> </message> @@ -405,18 +401,18 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+358"/> + <location line="+373"/> <source>Network activity disabled.</source> <extracomment>A substring of the tooltip.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+426"/> + <location line="+424"/> <source>Proxy is <b>enabled</b>: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="-1096"/> + <location line="-1109"/> <source>Send coins to a Bitcoin address</source> <translation>Send coins to a Bitcoin address</translation> </message> @@ -546,7 +542,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>Tabs toolbar</translation> </message> <message> - <location line="+422"/> + <location line="+437"/> <source>Syncing Headers (%1%)…</source> <translation type="unfinished"></translation> </message> @@ -576,7 +572,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-766"/> + <location line="-781"/> <source>Request payments (generates QR codes and bitcoin: URIs)</source> <translation type="unfinished"></translation> </message> @@ -596,7 +592,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location line="+678"/> + <location line="+693"/> <source>Processed %n block(s) of transaction history.</source> <translation> <numerusform>Processed %n block of transaction history.</numerusform> @@ -644,7 +640,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>Up to date</translation> </message> <message> - <location line="-715"/> + <location line="-730"/> <source>Load Partially Signed Bitcoin Transaction</source> <translation type="unfinished"></translation> </message> @@ -744,7 +740,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+248"/> + <location line="+263"/> <source>%1 client</source> <translation type="unfinished"></translation> </message> @@ -792,7 +788,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+110"/> + <location line="+108"/> <source>Date: %1 </source> <translation type="unfinished"></translation> @@ -956,34 +952,38 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished">Confirmed</translation> </message> <message> - <location filename="../coincontroldialog.cpp" line="+55"/> - <source>Copy address</source> + <location filename="../coincontroldialog.cpp" line="+66"/> + <source>Copy amount</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="-11"/> + <source>&Copy address</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy label</source> + <source>Copy &label</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <location line="+9"/> - <source>Copy amount</source> + <source>Copy &amount</source> <translation type="unfinished"></translation> </message> <message> - <location line="-8"/> - <source>Copy transaction ID</source> + <location line="+1"/> + <source>Copy transaction &ID</source> <translation type="unfinished"></translation> </message> <message> <location line="+2"/> - <source>Lock unspent</source> + <source>L&ock unspent</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Unlock unspent</source> + <source>&Unlock unspent</source> <translation type="unfinished"></translation> </message> <message> @@ -1061,12 +1061,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>CreateWalletActivity</name> <message> - <location filename="../walletcontroller.cpp" line="+253"/> + <location filename="../walletcontroller.cpp" line="+254"/> <source>Creating Wallet <b>%1</b>…</source> <translation type="unfinished"></translation> </message> <message> - <location line="+28"/> + <location line="+31"/> <source>Create wallet failed</source> <translation type="unfinished"></translation> </message> @@ -1075,6 +1075,11 @@ Signing is only possible with addresses of the type 'legacy'.</source> <source>Create wallet warning</source> <translation type="unfinished"></translation> </message> + <message> + <location line="+16"/> + <source>Can't list signers</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>CreateWalletDialog</name> @@ -1139,15 +1144,31 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../createwalletdialog.cpp" line="+21"/> + <location line="+7"/> + <source>Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>External signer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../createwalletdialog.cpp" line="+22"/> <source>Create</source> <translation type="unfinished"></translation> </message> <message> - <location line="+42"/> + <location line="+68"/> <source>Compiled without sqlite support (required for descriptor wallets)</source> <translation type="unfinished"></translation> </message> + <message> + <location line="+14"/> + <source>Compiled without external signing support (required for external signing)</source> + <extracomment>"External signing" means using devices such as hardware wallets.</extracomment> + <translation type="unfinished"></translation> + </message> </context> <context> <name>EditAddressDialog</name> @@ -1472,7 +1493,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>OpenWalletActivity</name> <message> - <location filename="../walletcontroller.cpp" line="+39"/> + <location filename="../walletcontroller.cpp" line="+32"/> <source>Open wallet failed</source> <translation type="unfinished"></translation> </message> @@ -1530,7 +1551,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+171"/> + <location line="+201"/> <location line="+187"/> <source>IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1)</source> <translation type="unfinished"></translation> @@ -1579,7 +1600,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>&Network</translation> </message> <message> - <location line="-188"/> + <location line="-218"/> <source>Prune &block storage to</source> <translation type="unfinished"></translation> </message> @@ -1629,7 +1650,22 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+30"/> + <location line="+10"/> + <source>External Signer (e.g. hardware wallet)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+8"/> + <source>&External signer script path</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> + <source>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+32"/> <source>Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source> <translation>Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</translation> </message> @@ -1762,12 +1798,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>Choose the default subdivision unit to show in the interface and when sending coins.</translation> </message> <message> - <location line="-463"/> + <location line="-493"/> <source>Whether to show coin control features or not.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+260"/> + <location line="+290"/> <source>Connect to the Bitcoin network through a separate SOCKS5 proxy for Tor onion services.</source> <translation type="unfinished"></translation> </message> @@ -1812,7 +1848,13 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>&Cancel</translation> </message> <message> - <location filename="../optionsdialog.cpp" line="+104"/> + <location filename="../optionsdialog.cpp" line="+97"/> + <source>Compiled without external signing support (required for external signing)</source> + <extracomment>"External signing" means using devices such as hardware wallets.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+12"/> <source>default</source> <translation>default</translation> </message> @@ -1822,7 +1864,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+91"/> + <location line="+93"/> <source>Confirm options reset</source> <translation>Confirm options reset</translation> </message> @@ -1962,7 +2004,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../overviewpage.cpp" line="+193"/> + <location filename="../overviewpage.cpp" line="+188"/> <source>Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.</source> <translation type="unfinished"></translation> </message> @@ -2168,7 +2210,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <context> <name>PeerTableModel</name> <message> - <location filename="../peertablemodel.h" line="+101"/> + <location filename="../peertablemodel.h" line="+107"/> <source>User Agent</source> <extracomment>Title of Peers Table column which contains the peer's User Agent string.</extracomment> <translation type="unfinished"></translation> @@ -2384,7 +2426,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+111"/> + <location filename="../bitcoin.cpp" line="+112"/> <source>Error: Specified data directory "%1" does not exist.</source> <translation type="unfinished"></translation> </message> @@ -2418,12 +2460,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <name>QRImageWidget</name> <message> <location filename="../qrimagewidget.cpp" line="+30"/> - <source>Save Image…</source> + <source>&Save Image…</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy Image</source> + <source>&Copy Image</source> <translation type="unfinished"></translation> </message> <message> @@ -2467,7 +2509,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <location line="+23"/> <location line="+36"/> <location line="+23"/> - <location line="+722"/> + <location line="+692"/> <location line="+26"/> <location line="+26"/> <location line="+23"/> @@ -2490,12 +2532,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <location line="+23"/> <location line="+23"/> <location line="+26"/> - <location filename="../rpcconsole.h" line="+138"/> + <location filename="../rpcconsole.h" line="+139"/> <source>N/A</source> <translation>N/A</translation> </message> <message> - <location line="-1549"/> + <location line="-1519"/> <source>Client version</source> <translation>Client version</translation> </message> @@ -2536,12 +2578,12 @@ If you are receiving this error you should request the merchant provide a BIP21 </message> <message> <location line="+29"/> - <location line="+922"/> + <location line="+892"/> <source>Network</source> <translation>Network</translation> </message> <message> - <location line="-915"/> + <location line="-885"/> <source>Name</source> <translation type="unfinished"></translation> </message> @@ -2581,7 +2623,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+241"/> + <location line="+211"/> <source>&Reset</source> <translation type="unfinished"></translation> </message> @@ -2609,7 +2651,7 @@ If you are receiving this error you should request the merchant provide a BIP21 </message> <message> <location line="+68"/> - <location filename="../rpcconsole.cpp" line="+1091"/> + <location filename="../rpcconsole.cpp" line="+1124"/> <source>Select a peer to view detailed information.</source> <translation type="unfinished"></translation> </message> @@ -2644,13 +2686,13 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="-1516"/> - <location line="+1081"/> + <location line="-1486"/> + <location line="+1051"/> <source>User Agent</source> <translation type="unfinished"></translation> </message> <message> - <location line="-1155"/> + <location line="-1125"/> <source>Node window</source> <translation type="unfinished"></translation> </message> @@ -2665,17 +2707,17 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+84"/> + <location line="+78"/> <source>Decrease font size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+32"/> + <location line="+20"/> <source>Increase font size</source> <translation type="unfinished"></translation> </message> <message> - <location line="+558"/> + <location line="+546"/> <source>Permissions</source> <translation type="unfinished"></translation> </message> @@ -2780,7 +2822,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="-1288"/> + <location line="-1258"/> <source>Last block time</source> <translation>Last block time</translation> </message> @@ -2795,7 +2837,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation>&Console</translation> </message> <message> - <location line="+217"/> + <location line="+187"/> <source>&Network Traffic</source> <translation type="unfinished"></translation> </message> @@ -2815,12 +2857,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location filename="../forms/debugwindow.ui" line="-321"/> + <location filename="../forms/debugwindow.ui" line="-291"/> <source>Debug log file</source> <translation>Debug log file</translation> </message> <message> - <location line="+155"/> + <location line="+125"/> <source>Clear console</source> <translation>Clear console</translation> </message> @@ -2850,12 +2892,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+38"/> + <location line="+40"/> <source>Never</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../rpcconsole.cpp" line="-417"/> + <location filename="../rpcconsole.cpp" line="-429"/> <source>Inbound: initiated by peer</source> <translation type="unfinished"></translation> </message> @@ -2924,52 +2966,52 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+385"/> - <source>Network activity disabled</source> + <location line="+150"/> + <source>&Disconnect</source> <translation type="unfinished"></translation> </message> <message> - <location line="+77"/> - <source>Executing command without any wallet</source> + <location line="+1"/> + <source>1 &hour</source> <translation type="unfinished"></translation> </message> <message> - <location line="-2"/> - <source>Executing command using "%1" wallet</source> + <location line="+1"/> + <source>1 d&ay</source> <translation type="unfinished"></translation> </message> <message> - <location line="-319"/> - <source>Disconnect</source> + <location line="+1"/> + <source>1 &week</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>1 hour</source> + <source>1 &year</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>1 day</source> + <location line="+22"/> + <source>&Unban</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>1 week</source> + <location line="+221"/> + <source>Network activity disabled</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>1 year</source> + <location line="+77"/> + <source>Executing command without any wallet</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> - <source>Unban</source> + <location line="-2"/> + <source>Executing command using "%1" wallet</source> <translation type="unfinished"></translation> </message> <message> - <location line="+150"/> + <location line="-146"/> <source>Welcome to the %1 RPC console. Use up and down arrows to navigate history, and %2 to clear screen. Use %3 and %4 to increase or decrease the font size. @@ -2997,7 +3039,7 @@ For more information on using this console, type %6. <translation type="unfinished"></translation> </message> <message> - <location filename="../rpcconsole.h" line="-38"/> + <location filename="../rpcconsole.h" line="-40"/> <source>Unknown</source> <translation type="unfinished"></translation> </message> @@ -3102,27 +3144,27 @@ For more information on using this console, type %6. </message> <message> <location filename="../receivecoinsdialog.cpp" line="+47"/> - <source>Copy URI</source> + <source>Copy &URI</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy address</source> + <source>&Copy address</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy label</source> + <source>Copy &label</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy message</source> + <source>Copy &message</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy amount</source> + <source>Copy &amount</source> <translation type="unfinished"></translation> </message> <message> @@ -3180,6 +3222,16 @@ For more information on using this console, type %6. </message> <message> <location line="+10"/> + <source>&Verify</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Verify this address on e.g. a hardware wallet screen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> <source>&Save Image…</source> <translation type="unfinished"></translation> </message> @@ -3189,7 +3241,7 @@ For more information on using this console, type %6. <translation type="unfinished"></translation> </message> <message> - <location filename="../forms/receiverequestdialog.ui" line="-221"/> + <location filename="../forms/receiverequestdialog.ui" line="-234"/> <source>Payment information</source> <translation type="unfinished"></translation> </message> @@ -3197,7 +3249,7 @@ For more information on using this console, type %6. <context> <name>RecentRequestsTableModel</name> <message> - <location filename="../recentrequeststablemodel.cpp" line="+30"/> + <location filename="../recentrequeststablemodel.cpp" line="+32"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -3236,7 +3288,7 @@ For more information on using this console, type %6. <name>SendCoinsDialog</name> <message> <location filename="../forms/sendcoinsdialog.ui" line="+14"/> - <location filename="../sendcoinsdialog.cpp" line="+674"/> + <location filename="../sendcoinsdialog.cpp" line="+738"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -3423,7 +3475,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation>S&end</translation> </message> <message> - <location filename="../sendcoinsdialog.cpp" line="-582"/> + <location filename="../sendcoinsdialog.cpp" line="-646"/> <source>Copy quantity</source> <translation type="unfinished"></translation> </message> @@ -3463,7 +3515,24 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+29"/> + <location line="+30"/> + <source>Sign on device</source> + <extracomment>"device" usually means a hardware wallet</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Connect your hardware wallet first.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> + <source>Set external signer script path in Options -> Wallet</source> + <extracomment>"External signer" means using devices such as hardware wallets.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Cr&eate Unsigned</source> <translation type="unfinished"></translation> </message> @@ -3508,7 +3577,29 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+44"/> + <location line="+0"/> + <source>Sign and send</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+25"/> + <source>Sign failed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> + <source>External signer not found</source> + <extracomment>"External signer" means using devices such as hardware wallets.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> + <source>External signer failure</source> + <extracomment>"External signer" means using devices such as hardware wallets.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+58"/> <source>Save Transaction Data</source> <translation type="unfinished"></translation> </message> @@ -3524,7 +3615,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="-76"/> + <location line="+175"/> + <source>External balance:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="-302"/> <source>or</source> <translation type="unfinished"></translation> </message> @@ -3569,12 +3665,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>Send</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+229"/> + <location line="+283"/> <source>Watch-only balance:</source> <translation type="unfinished"></translation> </message> @@ -4434,62 +4525,62 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+75"/> - <source>Abandon transaction</source> + <location line="-26"/> + <source>Range…</source> <translation type="unfinished"></translation> </message> <message> - <location line="-3"/> - <source>Increase transaction fee</source> + <location line="+90"/> + <source>&Copy address</source> <translation type="unfinished"></translation> </message> <message> - <location line="-8"/> - <source>Copy address</source> + <location line="+1"/> + <source>Copy &label</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy label</source> + <source>Copy &amount</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy amount</source> + <source>Copy transaction &ID</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy transaction ID</source> + <source>Copy &raw transaction</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy raw transaction</source> + <source>Copy full transaction &details</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Copy full transaction details</source> + <source>&Show transaction details</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> - <source>Edit address label</source> + <location line="+2"/> + <source>Increase transaction &fee</source> <translation type="unfinished"></translation> </message> <message> - <location line="-6"/> - <source>Show transaction details</source> + <location line="+3"/> + <source>A&bandon transaction</source> <translation type="unfinished"></translation> </message> <message> - <location line="-96"/> - <source>Range…</source> + <location line="+1"/> + <source>&Edit address label</source> <translation type="unfinished"></translation> </message> <message> - <location line="+276"/> + <location line="+174"/> <source>Export Transaction History</source> <translation type="unfinished"></translation> </message> @@ -4576,7 +4667,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>WalletController</name> <message> - <location filename="../walletcontroller.cpp" line="-250"/> + <location filename="../walletcontroller.cpp" line="-262"/> <source>Close wallet</source> <translation type="unfinished"></translation> </message> @@ -4604,7 +4695,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>WalletFrame</name> <message> - <location filename="../walletframe.cpp" line="+39"/> + <location filename="../walletframe.cpp" line="+35"/> <source>No wallet has been loaded. Go to File > Open Wallet to load a wallet. - OR -</source> @@ -4624,7 +4715,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished">Send Coins</translation> </message> <message> - <location line="+279"/> + <location line="+260"/> <location line="+52"/> <location line="+13"/> <location line="+5"/> @@ -4692,7 +4783,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+20"/> + <location line="+14"/> + <source>Can't display address</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+18"/> <source>default wallet</source> <translation type="unfinished"></translation> </message> @@ -4842,6 +4938,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+3"/> + <source>Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Error: Listening for incoming connections failed (listen returned error %s)</source> <translation type="unfinished"></translation> </message> @@ -4947,11 +5048,6 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+3"/> - <source>Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+3"/> <source>Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.</source> <translation type="unfinished"></translation> </message> @@ -5162,6 +5258,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: No %s addresses available.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Unable to parse version %u as a uint32_t</source> <translation type="unfinished"></translation> </message> @@ -5442,6 +5543,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Transaction needs a change address, but we can't generate it. %s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Transaction too large</source> <translation type="unfinished"></translation> </message> @@ -5502,6 +5608,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unknown new rules activated (versionbit %i)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unsupported logging category %s=%s.</source> <translation type="unfinished"></translation> </message> @@ -5535,10 +5646,5 @@ Go to File > Open Wallet to load a wallet. <source>Wallet needed to be rewritten: restart %s to complete</source> <translation type="unfinished"></translation> </message> - <message> - <location line="+1"/> - <source>Warning: unknown new rules activated (versionbit %i)</source> - <translation type="unfinished"></translation> - </message> </context> </TS> diff --git a/src/qt/locale/bitcoin_en.xlf b/src/qt/locale/bitcoin_en.xlf index ffe7812738..caefddc663 100644 --- a/src/qt/locale/bitcoin_en.xlf +++ b/src/qt/locale/bitcoin_en.xlf @@ -56,6 +56,7 @@ <source xml:space="preserve">&Delete</source> <target xml:space="preserve">&Delete</target> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../addressbookpage.cpp</context><context context-type="linenumber">122</context></context-group> </trans-unit> </group> </body></file> @@ -98,43 +99,38 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context-group purpose="location"><context context-type="linenumber">109</context></context-group> </trans-unit> <trans-unit id="_msg19"> - <source xml:space="preserve">Copy Address</source> + <source xml:space="preserve">&Copy Address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">117</context></context-group> </trans-unit> <trans-unit id="_msg20"> - <source xml:space="preserve">Copy Label</source> + <source xml:space="preserve">Copy &Label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">118</context></context-group> </trans-unit> <trans-unit id="_msg21"> - <source xml:space="preserve">Edit</source> + <source xml:space="preserve">&Edit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">119</context></context-group> </trans-unit> <trans-unit id="_msg22"> - <source xml:space="preserve">Delete</source> - <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">122</context></context-group> - </trans-unit> - <trans-unit id="_msg23"> <source xml:space="preserve">Export Address List</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg24"> + <trans-unit id="_msg23"> <source xml:space="preserve">Comma separated file</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">286</context></context-group> <note annotates="source" from="developer">Expanded name of the CSV file format. See https://en.wikipedia.org/wiki/Comma-separated_values</note> </trans-unit> - <trans-unit id="_msg25"> + <trans-unit id="_msg24"> <source xml:space="preserve">There was an error trying to save the address list to %1. Please try again.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">302</context></context-group> <note annotates="source" from="developer">An error message. %1 is a stand-in argument for the name of the file we attempted to save to.</note> </trans-unit> - <trans-unit id="_msg26"> + <trans-unit id="_msg25"> <source xml:space="preserve">Exporting Failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">299</context></context-group> @@ -143,17 +139,17 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../addresstablemodel.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="AddressTableModel"> - <trans-unit id="_msg27"> + <trans-unit id="_msg26"> <source xml:space="preserve">Label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">168</context></context-group> </trans-unit> - <trans-unit id="_msg28"> + <trans-unit id="_msg27"> <source xml:space="preserve">Address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">168</context></context-group> </trans-unit> - <trans-unit id="_msg29"> + <trans-unit id="_msg28"> <source xml:space="preserve">(no label)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">206</context></context-group> @@ -162,27 +158,27 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/askpassphrasedialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="AskPassphraseDialog"> - <trans-unit id="_msg30" approved="yes"> + <trans-unit id="_msg29" approved="yes"> <source xml:space="preserve">Passphrase Dialog</source> <target xml:space="preserve">Passphrase Dialog</target> <context-group purpose="location"><context context-type="linenumber">26</context></context-group> </trans-unit> - <trans-unit id="_msg31" approved="yes"> + <trans-unit id="_msg30" approved="yes"> <source xml:space="preserve">Enter passphrase</source> <target xml:space="preserve">Enter passphrase</target> <context-group purpose="location"><context context-type="linenumber">56</context></context-group> </trans-unit> - <trans-unit id="_msg32" approved="yes"> + <trans-unit id="_msg31" approved="yes"> <source xml:space="preserve">New passphrase</source> <target xml:space="preserve">New passphrase</target> <context-group purpose="location"><context context-type="linenumber">70</context></context-group> </trans-unit> - <trans-unit id="_msg33" approved="yes"> + <trans-unit id="_msg32" approved="yes"> <source xml:space="preserve">Repeat new passphrase</source> <target xml:space="preserve">Repeat new passphrase</target> <context-group purpose="location"><context context-type="linenumber">84</context></context-group> </trans-unit> - <trans-unit id="_msg34"> + <trans-unit id="_msg33"> <source xml:space="preserve">Show passphrase</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> @@ -191,83 +187,83 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../askpassphrasedialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="AskPassphraseDialog"> - <trans-unit id="_msg35"> + <trans-unit id="_msg34"> <source xml:space="preserve">Encrypt wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg36"> + <trans-unit id="_msg35"> <source xml:space="preserve">This operation needs your wallet passphrase to unlock the wallet.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">54</context></context-group> </trans-unit> - <trans-unit id="_msg37"> + <trans-unit id="_msg36"> <source xml:space="preserve">Unlock wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">59</context></context-group> </trans-unit> - <trans-unit id="_msg38"> + <trans-unit id="_msg37"> <source xml:space="preserve">Change passphrase</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">62</context></context-group> </trans-unit> - <trans-unit id="_msg39"> + <trans-unit id="_msg38"> <source xml:space="preserve">Confirm wallet encryption</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> - <trans-unit id="_msg40"> + <trans-unit id="_msg39"> <source xml:space="preserve">Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg41"> + <trans-unit id="_msg40"> <source xml:space="preserve">Are you sure you wish to encrypt your wallet?</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg42"> + <trans-unit id="_msg41"> <source xml:space="preserve">Wallet encrypted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">129</context></context-group> <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg43"> + <trans-unit id="_msg42"> <source xml:space="preserve">Enter the new passphrase for the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">48</context></context-group> </trans-unit> - <trans-unit id="_msg44"> + <trans-unit id="_msg43"> <source xml:space="preserve">Enter the old passphrase and new passphrase for the wallet.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">63</context></context-group> </trans-unit> - <trans-unit id="_msg45"> + <trans-unit id="_msg44"> <source xml:space="preserve">Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">118</context></context-group> </trans-unit> - <trans-unit id="_msg46"> + <trans-unit id="_msg45"> <source xml:space="preserve">Wallet to be encrypted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg47"> + <trans-unit id="_msg46"> <source xml:space="preserve">Your wallet is about to be encrypted. </source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg48"> + <trans-unit id="_msg47"> <source xml:space="preserve">Your wallet is now encrypted. </source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">131</context></context-group> </trans-unit> - <trans-unit id="_msg49"> + <trans-unit id="_msg48"> <source xml:space="preserve">IMPORTANT: Any previous backups you have made of your wallet file should be replaced with the newly generated, encrypted wallet file. For security reasons, previous backups of the unencrypted wallet file will become useless as soon as you start using the new, encrypted wallet.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">133</context></context-group> </trans-unit> - <trans-unit id="_msg50"> + <trans-unit id="_msg49"> <source xml:space="preserve">Wallet encryption failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> @@ -275,35 +271,35 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context-group purpose="location"><context context-type="linenumber">179</context></context-group> <context-group purpose="location"><context context-type="linenumber">185</context></context-group> </trans-unit> - <trans-unit id="_msg51"> + <trans-unit id="_msg50"> <source xml:space="preserve">Wallet encryption failed due to an internal error. Your wallet was not encrypted.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">140</context></context-group> </trans-unit> - <trans-unit id="_msg52"> + <trans-unit id="_msg51"> <source xml:space="preserve">The supplied passphrases do not match.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">148</context></context-group> <context-group purpose="location"><context context-type="linenumber">186</context></context-group> </trans-unit> - <trans-unit id="_msg53"> + <trans-unit id="_msg52"> <source xml:space="preserve">Wallet unlock failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">159</context></context-group> <context-group purpose="location"><context context-type="linenumber">165</context></context-group> </trans-unit> - <trans-unit id="_msg54"> + <trans-unit id="_msg53"> <source xml:space="preserve">The passphrase entered for the wallet decryption was incorrect.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">160</context></context-group> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg55"> + <trans-unit id="_msg54"> <source xml:space="preserve">Wallet passphrase was successfully changed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg56"> + <trans-unit id="_msg55"> <source xml:space="preserve">Warning: The Caps Lock key is on!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">220</context></context-group> @@ -313,12 +309,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../bantablemodel.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="BanTableModel"> - <trans-unit id="_msg57"> + <trans-unit id="_msg56"> <source xml:space="preserve">IP/Netmask</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">85</context></context-group> </trans-unit> - <trans-unit id="_msg58"> + <trans-unit id="_msg57"> <source xml:space="preserve">Banned Until</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">85</context></context-group> @@ -327,683 +323,683 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../bitcoin.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="BitcoinApplication"> - <trans-unit id="_msg59"> + <trans-unit id="_msg58"> <source xml:space="preserve">Runaway exception</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">420</context></context-group> + <context-group purpose="location"><context context-type="linenumber">421</context></context-group> </trans-unit> - <trans-unit id="_msg60"> + <trans-unit id="_msg59"> <source xml:space="preserve">A fatal error occurred. %1 can no longer continue safely and will quit.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">421</context></context-group> + <context-group purpose="location"><context context-type="linenumber">422</context></context-group> </trans-unit> - <trans-unit id="_msg61"> + <trans-unit id="_msg60"> <source xml:space="preserve">Internal error</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">430</context></context-group> + <context-group purpose="location"><context context-type="linenumber">431</context></context-group> </trans-unit> - <trans-unit id="_msg62"> + <trans-unit id="_msg61"> <source xml:space="preserve">An internal error occurred. %1 will attempt to continue safely. This is an unexpected bug which can be reported as described below.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">431</context></context-group> + <context-group purpose="location"><context context-type="linenumber">432</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg63"> + <trans-unit id="_msg62"> <source xml:space="preserve">Error: Specified data directory "%1" does not exist.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">542</context></context-group> + <context-group purpose="location"><context context-type="linenumber">544</context></context-group> </trans-unit> - <trans-unit id="_msg64"> + <trans-unit id="_msg63"> <source xml:space="preserve">Error: Cannot parse configuration file: %1.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">548</context></context-group> + <context-group purpose="location"><context context-type="linenumber">550</context></context-group> </trans-unit> - <trans-unit id="_msg65"> + <trans-unit id="_msg64"> <source xml:space="preserve">Error: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">563</context></context-group> + <context-group purpose="location"><context context-type="linenumber">565</context></context-group> </trans-unit> - <trans-unit id="_msg66"> + <trans-unit id="_msg65"> <source xml:space="preserve">Error initializing settings: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">572</context></context-group> + <context-group purpose="location"><context context-type="linenumber">574</context></context-group> </trans-unit> - <trans-unit id="_msg67"> + <trans-unit id="_msg66"> <source xml:space="preserve">%1 didn't yet exit safely…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">635</context></context-group> + <context-group purpose="location"><context context-type="linenumber">637</context></context-group> </trans-unit> </group> </body></file> <file original="../bitcoingui.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="BitcoinGUI"> - <trans-unit id="_msg68" approved="yes"> + <trans-unit id="_msg67" approved="yes"> <source xml:space="preserve">&Overview</source> <target xml:space="preserve">&Overview</target> <context-group purpose="location"><context context-type="linenumber">245</context></context-group> </trans-unit> - <trans-unit id="_msg69" approved="yes"> + <trans-unit id="_msg68" approved="yes"> <source xml:space="preserve">Show general overview of wallet</source> <target xml:space="preserve">Show general overview of wallet</target> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg70" approved="yes"> + <trans-unit id="_msg69" approved="yes"> <source xml:space="preserve">&Transactions</source> <target xml:space="preserve">&Transactions</target> <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg71" approved="yes"> + <trans-unit id="_msg70" approved="yes"> <source xml:space="preserve">Browse transaction history</source> <target xml:space="preserve">Browse transaction history</target> <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> - <trans-unit id="_msg72" approved="yes"> + <trans-unit id="_msg71" approved="yes"> <source xml:space="preserve">E&xit</source> <target xml:space="preserve">E&xit</target> <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> - <trans-unit id="_msg73" approved="yes"> + <trans-unit id="_msg72" approved="yes"> <source xml:space="preserve">Quit application</source> <target xml:space="preserve">Quit application</target> <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> - <trans-unit id="_msg74"> + <trans-unit id="_msg73"> <source xml:space="preserve">&About %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">302</context></context-group> </trans-unit> - <trans-unit id="_msg75"> + <trans-unit id="_msg74"> <source xml:space="preserve">Show information about %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> - <trans-unit id="_msg76" approved="yes"> + <trans-unit id="_msg75" approved="yes"> <source xml:space="preserve">About &Qt</source> <target xml:space="preserve">About &Qt</target> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> - <trans-unit id="_msg77" approved="yes"> + <trans-unit id="_msg76" approved="yes"> <source xml:space="preserve">Show information about Qt</source> <target xml:space="preserve">Show information about Qt</target> <context-group purpose="location"><context context-type="linenumber">307</context></context-group> </trans-unit> - <trans-unit id="_msg78"> + <trans-unit id="_msg77"> <source xml:space="preserve">Modify configuration options for %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">310</context></context-group> </trans-unit> - <trans-unit id="_msg79"> + <trans-unit id="_msg78"> <source xml:space="preserve">Create a new wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">356</context></context-group> </trans-unit> - <trans-unit id="_msg80"> + <trans-unit id="_msg79"> <source xml:space="preserve">Wallet:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">565</context></context-group> </trans-unit> - <trans-unit id="_msg81"> + <trans-unit id="_msg80"> <source xml:space="preserve">Network activity disabled.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">923</context></context-group> + <context-group purpose="location"><context context-type="linenumber">938</context></context-group> <note annotates="source" from="developer">A substring of the tooltip.</note> </trans-unit> - <trans-unit id="_msg82"> + <trans-unit id="_msg81"> <source xml:space="preserve">Proxy is <b>enabled</b>: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1349</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1362</context></context-group> </trans-unit> - <trans-unit id="_msg83" approved="yes"> + <trans-unit id="_msg82" approved="yes"> <source xml:space="preserve">Send coins to a Bitcoin address</source> <target xml:space="preserve">Send coins to a Bitcoin address</target> <context-group purpose="location"><context context-type="linenumber">253</context></context-group> </trans-unit> - <trans-unit id="_msg84" approved="yes"> + <trans-unit id="_msg83" approved="yes"> <source xml:space="preserve">Backup wallet to another location</source> <target xml:space="preserve">Backup wallet to another location</target> <context-group purpose="location"><context context-type="linenumber">320</context></context-group> </trans-unit> - <trans-unit id="_msg85" approved="yes"> + <trans-unit id="_msg84" approved="yes"> <source xml:space="preserve">Change the passphrase used for wallet encryption</source> <target xml:space="preserve">Change the passphrase used for wallet encryption</target> <context-group purpose="location"><context context-type="linenumber">322</context></context-group> </trans-unit> - <trans-unit id="_msg86" approved="yes"> + <trans-unit id="_msg85" approved="yes"> <source xml:space="preserve">&Send</source> <target xml:space="preserve">&Send</target> <context-group purpose="location"><context context-type="linenumber">252</context></context-group> </trans-unit> - <trans-unit id="_msg87" approved="yes"> + <trans-unit id="_msg86" approved="yes"> <source xml:space="preserve">&Receive</source> <target xml:space="preserve">&Receive</target> <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> - <trans-unit id="_msg88"> + <trans-unit id="_msg87"> <source xml:space="preserve">&Options…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> - <trans-unit id="_msg89" approved="yes"> + <trans-unit id="_msg88" approved="yes"> <source xml:space="preserve">&Show / Hide</source> <target xml:space="preserve">&Show / Hide</target> <context-group purpose="location"><context context-type="linenumber">313</context></context-group> </trans-unit> - <trans-unit id="_msg90" approved="yes"> + <trans-unit id="_msg89" approved="yes"> <source xml:space="preserve">Show or hide the main Window</source> <target xml:space="preserve">Show or hide the main Window</target> <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> - <trans-unit id="_msg91"> + <trans-unit id="_msg90"> <source xml:space="preserve">&Encrypt Wallet…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">316</context></context-group> </trans-unit> - <trans-unit id="_msg92" approved="yes"> + <trans-unit id="_msg91" approved="yes"> <source xml:space="preserve">Encrypt the private keys that belong to your wallet</source> <target xml:space="preserve">Encrypt the private keys that belong to your wallet</target> <context-group purpose="location"><context context-type="linenumber">317</context></context-group> </trans-unit> - <trans-unit id="_msg93"> + <trans-unit id="_msg92"> <source xml:space="preserve">&Backup Wallet…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">319</context></context-group> </trans-unit> - <trans-unit id="_msg94"> + <trans-unit id="_msg93"> <source xml:space="preserve">&Change Passphrase…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg95"> + <trans-unit id="_msg94"> <source xml:space="preserve">Sign &message…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg96" approved="yes"> + <trans-unit id="_msg95" approved="yes"> <source xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</source> <target xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</target> <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> - <trans-unit id="_msg97"> + <trans-unit id="_msg96"> <source xml:space="preserve">&Verify message…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">325</context></context-group> </trans-unit> - <trans-unit id="_msg98" approved="yes"> + <trans-unit id="_msg97" approved="yes"> <source xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</source> <target xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</target> <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg99"> + <trans-unit id="_msg98"> <source xml:space="preserve">&Load PSBT from file…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">327</context></context-group> </trans-unit> - <trans-unit id="_msg100"> + <trans-unit id="_msg99"> <source xml:space="preserve">Load PSBT from clipboard…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">329</context></context-group> </trans-unit> - <trans-unit id="_msg101"> + <trans-unit id="_msg100"> <source xml:space="preserve">Open &URI…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">343</context></context-group> </trans-unit> - <trans-unit id="_msg102"> + <trans-unit id="_msg101"> <source xml:space="preserve">Close Wallet…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">351</context></context-group> </trans-unit> - <trans-unit id="_msg103"> + <trans-unit id="_msg102"> <source xml:space="preserve">Create Wallet…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">354</context></context-group> </trans-unit> - <trans-unit id="_msg104"> + <trans-unit id="_msg103"> <source xml:space="preserve">Close All Wallets…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">358</context></context-group> </trans-unit> - <trans-unit id="_msg105" approved="yes"> + <trans-unit id="_msg104" approved="yes"> <source xml:space="preserve">&File</source> <target xml:space="preserve">&File</target> <context-group purpose="location"><context context-type="linenumber">455</context></context-group> </trans-unit> - <trans-unit id="_msg106" approved="yes"> + <trans-unit id="_msg105" approved="yes"> <source xml:space="preserve">&Settings</source> <target xml:space="preserve">&Settings</target> <context-group purpose="location"><context context-type="linenumber">473</context></context-group> </trans-unit> - <trans-unit id="_msg107" approved="yes"> + <trans-unit id="_msg106" approved="yes"> <source xml:space="preserve">&Help</source> <target xml:space="preserve">&Help</target> <context-group purpose="location"><context context-type="linenumber">534</context></context-group> </trans-unit> - <trans-unit id="_msg108" approved="yes"> + <trans-unit id="_msg107" approved="yes"> <source xml:space="preserve">Tabs toolbar</source> <target xml:space="preserve">Tabs toolbar</target> <context-group purpose="location"><context context-type="linenumber">545</context></context-group> </trans-unit> - <trans-unit id="_msg109"> + <trans-unit id="_msg108"> <source xml:space="preserve">Syncing Headers (%1%)…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">967</context></context-group> + <context-group purpose="location"><context context-type="linenumber">982</context></context-group> </trans-unit> - <trans-unit id="_msg110"> + <trans-unit id="_msg109"> <source xml:space="preserve">Synchronizing with network…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1013</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1028</context></context-group> </trans-unit> - <trans-unit id="_msg111"> + <trans-unit id="_msg110"> <source xml:space="preserve">Indexing blocks on disk…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1018</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1033</context></context-group> </trans-unit> - <trans-unit id="_msg112"> + <trans-unit id="_msg111"> <source xml:space="preserve">Processing blocks on disk…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1020</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1035</context></context-group> </trans-unit> - <trans-unit id="_msg113"> + <trans-unit id="_msg112"> <source xml:space="preserve">Reindexing blocks on disk…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1024</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1039</context></context-group> </trans-unit> - <trans-unit id="_msg114"> + <trans-unit id="_msg113"> <source xml:space="preserve">Connecting to peers…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1030</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1045</context></context-group> </trans-unit> - <trans-unit id="_msg115"> + <trans-unit id="_msg114"> <source xml:space="preserve">Request payments (generates QR codes and bitcoin: URIs)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">264</context></context-group> </trans-unit> - <trans-unit id="_msg116"> + <trans-unit id="_msg115"> <source xml:space="preserve">Show the list of used sending addresses and labels</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> - <trans-unit id="_msg117"> + <trans-unit id="_msg116"> <source xml:space="preserve">Show the list of used receiving addresses and labels</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">341</context></context-group> </trans-unit> - <trans-unit id="_msg118"> + <trans-unit id="_msg117"> <source xml:space="preserve">&Command-line options</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">361</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">1039</context></context-group> - <trans-unit id="_msg119[0]" approved="yes"> + <context-group purpose="location"><context context-type="linenumber">1054</context></context-group> + <trans-unit id="_msg118[0]" approved="yes"> <source xml:space="preserve">Processed %n block(s) of transaction history.</source> <target xml:space="preserve">Processed %n block of transaction history.</target> </trans-unit> - <trans-unit id="_msg119[1]" approved="yes"> + <trans-unit id="_msg118[1]" approved="yes"> <source xml:space="preserve">Processed %n block(s) of transaction history.</source> <target xml:space="preserve">Processed %n blocks of transaction history.</target> </trans-unit> </group> - <trans-unit id="_msg120" approved="yes"> + <trans-unit id="_msg119" approved="yes"> <source xml:space="preserve">%1 behind</source> <target xml:space="preserve">%1 behind</target> - <context-group purpose="location"><context context-type="linenumber">1062</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1077</context></context-group> </trans-unit> - <trans-unit id="_msg121"> + <trans-unit id="_msg120"> <source xml:space="preserve">Catching up…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1067</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1082</context></context-group> </trans-unit> - <trans-unit id="_msg122" approved="yes"> + <trans-unit id="_msg121" approved="yes"> <source xml:space="preserve">Last received block was generated %1 ago.</source> <target xml:space="preserve">Last received block was generated %1 ago.</target> - <context-group purpose="location"><context context-type="linenumber">1086</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1101</context></context-group> </trans-unit> - <trans-unit id="_msg123" approved="yes"> + <trans-unit id="_msg122" approved="yes"> <source xml:space="preserve">Transactions after this will not yet be visible.</source> <target xml:space="preserve">Transactions after this will not yet be visible.</target> - <context-group purpose="location"><context context-type="linenumber">1088</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1103</context></context-group> </trans-unit> - <trans-unit id="_msg124" approved="yes"> + <trans-unit id="_msg123" approved="yes"> <source xml:space="preserve">Error</source> <target xml:space="preserve">Error</target> - <context-group purpose="location"><context context-type="linenumber">1113</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1128</context></context-group> </trans-unit> - <trans-unit id="_msg125" approved="yes"> + <trans-unit id="_msg124" approved="yes"> <source xml:space="preserve">Warning</source> <target xml:space="preserve">Warning</target> - <context-group purpose="location"><context context-type="linenumber">1117</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1132</context></context-group> </trans-unit> - <trans-unit id="_msg126" approved="yes"> + <trans-unit id="_msg125" approved="yes"> <source xml:space="preserve">Information</source> <target xml:space="preserve">Information</target> - <context-group purpose="location"><context context-type="linenumber">1121</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1136</context></context-group> </trans-unit> - <trans-unit id="_msg127" approved="yes"> + <trans-unit id="_msg126" approved="yes"> <source xml:space="preserve">Up to date</source> <target xml:space="preserve">Up to date</target> - <context-group purpose="location"><context context-type="linenumber">1043</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1058</context></context-group> </trans-unit> - <trans-unit id="_msg128"> + <trans-unit id="_msg127"> <source xml:space="preserve">Load Partially Signed Bitcoin Transaction</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">328</context></context-group> </trans-unit> - <trans-unit id="_msg129"> + <trans-unit id="_msg128"> <source xml:space="preserve">Load Partially Signed Bitcoin Transaction from clipboard</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">330</context></context-group> </trans-unit> - <trans-unit id="_msg130"> + <trans-unit id="_msg129"> <source xml:space="preserve">Node window</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">332</context></context-group> </trans-unit> - <trans-unit id="_msg131"> + <trans-unit id="_msg130"> <source xml:space="preserve">Open node debugging and diagnostic console</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">333</context></context-group> </trans-unit> - <trans-unit id="_msg132"> + <trans-unit id="_msg131"> <source xml:space="preserve">&Sending addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> - <trans-unit id="_msg133"> + <trans-unit id="_msg132"> <source xml:space="preserve">&Receiving addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">340</context></context-group> </trans-unit> - <trans-unit id="_msg134"> + <trans-unit id="_msg133"> <source xml:space="preserve">Open a bitcoin: URI</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">344</context></context-group> </trans-unit> - <trans-unit id="_msg135"> + <trans-unit id="_msg134"> <source xml:space="preserve">Open Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">346</context></context-group> </trans-unit> - <trans-unit id="_msg136"> + <trans-unit id="_msg135"> <source xml:space="preserve">Open a wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">348</context></context-group> </trans-unit> - <trans-unit id="_msg137"> + <trans-unit id="_msg136"> <source xml:space="preserve">Close wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">352</context></context-group> </trans-unit> - <trans-unit id="_msg138"> + <trans-unit id="_msg137"> <source xml:space="preserve">Close all wallets</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">359</context></context-group> </trans-unit> - <trans-unit id="_msg139"> + <trans-unit id="_msg138"> <source xml:space="preserve">Show the %1 help message to get a list with possible Bitcoin command-line options</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">363</context></context-group> </trans-unit> - <trans-unit id="_msg140"> + <trans-unit id="_msg139"> <source xml:space="preserve">&Mask values</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">365</context></context-group> </trans-unit> - <trans-unit id="_msg141"> + <trans-unit id="_msg140"> <source xml:space="preserve">Mask the values in the Overview tab</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">367</context></context-group> </trans-unit> - <trans-unit id="_msg142"> + <trans-unit id="_msg141"> <source xml:space="preserve">default wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">399</context></context-group> </trans-unit> - <trans-unit id="_msg143"> + <trans-unit id="_msg142"> <source xml:space="preserve">No wallets available</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">420</context></context-group> </trans-unit> - <trans-unit id="_msg144"> + <trans-unit id="_msg143"> <source xml:space="preserve">&Window</source> <target xml:space="preserve" state="needs-review-translation">&Window</target> <context-group purpose="location"><context context-type="linenumber">484</context></context-group> </trans-unit> - <trans-unit id="_msg145"> + <trans-unit id="_msg144"> <source xml:space="preserve">Minimize</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">486</context></context-group> </trans-unit> - <trans-unit id="_msg146"> + <trans-unit id="_msg145"> <source xml:space="preserve">Zoom</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">496</context></context-group> </trans-unit> - <trans-unit id="_msg147"> + <trans-unit id="_msg146"> <source xml:space="preserve">Main Window</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">514</context></context-group> </trans-unit> - <trans-unit id="_msg148"> + <trans-unit id="_msg147"> <source xml:space="preserve">%1 client</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">762</context></context-group> + <context-group purpose="location"><context context-type="linenumber">777</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">920</context></context-group> + <context-group purpose="location"><context context-type="linenumber">935</context></context-group> <note annotates="source" from="developer">A substring of the tooltip.</note> - <trans-unit id="_msg149[0]"> + <trans-unit id="_msg148[0]"> <source xml:space="preserve">%n active connection(s) to Bitcoin network.</source> <target xml:space="preserve"></target> </trans-unit> - <trans-unit id="_msg149[1]"> + <trans-unit id="_msg148[1]"> <source xml:space="preserve">%n active connection(s) to Bitcoin network.</source> <target xml:space="preserve"></target> </trans-unit> </group> - <trans-unit id="_msg150"> + <trans-unit id="_msg149"> <source xml:space="preserve">Click for more actions.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">930</context></context-group> + <context-group purpose="location"><context context-type="linenumber">945</context></context-group> <note annotates="source" from="developer">A substring of the tooltip. "More actions" are available via the context menu.</note> </trans-unit> - <trans-unit id="_msg151"> + <trans-unit id="_msg150"> <source xml:space="preserve">Show Peers tab</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">947</context></context-group> + <context-group purpose="location"><context context-type="linenumber">962</context></context-group> <note annotates="source" from="developer">A context menu item. The "Peers tab" is an element of the "Node window".</note> </trans-unit> - <trans-unit id="_msg152"> + <trans-unit id="_msg151"> <source xml:space="preserve">Disable network activity</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">955</context></context-group> + <context-group purpose="location"><context context-type="linenumber">970</context></context-group> <note annotates="source" from="developer">A context menu item.</note> </trans-unit> - <trans-unit id="_msg153"> + <trans-unit id="_msg152"> <source xml:space="preserve">Enable network activity</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">957</context></context-group> + <context-group purpose="location"><context context-type="linenumber">972</context></context-group> <note annotates="source" from="developer">A context menu item. The network activity was disabled previously.</note> </trans-unit> - <trans-unit id="_msg154"> + <trans-unit id="_msg153"> <source xml:space="preserve">Error: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1114</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1129</context></context-group> </trans-unit> - <trans-unit id="_msg155"> + <trans-unit id="_msg154"> <source xml:space="preserve">Warning: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1118</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1133</context></context-group> </trans-unit> - <trans-unit id="_msg156"> + <trans-unit id="_msg155"> <source xml:space="preserve">Date: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1228</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1241</context></context-group> </trans-unit> - <trans-unit id="_msg157"> + <trans-unit id="_msg156"> <source xml:space="preserve">Amount: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1229</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1242</context></context-group> </trans-unit> - <trans-unit id="_msg158"> + <trans-unit id="_msg157"> <source xml:space="preserve">Wallet: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1231</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1244</context></context-group> </trans-unit> - <trans-unit id="_msg159"> + <trans-unit id="_msg158"> <source xml:space="preserve">Type: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1233</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1246</context></context-group> </trans-unit> - <trans-unit id="_msg160"> + <trans-unit id="_msg159"> <source xml:space="preserve">Label: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1235</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1248</context></context-group> </trans-unit> - <trans-unit id="_msg161"> + <trans-unit id="_msg160"> <source xml:space="preserve">Address: %1 </source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1237</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1250</context></context-group> </trans-unit> - <trans-unit id="_msg162" approved="yes"> + <trans-unit id="_msg161" approved="yes"> <source xml:space="preserve">Sent transaction</source> <target xml:space="preserve">Sent transaction</target> - <context-group purpose="location"><context context-type="linenumber">1238</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1251</context></context-group> </trans-unit> - <trans-unit id="_msg163" approved="yes"> + <trans-unit id="_msg162" approved="yes"> <source xml:space="preserve">Incoming transaction</source> <target xml:space="preserve">Incoming transaction</target> - <context-group purpose="location"><context context-type="linenumber">1238</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1251</context></context-group> </trans-unit> - <trans-unit id="_msg164"> + <trans-unit id="_msg163"> <source xml:space="preserve">HD key generation is <b>enabled</b></source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1290</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1303</context></context-group> </trans-unit> - <trans-unit id="_msg165"> + <trans-unit id="_msg164"> <source xml:space="preserve">HD key generation is <b>disabled</b></source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1290</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1303</context></context-group> </trans-unit> - <trans-unit id="_msg166"> + <trans-unit id="_msg165"> <source xml:space="preserve">Private key <b>disabled</b></source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1290</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1303</context></context-group> </trans-unit> - <trans-unit id="_msg167" approved="yes"> + <trans-unit id="_msg166" approved="yes"> <source xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>unlocked</b></source> <target xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>unlocked</b></target> - <context-group purpose="location"><context context-type="linenumber">1309</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1322</context></context-group> </trans-unit> - <trans-unit id="_msg168" approved="yes"> + <trans-unit id="_msg167" approved="yes"> <source xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>locked</b></source> <target xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>locked</b></target> - <context-group purpose="location"><context context-type="linenumber">1317</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1330</context></context-group> </trans-unit> - <trans-unit id="_msg169"> + <trans-unit id="_msg168"> <source xml:space="preserve">Original message:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1437</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1450</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="UnitDisplayStatusBarControl"> - <trans-unit id="_msg170"> + <trans-unit id="_msg169"> <source xml:space="preserve">Unit to show amounts in. Click to select another unit.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1478</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1491</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/coincontroldialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CoinControlDialog"> - <trans-unit id="_msg171"> + <trans-unit id="_msg170"> <source xml:space="preserve">Coin Selection</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg172"> + <trans-unit id="_msg171"> <source xml:space="preserve">Quantity:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">48</context></context-group> </trans-unit> - <trans-unit id="_msg173"> + <trans-unit id="_msg172"> <source xml:space="preserve">Bytes:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">77</context></context-group> </trans-unit> - <trans-unit id="_msg174"> + <trans-unit id="_msg173"> <source xml:space="preserve">Amount:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg175"> + <trans-unit id="_msg174"> <source xml:space="preserve">Fee:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> - <trans-unit id="_msg176"> + <trans-unit id="_msg175"> <source xml:space="preserve">Dust:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">154</context></context-group> </trans-unit> - <trans-unit id="_msg177"> + <trans-unit id="_msg176"> <source xml:space="preserve">After Fee:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> - <trans-unit id="_msg178"> + <trans-unit id="_msg177"> <source xml:space="preserve">Change:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg179"> + <trans-unit id="_msg178"> <source xml:space="preserve">(un)select all</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg180"> + <trans-unit id="_msg179"> <source xml:space="preserve">Tree mode</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">351</context></context-group> </trans-unit> - <trans-unit id="_msg181"> + <trans-unit id="_msg180"> <source xml:space="preserve">List mode</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">364</context></context-group> </trans-unit> - <trans-unit id="_msg182"> + <trans-unit id="_msg181"> <source xml:space="preserve">Amount</source> <target xml:space="preserve" state="needs-review-translation">Amount</target> <context-group purpose="location"><context context-type="linenumber">420</context></context-group> </trans-unit> - <trans-unit id="_msg183"> + <trans-unit id="_msg182"> <source xml:space="preserve">Received with label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">425</context></context-group> </trans-unit> - <trans-unit id="_msg184"> + <trans-unit id="_msg183"> <source xml:space="preserve">Received with address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">430</context></context-group> </trans-unit> - <trans-unit id="_msg185"> + <trans-unit id="_msg184"> <source xml:space="preserve">Date</source> <target xml:space="preserve" state="needs-review-translation">Date</target> <context-group purpose="location"><context context-type="linenumber">435</context></context-group> </trans-unit> - <trans-unit id="_msg186"> + <trans-unit id="_msg185"> <source xml:space="preserve">Confirmations</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">440</context></context-group> </trans-unit> - <trans-unit id="_msg187"> + <trans-unit id="_msg186"> <source xml:space="preserve">Confirmed</source> <target xml:space="preserve" state="needs-review-translation">Confirmed</target> <context-group purpose="location"><context context-type="linenumber">443</context></context-group> @@ -1012,34 +1008,38 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../coincontroldialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CoinControlDialog"> + <trans-unit id="_msg187"> + <source xml:space="preserve">Copy amount</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">66</context></context-group> + </trans-unit> <trans-unit id="_msg188"> - <source xml:space="preserve">Copy address</source> + <source xml:space="preserve">&Copy address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">55</context></context-group> </trans-unit> <trans-unit id="_msg189"> - <source xml:space="preserve">Copy label</source> + <source xml:space="preserve">Copy &label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">56</context></context-group> </trans-unit> <trans-unit id="_msg190"> - <source xml:space="preserve">Copy amount</source> + <source xml:space="preserve">Copy &amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">57</context></context-group> - <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> <trans-unit id="_msg191"> - <source xml:space="preserve">Copy transaction ID</source> + <source xml:space="preserve">Copy transaction &ID</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> <trans-unit id="_msg192"> - <source xml:space="preserve">Lock unspent</source> + <source xml:space="preserve">L&ock unspent</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> <trans-unit id="_msg193"> - <source xml:space="preserve">Unlock unspent</source> + <source xml:space="preserve">&Unlock unspent</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> @@ -1121,170 +1121,191 @@ Signing is only possible with addresses of the type 'legacy'.</source> <trans-unit id="_msg208"> <source xml:space="preserve">Creating Wallet <b>%1</b>…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + <context-group purpose="location"><context context-type="linenumber">254</context></context-group> </trans-unit> <trans-unit id="_msg209"> <source xml:space="preserve">Create wallet failed</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">281</context></context-group> + <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> <trans-unit id="_msg210"> <source xml:space="preserve">Create wallet warning</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">283</context></context-group> + <context-group purpose="location"><context context-type="linenumber">287</context></context-group> + </trans-unit> + <trans-unit id="_msg211"> + <source xml:space="preserve">Can't list signers</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="OpenWalletActivity"> - <trans-unit id="_msg211"> + <trans-unit id="_msg212"> <source xml:space="preserve">Open wallet failed</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">322</context></context-group> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg212"> + <trans-unit id="_msg213"> <source xml:space="preserve">Open wallet warning</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + <context-group purpose="location"><context context-type="linenumber">337</context></context-group> </trans-unit> - <trans-unit id="_msg213"> + <trans-unit id="_msg214"> <source xml:space="preserve">default wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">334</context></context-group> + <context-group purpose="location"><context context-type="linenumber">347</context></context-group> </trans-unit> - <trans-unit id="_msg214"> + <trans-unit id="_msg215"> <source xml:space="preserve">Opening Wallet <b>%1</b>…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">336</context></context-group> + <context-group purpose="location"><context context-type="linenumber">349</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="WalletController"> - <trans-unit id="_msg215"> - <source xml:space="preserve">Close wallet</source> - <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">86</context></context-group> - </trans-unit> <trans-unit id="_msg216"> - <source xml:space="preserve">Are you sure you wish to close the wallet <i>%1</i>?</source> + <source xml:space="preserve">Close wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> <trans-unit id="_msg217"> - <source xml:space="preserve">Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.</source> + <source xml:space="preserve">Are you sure you wish to close the wallet <i>%1</i>?</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> <trans-unit id="_msg218"> - <source xml:space="preserve">Close all wallets</source> + <source xml:space="preserve">Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">101</context></context-group> + <context-group purpose="location"><context context-type="linenumber">89</context></context-group> </trans-unit> <trans-unit id="_msg219"> - <source xml:space="preserve">Are you sure you wish to close all wallets?</source> + <source xml:space="preserve">Close all wallets</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> + <trans-unit id="_msg220"> + <source xml:space="preserve">Are you sure you wish to close all wallets?</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> + </trans-unit> </group> </body></file> <file original="../forms/createwalletdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CreateWalletDialog"> - <trans-unit id="_msg220"> + <trans-unit id="_msg221"> <source xml:space="preserve">Create Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg221"> + <trans-unit id="_msg222"> <source xml:space="preserve">Wallet Name</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">25</context></context-group> </trans-unit> - <trans-unit id="_msg222"> + <trans-unit id="_msg223"> <source xml:space="preserve">Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">38</context></context-group> </trans-unit> - <trans-unit id="_msg223"> + <trans-unit id="_msg224"> <source xml:space="preserve">Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg224"> + <trans-unit id="_msg225"> <source xml:space="preserve">Encrypt Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg225"> + <trans-unit id="_msg226"> <source xml:space="preserve">Advanced Options</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">76</context></context-group> </trans-unit> - <trans-unit id="_msg226"> + <trans-unit id="_msg227"> <source xml:space="preserve">Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">85</context></context-group> </trans-unit> - <trans-unit id="_msg227"> + <trans-unit id="_msg228"> <source xml:space="preserve">Disable Private Keys</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> - <trans-unit id="_msg228"> + <trans-unit id="_msg229"> <source xml:space="preserve">Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg229"> + <trans-unit id="_msg230"> <source xml:space="preserve">Make Blank Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg230"> + <trans-unit id="_msg231"> <source xml:space="preserve">Use descriptors for scriptPubKey management</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg231"> + <trans-unit id="_msg232"> <source xml:space="preserve">Descriptor Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> + <trans-unit id="_msg233"> + <source xml:space="preserve">Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">115</context></context-group> + </trans-unit> + <trans-unit id="_msg234"> + <source xml:space="preserve">External signer</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">118</context></context-group> + </trans-unit> </group> </body></file> <file original="../createwalletdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CreateWalletDialog"> - <trans-unit id="_msg232"> + <trans-unit id="_msg235"> <source xml:space="preserve">Create</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">21</context></context-group> + <context-group purpose="location"><context context-type="linenumber">22</context></context-group> </trans-unit> - <trans-unit id="_msg233"> + <trans-unit id="_msg236"> <source xml:space="preserve">Compiled without sqlite support (required for descriptor wallets)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">63</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> + </trans-unit> + <trans-unit id="_msg237"> + <source xml:space="preserve">Compiled without external signing support (required for external signing)</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">104</context></context-group> + <note annotates="source" from="developer">"External signing" means using devices such as hardware wallets.</note> </trans-unit> </group> </body></file> <file original="../forms/editaddressdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="EditAddressDialog"> - <trans-unit id="_msg234" approved="yes"> + <trans-unit id="_msg238" approved="yes"> <source xml:space="preserve">Edit Address</source> <target xml:space="preserve">Edit Address</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg235" approved="yes"> + <trans-unit id="_msg239" approved="yes"> <source xml:space="preserve">&Label</source> <target xml:space="preserve">&Label</target> <context-group purpose="location"><context context-type="linenumber">25</context></context-group> </trans-unit> - <trans-unit id="_msg236"> + <trans-unit id="_msg240"> <source xml:space="preserve">The label associated with this address list entry</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">35</context></context-group> </trans-unit> - <trans-unit id="_msg237"> + <trans-unit id="_msg241"> <source xml:space="preserve">The address associated with this address list entry. This can only be modified for sending addresses.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">52</context></context-group> </trans-unit> - <trans-unit id="_msg238" approved="yes"> + <trans-unit id="_msg242" approved="yes"> <source xml:space="preserve">&Address</source> <target xml:space="preserve">&Address</target> <context-group purpose="location"><context context-type="linenumber">42</context></context-group> @@ -1293,42 +1314,42 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../editaddressdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="EditAddressDialog"> - <trans-unit id="_msg239"> + <trans-unit id="_msg243"> <source xml:space="preserve">New sending address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">29</context></context-group> </trans-unit> - <trans-unit id="_msg240"> + <trans-unit id="_msg244"> <source xml:space="preserve">Edit receiving address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg241"> + <trans-unit id="_msg245"> <source xml:space="preserve">Edit sending address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> </trans-unit> - <trans-unit id="_msg242"> + <trans-unit id="_msg246"> <source xml:space="preserve">The entered address "%1" is not a valid Bitcoin address.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">113</context></context-group> </trans-unit> - <trans-unit id="_msg243"> + <trans-unit id="_msg247"> <source xml:space="preserve">Address "%1" already exists as a receiving address with label "%2" and so cannot be added as a sending address.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg244"> + <trans-unit id="_msg248"> <source xml:space="preserve">The entered address "%1" is already in the address book with label "%2".</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">151</context></context-group> </trans-unit> - <trans-unit id="_msg245"> + <trans-unit id="_msg249"> <source xml:space="preserve">Could not unlock wallet.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> </trans-unit> - <trans-unit id="_msg246"> + <trans-unit id="_msg250"> <source xml:space="preserve">New key generation failed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">128</context></context-group> @@ -1337,59 +1358,59 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../intro.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="FreespaceChecker"> - <trans-unit id="_msg247" approved="yes"> + <trans-unit id="_msg251" approved="yes"> <source xml:space="preserve">A new data directory will be created.</source> <target xml:space="preserve">A new data directory will be created.</target> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg248" approved="yes"> + <trans-unit id="_msg252" approved="yes"> <source xml:space="preserve">name</source> <target xml:space="preserve">name</target> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg249" approved="yes"> + <trans-unit id="_msg253" approved="yes"> <source xml:space="preserve">Directory already exists. Add %1 if you intend to create a new directory here.</source> <target xml:space="preserve">Directory already exists. Add %1 if you intend to create a new directory here.</target> <context-group purpose="location"><context context-type="linenumber">97</context></context-group> </trans-unit> - <trans-unit id="_msg250" approved="yes"> + <trans-unit id="_msg254" approved="yes"> <source xml:space="preserve">Path already exists, and is not a directory.</source> <target xml:space="preserve">Path already exists, and is not a directory.</target> <context-group purpose="location"><context context-type="linenumber">100</context></context-group> </trans-unit> - <trans-unit id="_msg251" approved="yes"> + <trans-unit id="_msg255" approved="yes"> <source xml:space="preserve">Cannot create data directory here.</source> <target xml:space="preserve">Cannot create data directory here.</target> <context-group purpose="location"><context context-type="linenumber">107</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="Intro"> - <trans-unit id="_msg252"> + <trans-unit id="_msg256"> <source xml:space="preserve">Bitcoin</source> <target xml:space="preserve" state="needs-review-translation">Bitcoin</target> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg253"> + <trans-unit id="_msg257"> <source xml:space="preserve">%1 GB of free space available</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">301</context></context-group> </trans-unit> - <trans-unit id="_msg254"> + <trans-unit id="_msg258"> <source xml:space="preserve">(of %1 GB needed)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> - <trans-unit id="_msg255"> + <trans-unit id="_msg259"> <source xml:space="preserve">(%1 GB needed for full chain)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> - <trans-unit id="_msg256"> + <trans-unit id="_msg260"> <source xml:space="preserve">At least %1 GB of data will be stored in this directory, and it will grow over time.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> - <trans-unit id="_msg257"> + <trans-unit id="_msg261"> <source xml:space="preserve">Approximately %1 GB of data will be stored in this directory.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">381</context></context-group> @@ -1397,31 +1418,31 @@ Signing is only possible with addresses of the type 'legacy'.</source> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">390</context></context-group> <note annotates="source" from="developer">Explanatory text on the capability of the current prune target.</note> - <trans-unit id="_msg258[0]"> + <trans-unit id="_msg262[0]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> <target xml:space="preserve"></target> </trans-unit> - <trans-unit id="_msg258[1]"> + <trans-unit id="_msg262[1]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> <target xml:space="preserve"></target> </trans-unit> </group> - <trans-unit id="_msg259"> + <trans-unit id="_msg263"> <source xml:space="preserve">%1 will download and store a copy of the Bitcoin block chain.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">392</context></context-group> </trans-unit> - <trans-unit id="_msg260"> + <trans-unit id="_msg264"> <source xml:space="preserve">The wallet will also be stored in this directory.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">394</context></context-group> </trans-unit> - <trans-unit id="_msg261"> + <trans-unit id="_msg265"> <source xml:space="preserve">Error: Specified data directory "%1" cannot be created.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> - <trans-unit id="_msg262" approved="yes"> + <trans-unit id="_msg266" approved="yes"> <source xml:space="preserve">Error</source> <target xml:space="preserve">Error</target> <context-group purpose="location"><context context-type="linenumber">280</context></context-group> @@ -1430,29 +1451,29 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../utilitydialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="HelpMessageDialog"> - <trans-unit id="_msg263"> + <trans-unit id="_msg267"> <source xml:space="preserve">version</source> <target xml:space="preserve" state="needs-review-translation">version</target> <context-group purpose="location"><context context-type="linenumber">37</context></context-group> </trans-unit> - <trans-unit id="_msg264"> + <trans-unit id="_msg268"> <source xml:space="preserve">About %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">41</context></context-group> </trans-unit> - <trans-unit id="_msg265"> + <trans-unit id="_msg269"> <source xml:space="preserve">Command-line options</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="ShutdownWindow"> - <trans-unit id="_msg266"> + <trans-unit id="_msg270"> <source xml:space="preserve">%1 is shutting down…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">145</context></context-group> </trans-unit> - <trans-unit id="_msg267"> + <trans-unit id="_msg271"> <source xml:space="preserve">Do not shut down the computer until this window disappears.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> @@ -1461,57 +1482,57 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/intro.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="Intro"> - <trans-unit id="_msg268" approved="yes"> + <trans-unit id="_msg272" approved="yes"> <source xml:space="preserve">Welcome</source> <target xml:space="preserve">Welcome</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg269"> + <trans-unit id="_msg273"> <source xml:space="preserve">Welcome to %1.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">23</context></context-group> </trans-unit> - <trans-unit id="_msg270"> + <trans-unit id="_msg274"> <source xml:space="preserve">As this is the first time the program is launched, you can choose where %1 will store its data.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg271"> + <trans-unit id="_msg275"> <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">206</context></context-group> </trans-unit> - <trans-unit id="_msg272"> + <trans-unit id="_msg276"> <source xml:space="preserve">Limit block chain storage to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> - <trans-unit id="_msg273"> + <trans-unit id="_msg277"> <source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg274"> + <trans-unit id="_msg278"> <source xml:space="preserve"> GB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> - <trans-unit id="_msg275"> + <trans-unit id="_msg279"> <source xml:space="preserve">This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg276"> + <trans-unit id="_msg280"> <source xml:space="preserve">If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg277" approved="yes"> + <trans-unit id="_msg281" approved="yes"> <source xml:space="preserve">Use the default data directory</source> <target xml:space="preserve">Use the default data directory</target> <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> - <trans-unit id="_msg278" approved="yes"> + <trans-unit id="_msg282" approved="yes"> <source xml:space="preserve">Use a custom data directory:</source> <target xml:space="preserve">Use a custom data directory:</target> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> @@ -1520,65 +1541,65 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/modaloverlay.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ModalOverlay"> - <trans-unit id="_msg279"> + <trans-unit id="_msg283"> <source xml:space="preserve">Form</source> <target xml:space="preserve" state="needs-review-translation">Form</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg280"> + <trans-unit id="_msg284"> <source xml:space="preserve">Recent transactions may not yet be visible, and therefore your wallet's balance might be incorrect. This information will be correct once your wallet has finished synchronizing with the bitcoin network, as detailed below.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">133</context></context-group> </trans-unit> - <trans-unit id="_msg281"> + <trans-unit id="_msg285"> <source xml:space="preserve">Attempting to spend bitcoins that are affected by not-yet-displayed transactions will not be accepted by the network.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg282"> + <trans-unit id="_msg286"> <source xml:space="preserve">Number of blocks left</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg283"> + <trans-unit id="_msg287"> <source xml:space="preserve">Unknown…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> <context-group purpose="location"><context context-type="sourcefile">../modaloverlay.cpp</context><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg284"> + <trans-unit id="_msg288"> <source xml:space="preserve">calculating…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">292</context></context-group> <context-group purpose="location"><context context-type="linenumber">312</context></context-group> </trans-unit> - <trans-unit id="_msg285"> + <trans-unit id="_msg289"> <source xml:space="preserve">Last block time</source> <target xml:space="preserve" state="needs-review-translation">Last block time</target> <context-group purpose="location"><context context-type="linenumber">235</context></context-group> </trans-unit> - <trans-unit id="_msg286"> + <trans-unit id="_msg290"> <source xml:space="preserve">Progress</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg287"> + <trans-unit id="_msg291"> <source xml:space="preserve">Progress increase per hour</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> - <trans-unit id="_msg288"> + <trans-unit id="_msg292"> <source xml:space="preserve">Estimated time left until synced</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg289"> + <trans-unit id="_msg293"> <source xml:space="preserve">Hide</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">342</context></context-group> </trans-unit> - <trans-unit id="_msg290"> + <trans-unit id="_msg294"> <source xml:space="preserve">Esc</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">345</context></context-group> @@ -1587,19 +1608,19 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../modaloverlay.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ModalOverlay"> - <trans-unit id="_msg291"> + <trans-unit id="_msg295"> <source xml:space="preserve">%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">34</context></context-group> </trans-unit> - <trans-unit id="_msg292"> + <trans-unit id="_msg296"> <source xml:space="preserve">Unknown. Syncing Headers (%1, %2%)…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg293"> + <trans-unit id="_msg297"> <source xml:space="preserve">unknown</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> @@ -1608,12 +1629,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/openuridialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OpenURIDialog"> - <trans-unit id="_msg294"> + <trans-unit id="_msg298"> <source xml:space="preserve">Open bitcoin URI</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg295"> + <trans-unit id="_msg299"> <source xml:space="preserve">URI:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">22</context></context-group> @@ -1622,474 +1643,495 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/optionsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OptionsDialog"> - <trans-unit id="_msg296" approved="yes"> + <trans-unit id="_msg300" approved="yes"> <source xml:space="preserve">Options</source> <target xml:space="preserve">Options</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg297" approved="yes"> + <trans-unit id="_msg301" approved="yes"> <source xml:space="preserve">&Main</source> <target xml:space="preserve">&Main</target> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg298"> + <trans-unit id="_msg302"> <source xml:space="preserve">Automatically start %1 after logging in to the system.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">33</context></context-group> </trans-unit> - <trans-unit id="_msg299"> + <trans-unit id="_msg303"> <source xml:space="preserve">&Start %1 on system login</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> </trans-unit> - <trans-unit id="_msg300"> + <trans-unit id="_msg304"> <source xml:space="preserve">Enabling pruning significantly reduces the disk space required to store transactions. All blocks are still fully validated. Reverting this setting requires re-downloading the entire blockchain.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg301"> + <trans-unit id="_msg305"> <source xml:space="preserve">Size of &database cache</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> - <trans-unit id="_msg302"> + <trans-unit id="_msg306"> <source xml:space="preserve">Number of script &verification threads</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">151</context></context-group> </trans-unit> - <trans-unit id="_msg303"> + <trans-unit id="_msg307"> <source xml:space="preserve">IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">322</context></context-group> - <context-group purpose="location"><context context-type="linenumber">509</context></context-group> + <context-group purpose="location"><context context-type="linenumber">352</context></context-group> + <context-group purpose="location"><context context-type="linenumber">539</context></context-group> </trans-unit> - <trans-unit id="_msg304"> + <trans-unit id="_msg308"> <source xml:space="preserve">Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">391</context></context-group> - <context-group purpose="location"><context context-type="linenumber">414</context></context-group> - <context-group purpose="location"><context context-type="linenumber">437</context></context-group> + <context-group purpose="location"><context context-type="linenumber">421</context></context-group> + <context-group purpose="location"><context context-type="linenumber">444</context></context-group> + <context-group purpose="location"><context context-type="linenumber">467</context></context-group> </trans-unit> - <trans-unit id="_msg305"> + <trans-unit id="_msg309"> <source xml:space="preserve">Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Exit in the menu.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">606</context></context-group> + <context-group purpose="location"><context context-type="linenumber">636</context></context-group> </trans-unit> - <trans-unit id="_msg306"> + <trans-unit id="_msg310"> <source xml:space="preserve">Third party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">686</context></context-group> - <context-group purpose="location"><context context-type="linenumber">699</context></context-group> + <context-group purpose="location"><context context-type="linenumber">716</context></context-group> + <context-group purpose="location"><context context-type="linenumber">729</context></context-group> </trans-unit> - <trans-unit id="_msg307"> + <trans-unit id="_msg311"> <source xml:space="preserve">Open the %1 configuration file from the working directory.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">878</context></context-group> + <context-group purpose="location"><context context-type="linenumber">908</context></context-group> </trans-unit> - <trans-unit id="_msg308"> + <trans-unit id="_msg312"> <source xml:space="preserve">Open Configuration File</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">881</context></context-group> + <context-group purpose="location"><context context-type="linenumber">911</context></context-group> </trans-unit> - <trans-unit id="_msg309" approved="yes"> + <trans-unit id="_msg313" approved="yes"> <source xml:space="preserve">Reset all client options to default.</source> <target xml:space="preserve">Reset all client options to default.</target> - <context-group purpose="location"><context context-type="linenumber">891</context></context-group> + <context-group purpose="location"><context context-type="linenumber">921</context></context-group> </trans-unit> - <trans-unit id="_msg310" approved="yes"> + <trans-unit id="_msg314" approved="yes"> <source xml:space="preserve">&Reset Options</source> <target xml:space="preserve">&Reset Options</target> - <context-group purpose="location"><context context-type="linenumber">894</context></context-group> + <context-group purpose="location"><context context-type="linenumber">924</context></context-group> </trans-unit> - <trans-unit id="_msg311" approved="yes"> + <trans-unit id="_msg315" approved="yes"> <source xml:space="preserve">&Network</source> <target xml:space="preserve">&Network</target> - <context-group purpose="location"><context context-type="linenumber">249</context></context-group> + <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg312"> + <trans-unit id="_msg316"> <source xml:space="preserve">Prune &block storage to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> - <trans-unit id="_msg313"> + <trans-unit id="_msg317"> <source xml:space="preserve">GB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">71</context></context-group> </trans-unit> - <trans-unit id="_msg314"> + <trans-unit id="_msg318"> <source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg315"> + <trans-unit id="_msg319"> <source xml:space="preserve">MiB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg316"> + <trans-unit id="_msg320"> <source xml:space="preserve">(0 = auto, <0 = leave that many cores free)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg317"> + <trans-unit id="_msg321"> <source xml:space="preserve">W&allet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">200</context></context-group> </trans-unit> - <trans-unit id="_msg318"> + <trans-unit id="_msg322"> <source xml:space="preserve">Expert</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">206</context></context-group> </trans-unit> - <trans-unit id="_msg319"> + <trans-unit id="_msg323"> <source xml:space="preserve">Enable coin &control features</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg320"> + <trans-unit id="_msg324"> <source xml:space="preserve">If you disable the spending of unconfirmed change, the change from a transaction cannot be used until that transaction has at least one confirmation. This also affects how your balance is computed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg321"> + <trans-unit id="_msg325"> <source xml:space="preserve">&Spend unconfirmed change</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> - <trans-unit id="_msg322" approved="yes"> + <trans-unit id="_msg326"> + <source xml:space="preserve">External Signer (e.g. hardware wallet)</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">235</context></context-group> + </trans-unit> + <trans-unit id="_msg327"> + <source xml:space="preserve">&External signer script path</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + </trans-unit> + <trans-unit id="_msg328"> + <source xml:space="preserve">Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + </trans-unit> + <trans-unit id="_msg329" approved="yes"> <source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source> <target xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</target> - <context-group purpose="location"><context context-type="linenumber">255</context></context-group> + <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> - <trans-unit id="_msg323" approved="yes"> + <trans-unit id="_msg330" approved="yes"> <source xml:space="preserve">Map port using &UPnP</source> <target xml:space="preserve">Map port using &UPnP</target> - <context-group purpose="location"><context context-type="linenumber">258</context></context-group> + <context-group purpose="location"><context context-type="linenumber">288</context></context-group> </trans-unit> - <trans-unit id="_msg324"> + <trans-unit id="_msg331"> <source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports NAT-PMP and it is enabled. The external port could be random.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">265</context></context-group> + <context-group purpose="location"><context context-type="linenumber">295</context></context-group> </trans-unit> - <trans-unit id="_msg325"> + <trans-unit id="_msg332"> <source xml:space="preserve">Map port using NA&T-PMP</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">268</context></context-group> + <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> - <trans-unit id="_msg326"> + <trans-unit id="_msg333"> <source xml:space="preserve">Accept connections from outside.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">275</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg327"> + <trans-unit id="_msg334"> <source xml:space="preserve">Allow incomin&g connections</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">278</context></context-group> + <context-group purpose="location"><context context-type="linenumber">308</context></context-group> </trans-unit> - <trans-unit id="_msg328"> + <trans-unit id="_msg335"> <source xml:space="preserve">Connect to the Bitcoin network through a SOCKS5 proxy.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">285</context></context-group> + <context-group purpose="location"><context context-type="linenumber">315</context></context-group> </trans-unit> - <trans-unit id="_msg329"> + <trans-unit id="_msg336"> <source xml:space="preserve">&Connect through SOCKS5 proxy (default proxy):</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">288</context></context-group> + <context-group purpose="location"><context context-type="linenumber">318</context></context-group> </trans-unit> - <trans-unit id="_msg330" approved="yes"> + <trans-unit id="_msg337" approved="yes"> <source xml:space="preserve">Proxy &IP:</source> <target xml:space="preserve">Proxy &IP:</target> - <context-group purpose="location"><context context-type="linenumber">297</context></context-group> - <context-group purpose="location"><context context-type="linenumber">484</context></context-group> + <context-group purpose="location"><context context-type="linenumber">327</context></context-group> + <context-group purpose="location"><context context-type="linenumber">514</context></context-group> </trans-unit> - <trans-unit id="_msg331" approved="yes"> + <trans-unit id="_msg338" approved="yes"> <source xml:space="preserve">&Port:</source> <target xml:space="preserve">&Port:</target> - <context-group purpose="location"><context context-type="linenumber">329</context></context-group> - <context-group purpose="location"><context context-type="linenumber">516</context></context-group> + <context-group purpose="location"><context context-type="linenumber">359</context></context-group> + <context-group purpose="location"><context context-type="linenumber">546</context></context-group> </trans-unit> - <trans-unit id="_msg332" approved="yes"> + <trans-unit id="_msg339" approved="yes"> <source xml:space="preserve">Port of the proxy (e.g. 9050)</source> <target xml:space="preserve">Port of the proxy (e.g. 9050)</target> - <context-group purpose="location"><context context-type="linenumber">354</context></context-group> - <context-group purpose="location"><context context-type="linenumber">541</context></context-group> + <context-group purpose="location"><context context-type="linenumber">384</context></context-group> + <context-group purpose="location"><context context-type="linenumber">571</context></context-group> </trans-unit> - <trans-unit id="_msg333"> + <trans-unit id="_msg340"> <source xml:space="preserve">Used for reaching peers via:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">378</context></context-group> + <context-group purpose="location"><context context-type="linenumber">408</context></context-group> </trans-unit> - <trans-unit id="_msg334"> + <trans-unit id="_msg341"> <source xml:space="preserve">IPv4</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">401</context></context-group> + <context-group purpose="location"><context context-type="linenumber">431</context></context-group> </trans-unit> - <trans-unit id="_msg335"> + <trans-unit id="_msg342"> <source xml:space="preserve">IPv6</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">424</context></context-group> + <context-group purpose="location"><context context-type="linenumber">454</context></context-group> </trans-unit> - <trans-unit id="_msg336"> + <trans-unit id="_msg343"> <source xml:space="preserve">Tor</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">447</context></context-group> + <context-group purpose="location"><context context-type="linenumber">477</context></context-group> </trans-unit> - <trans-unit id="_msg337" approved="yes"> + <trans-unit id="_msg344" approved="yes"> <source xml:space="preserve">&Window</source> <target xml:space="preserve">&Window</target> - <context-group purpose="location"><context context-type="linenumber">577</context></context-group> + <context-group purpose="location"><context context-type="linenumber">607</context></context-group> </trans-unit> - <trans-unit id="_msg338"> + <trans-unit id="_msg345"> <source xml:space="preserve">Show the icon in the system tray.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">583</context></context-group> + <context-group purpose="location"><context context-type="linenumber">613</context></context-group> </trans-unit> - <trans-unit id="_msg339"> + <trans-unit id="_msg346"> <source xml:space="preserve">&Show tray icon</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">586</context></context-group> + <context-group purpose="location"><context context-type="linenumber">616</context></context-group> </trans-unit> - <trans-unit id="_msg340" approved="yes"> + <trans-unit id="_msg347" approved="yes"> <source xml:space="preserve">Show only a tray icon after minimizing the window.</source> <target xml:space="preserve">Show only a tray icon after minimizing the window.</target> - <context-group purpose="location"><context context-type="linenumber">596</context></context-group> + <context-group purpose="location"><context context-type="linenumber">626</context></context-group> </trans-unit> - <trans-unit id="_msg341" approved="yes"> + <trans-unit id="_msg348" approved="yes"> <source xml:space="preserve">&Minimize to the tray instead of the taskbar</source> <target xml:space="preserve">&Minimize to the tray instead of the taskbar</target> - <context-group purpose="location"><context context-type="linenumber">599</context></context-group> + <context-group purpose="location"><context context-type="linenumber">629</context></context-group> </trans-unit> - <trans-unit id="_msg342" approved="yes"> + <trans-unit id="_msg349" approved="yes"> <source xml:space="preserve">M&inimize on close</source> <target xml:space="preserve">M&inimize on close</target> - <context-group purpose="location"><context context-type="linenumber">609</context></context-group> + <context-group purpose="location"><context context-type="linenumber">639</context></context-group> </trans-unit> - <trans-unit id="_msg343" approved="yes"> + <trans-unit id="_msg350" approved="yes"> <source xml:space="preserve">&Display</source> <target xml:space="preserve">&Display</target> - <context-group purpose="location"><context context-type="linenumber">630</context></context-group> + <context-group purpose="location"><context context-type="linenumber">660</context></context-group> </trans-unit> - <trans-unit id="_msg344" approved="yes"> + <trans-unit id="_msg351" approved="yes"> <source xml:space="preserve">User Interface &language:</source> <target xml:space="preserve">User Interface &language:</target> - <context-group purpose="location"><context context-type="linenumber">638</context></context-group> + <context-group purpose="location"><context context-type="linenumber">668</context></context-group> </trans-unit> - <trans-unit id="_msg345"> + <trans-unit id="_msg352"> <source xml:space="preserve">The user interface language can be set here. This setting will take effect after restarting %1.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">651</context></context-group> + <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg346" approved="yes"> + <trans-unit id="_msg353" approved="yes"> <source xml:space="preserve">&Unit to show amounts in:</source> <target xml:space="preserve">&Unit to show amounts in:</target> - <context-group purpose="location"><context context-type="linenumber">662</context></context-group> + <context-group purpose="location"><context context-type="linenumber">692</context></context-group> </trans-unit> - <trans-unit id="_msg347" approved="yes"> + <trans-unit id="_msg354" approved="yes"> <source xml:space="preserve">Choose the default subdivision unit to show in the interface and when sending coins.</source> <target xml:space="preserve">Choose the default subdivision unit to show in the interface and when sending coins.</target> - <context-group purpose="location"><context context-type="linenumber">675</context></context-group> + <context-group purpose="location"><context context-type="linenumber">705</context></context-group> </trans-unit> - <trans-unit id="_msg348"> + <trans-unit id="_msg355"> <source xml:space="preserve">Whether to show coin control features or not.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg349"> + <trans-unit id="_msg356"> <source xml:space="preserve">Connect to the Bitcoin network through a separate SOCKS5 proxy for Tor onion services.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">472</context></context-group> + <context-group purpose="location"><context context-type="linenumber">502</context></context-group> </trans-unit> - <trans-unit id="_msg350"> + <trans-unit id="_msg357"> <source xml:space="preserve">Use separate SOCKS&5 proxy to reach peers via Tor onion services:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">475</context></context-group> + <context-group purpose="location"><context context-type="linenumber">505</context></context-group> </trans-unit> - <trans-unit id="_msg351"> + <trans-unit id="_msg358"> <source xml:space="preserve">&Third party transaction URLs</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">689</context></context-group> + <context-group purpose="location"><context context-type="linenumber">719</context></context-group> </trans-unit> - <trans-unit id="_msg352"> + <trans-unit id="_msg359"> <source xml:space="preserve">Monospaced font in the Overview tab:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">711</context></context-group> + <context-group purpose="location"><context context-type="linenumber">741</context></context-group> </trans-unit> - <trans-unit id="_msg353"> + <trans-unit id="_msg360"> <source xml:space="preserve">embedded "%1"</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">719</context></context-group> + <context-group purpose="location"><context context-type="linenumber">749</context></context-group> </trans-unit> - <trans-unit id="_msg354"> + <trans-unit id="_msg361"> <source xml:space="preserve">closest matching "%1"</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">768</context></context-group> + <context-group purpose="location"><context context-type="linenumber">798</context></context-group> </trans-unit> - <trans-unit id="_msg355"> + <trans-unit id="_msg362"> <source xml:space="preserve">Options set in this dialog are overridden by the command line or in the configuration file:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">833</context></context-group> + <context-group purpose="location"><context context-type="linenumber">863</context></context-group> </trans-unit> - <trans-unit id="_msg356" approved="yes"> + <trans-unit id="_msg363" approved="yes"> <source xml:space="preserve">&OK</source> <target xml:space="preserve">&OK</target> - <context-group purpose="location"><context context-type="linenumber">974</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1004</context></context-group> </trans-unit> - <trans-unit id="_msg357" approved="yes"> + <trans-unit id="_msg364" approved="yes"> <source xml:space="preserve">&Cancel</source> <target xml:space="preserve">&Cancel</target> - <context-group purpose="location"><context context-type="linenumber">987</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1017</context></context-group> </trans-unit> </group> </body></file> <file original="../optionsdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OptionsDialog"> - <trans-unit id="_msg358" approved="yes"> + <trans-unit id="_msg365"> + <source xml:space="preserve">Compiled without external signing support (required for external signing)</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">97</context></context-group> + <note annotates="source" from="developer">"External signing" means using devices such as hardware wallets.</note> + </trans-unit> + <trans-unit id="_msg366" approved="yes"> <source xml:space="preserve">default</source> <target xml:space="preserve">default</target> - <context-group purpose="location"><context context-type="linenumber">104</context></context-group> + <context-group purpose="location"><context context-type="linenumber">109</context></context-group> </trans-unit> - <trans-unit id="_msg359"> + <trans-unit id="_msg367"> <source xml:space="preserve">none</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">185</context></context-group> + <context-group purpose="location"><context context-type="linenumber">190</context></context-group> </trans-unit> - <trans-unit id="_msg360" approved="yes"> + <trans-unit id="_msg368" approved="yes"> <source xml:space="preserve">Confirm options reset</source> <target xml:space="preserve">Confirm options reset</target> - <context-group purpose="location"><context context-type="linenumber">276</context></context-group> + <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg361"> + <trans-unit id="_msg369"> <source xml:space="preserve">Client restart required to activate changes.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">277</context></context-group> - <context-group purpose="location"><context context-type="linenumber">334</context></context-group> + <context-group purpose="location"><context context-type="linenumber">284</context></context-group> + <context-group purpose="location"><context context-type="linenumber">341</context></context-group> </trans-unit> - <trans-unit id="_msg362"> + <trans-unit id="_msg370"> <source xml:space="preserve">Client will be shut down. Do you want to proceed?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">277</context></context-group> + <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg363"> + <trans-unit id="_msg371"> <source xml:space="preserve">Configuration options</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">292</context></context-group> + <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> - <trans-unit id="_msg364"> + <trans-unit id="_msg372"> <source xml:space="preserve">The configuration file is used to specify advanced user options which override GUI settings. Additionally, any command-line options will override this configuration file.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">293</context></context-group> + <context-group purpose="location"><context context-type="linenumber">300</context></context-group> </trans-unit> - <trans-unit id="_msg365"> + <trans-unit id="_msg373"> <source xml:space="preserve">Error</source> <target xml:space="preserve" state="needs-review-translation">Error</target> - <context-group purpose="location"><context context-type="linenumber">298</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg366"> + <trans-unit id="_msg374"> <source xml:space="preserve">The configuration file could not be opened.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">298</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg367"> + <trans-unit id="_msg375"> <source xml:space="preserve">This change would require a client restart.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">338</context></context-group> + <context-group purpose="location"><context context-type="linenumber">345</context></context-group> </trans-unit> - <trans-unit id="_msg368" approved="yes"> + <trans-unit id="_msg376" approved="yes"> <source xml:space="preserve">The supplied proxy address is invalid.</source> <target xml:space="preserve">The supplied proxy address is invalid.</target> - <context-group purpose="location"><context context-type="linenumber">366</context></context-group> + <context-group purpose="location"><context context-type="linenumber">373</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/overviewpage.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OverviewPage"> - <trans-unit id="_msg369" approved="yes"> + <trans-unit id="_msg377" approved="yes"> <source xml:space="preserve">Form</source> <target xml:space="preserve">Form</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg370" approved="yes"> + <trans-unit id="_msg378" approved="yes"> <source xml:space="preserve">The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet.</source> <target xml:space="preserve">The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet.</target> <context-group purpose="location"><context context-type="linenumber">76</context></context-group> <context-group purpose="location"><context context-type="linenumber">411</context></context-group> </trans-unit> - <trans-unit id="_msg371"> + <trans-unit id="_msg379"> <source xml:space="preserve">Watch-only:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg372"> + <trans-unit id="_msg380"> <source xml:space="preserve">Available:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">294</context></context-group> </trans-unit> - <trans-unit id="_msg373" approved="yes"> + <trans-unit id="_msg381" approved="yes"> <source xml:space="preserve">Your current spendable balance</source> <target xml:space="preserve">Your current spendable balance</target> <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> - <trans-unit id="_msg374"> + <trans-unit id="_msg382"> <source xml:space="preserve">Pending:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> - <trans-unit id="_msg375" approved="yes"> + <trans-unit id="_msg383" approved="yes"> <source xml:space="preserve">Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance</source> <target xml:space="preserve">Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance</target> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg376" approved="yes"> + <trans-unit id="_msg384" approved="yes"> <source xml:space="preserve">Immature:</source> <target xml:space="preserve">Immature:</target> <context-group purpose="location"><context context-type="linenumber">239</context></context-group> </trans-unit> - <trans-unit id="_msg377" approved="yes"> + <trans-unit id="_msg385" approved="yes"> <source xml:space="preserve">Mined balance that has not yet matured</source> <target xml:space="preserve">Mined balance that has not yet matured</target> <context-group purpose="location"><context context-type="linenumber">210</context></context-group> </trans-unit> - <trans-unit id="_msg378"> + <trans-unit id="_msg386"> <source xml:space="preserve">Balances</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> - <trans-unit id="_msg379" approved="yes"> + <trans-unit id="_msg387" approved="yes"> <source xml:space="preserve">Total:</source> <target xml:space="preserve">Total:</target> <context-group purpose="location"><context context-type="linenumber">200</context></context-group> </trans-unit> - <trans-unit id="_msg380" approved="yes"> + <trans-unit id="_msg388" approved="yes"> <source xml:space="preserve">Your current total balance</source> <target xml:space="preserve">Your current total balance</target> <context-group purpose="location"><context context-type="linenumber">249</context></context-group> </trans-unit> - <trans-unit id="_msg381"> + <trans-unit id="_msg389"> <source xml:space="preserve">Your current balance in watch-only addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg382"> + <trans-unit id="_msg390"> <source xml:space="preserve">Spendable:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">346</context></context-group> </trans-unit> - <trans-unit id="_msg383"> + <trans-unit id="_msg391"> <source xml:space="preserve">Recent transactions</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">395</context></context-group> </trans-unit> - <trans-unit id="_msg384"> + <trans-unit id="_msg392"> <source xml:space="preserve">Unconfirmed transactions to watch-only addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg385"> + <trans-unit id="_msg393"> <source xml:space="preserve">Mined balance in watch-only addresses that has not yet matured</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> - <trans-unit id="_msg386"> + <trans-unit id="_msg394"> <source xml:space="preserve">Current total balance in watch-only addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">268</context></context-group> @@ -2098,41 +2140,41 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../overviewpage.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OverviewPage"> - <trans-unit id="_msg387"> + <trans-unit id="_msg395"> <source xml:space="preserve">Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">193</context></context-group> + <context-group purpose="location"><context context-type="linenumber">188</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/psbtoperationsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog"> - <trans-unit id="_msg388"> + <trans-unit id="_msg396"> <source xml:space="preserve">Dialog</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg389"> + <trans-unit id="_msg397"> <source xml:space="preserve">Sign Tx</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg390"> + <trans-unit id="_msg398"> <source xml:space="preserve">Broadcast Tx</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> - <trans-unit id="_msg391"> + <trans-unit id="_msg399"> <source xml:space="preserve">Copy to Clipboard</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg392"> + <trans-unit id="_msg400"> <source xml:space="preserve">Save…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">129</context></context-group> </trans-unit> - <trans-unit id="_msg393"> + <trans-unit id="_msg401"> <source xml:space="preserve">Close</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">136</context></context-group> @@ -2141,123 +2183,123 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../psbtoperationsdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog"> - <trans-unit id="_msg394"> + <trans-unit id="_msg402"> <source xml:space="preserve">Failed to load transaction: %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">55</context></context-group> </trans-unit> - <trans-unit id="_msg395"> + <trans-unit id="_msg403"> <source xml:space="preserve">Failed to sign transaction: %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg396"> + <trans-unit id="_msg404"> <source xml:space="preserve">Could not sign any more inputs.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">81</context></context-group> </trans-unit> - <trans-unit id="_msg397"> + <trans-unit id="_msg405"> <source xml:space="preserve">Signed %1 inputs, but more signatures are still required.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">83</context></context-group> </trans-unit> - <trans-unit id="_msg398"> + <trans-unit id="_msg406"> <source xml:space="preserve">Signed transaction successfully. Transaction is ready to broadcast.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg399"> + <trans-unit id="_msg407"> <source xml:space="preserve">Unknown error processing transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg400"> + <trans-unit id="_msg408"> <source xml:space="preserve">Transaction broadcast successfully! Transaction ID: %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> - <trans-unit id="_msg401"> + <trans-unit id="_msg409"> <source xml:space="preserve">Transaction broadcast failed: %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg402"> + <trans-unit id="_msg410"> <source xml:space="preserve">PSBT copied to clipboard.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg403"> + <trans-unit id="_msg411"> <source xml:space="preserve">Save Transaction Data</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg404"> + <trans-unit id="_msg412"> <source xml:space="preserve">Partially Signed Transaction (Binary)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">145</context></context-group> <note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note> </trans-unit> - <trans-unit id="_msg405"> + <trans-unit id="_msg413"> <source xml:space="preserve">PSBT saved to disk.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg406"> + <trans-unit id="_msg414"> <source xml:space="preserve"> * Sends %1 to %2</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">168</context></context-group> </trans-unit> - <trans-unit id="_msg407"> + <trans-unit id="_msg415"> <source xml:space="preserve">Unable to calculate transaction fee or total transaction amount.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">178</context></context-group> </trans-unit> - <trans-unit id="_msg408"> + <trans-unit id="_msg416"> <source xml:space="preserve">Pays transaction fee: </source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg409"> + <trans-unit id="_msg417"> <source xml:space="preserve">Total Amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">192</context></context-group> </trans-unit> - <trans-unit id="_msg410"> + <trans-unit id="_msg418"> <source xml:space="preserve">or</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">195</context></context-group> </trans-unit> - <trans-unit id="_msg411"> + <trans-unit id="_msg419"> <source xml:space="preserve">Transaction has %1 unsigned inputs.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">201</context></context-group> </trans-unit> - <trans-unit id="_msg412"> + <trans-unit id="_msg420"> <source xml:space="preserve">Transaction is missing some information about inputs.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">243</context></context-group> </trans-unit> - <trans-unit id="_msg413"> + <trans-unit id="_msg421"> <source xml:space="preserve">Transaction still needs signature(s).</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> - <trans-unit id="_msg414"> + <trans-unit id="_msg422"> <source xml:space="preserve">(But this wallet cannot sign transactions.)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> - <trans-unit id="_msg415"> + <trans-unit id="_msg423"> <source xml:space="preserve">(But this wallet does not have the right keys.)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">253</context></context-group> </trans-unit> - <trans-unit id="_msg416"> + <trans-unit id="_msg424"> <source xml:space="preserve">Transaction is fully signed and ready for broadcast.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg417"> + <trans-unit id="_msg425"> <source xml:space="preserve">Transaction status is unknown.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">265</context></context-group> @@ -2266,17 +2308,17 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../paymentserver.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PaymentServer"> - <trans-unit id="_msg418"> + <trans-unit id="_msg426"> <source xml:space="preserve">Payment request error</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg419"> + <trans-unit id="_msg427"> <source xml:space="preserve">Cannot start bitcoin: click-to-pay handler</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg420"> + <trans-unit id="_msg428"> <source xml:space="preserve">URI handling</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">224</context></context-group> @@ -2284,12 +2326,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> <context-group purpose="location"><context context-type="linenumber">253</context></context-group> </trans-unit> - <trans-unit id="_msg421"> + <trans-unit id="_msg429"> <source xml:space="preserve">'bitcoin://' is not a valid URI. Use 'bitcoin:' instead.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">224</context></context-group> </trans-unit> - <trans-unit id="_msg422"> + <trans-unit id="_msg430"> <source xml:space="preserve">Cannot process payment request because BIP70 is not supported. Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored. If you are receiving this error you should request the merchant provide a BIP21 compatible URI.</source> @@ -2297,12 +2339,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <context-group purpose="location"><context context-type="linenumber">241</context></context-group> <context-group purpose="location"><context context-type="linenumber">264</context></context-group> </trans-unit> - <trans-unit id="_msg423"> + <trans-unit id="_msg431"> <source xml:space="preserve">URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">254</context></context-group> </trans-unit> - <trans-unit id="_msg424"> + <trans-unit id="_msg432"> <source xml:space="preserve">Payment request file handling</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">263</context></context-group> @@ -2311,59 +2353,59 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../peertablemodel.h" datatype="c" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PeerTableModel"> - <trans-unit id="_msg425"> + <trans-unit id="_msg433"> <source xml:space="preserve">User Agent</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">101</context></context-group> + <context-group purpose="location"><context context-type="linenumber">107</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains the peer's User Agent string.</note> </trans-unit> - <trans-unit id="_msg426"> + <trans-unit id="_msg434"> <source xml:space="preserve">Ping</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">92</context></context-group> + <context-group purpose="location"><context context-type="linenumber">98</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the current latency of the connection with the peer.</note> </trans-unit> - <trans-unit id="_msg427"> + <trans-unit id="_msg435"> <source xml:space="preserve">Peer</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">80</context></context-group> + <context-group purpose="location"><context context-type="linenumber">86</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains a unique number used to identify a connection.</note> </trans-unit> - <trans-unit id="_msg428"> + <trans-unit id="_msg436"> <source xml:space="preserve">Sent</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">95</context></context-group> + <context-group purpose="location"><context context-type="linenumber">101</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have sent to the peer.</note> </trans-unit> - <trans-unit id="_msg429"> + <trans-unit id="_msg437"> <source xml:space="preserve">Received</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">98</context></context-group> + <context-group purpose="location"><context context-type="linenumber">104</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have received from the peer.</note> </trans-unit> - <trans-unit id="_msg430"> + <trans-unit id="_msg438"> <source xml:space="preserve">Address</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">83</context></context-group> + <context-group purpose="location"><context context-type="linenumber">89</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains the IP/Onion/I2P address of the connected peer.</note> </trans-unit> - <trans-unit id="_msg431"> + <trans-unit id="_msg439"> <source xml:space="preserve">Type</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">86</context></context-group> + <context-group purpose="location"><context context-type="linenumber">92</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which describes the type of peer connection. The "type" describes why the connection exists.</note> </trans-unit> - <trans-unit id="_msg432"> + <trans-unit id="_msg440"> <source xml:space="preserve">Network</source> <target xml:space="preserve" state="needs-review-translation">Network</target> - <context-group purpose="location"><context context-type="linenumber">89</context></context-group> + <context-group purpose="location"><context context-type="linenumber">95</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which states the network the peer connected through.</note> </trans-unit> </group> </body></file> <file original="../bitcoinunits.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg433"> + <trans-unit id="_msg441"> <source xml:space="preserve">Amount</source> <target xml:space="preserve" state="needs-review-translation">Amount</target> <context-group purpose="location"><context context-type="linenumber">213</context></context-group> @@ -2372,132 +2414,132 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../guiutil.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg434"> + <trans-unit id="_msg442"> <source xml:space="preserve">Enter a Bitcoin address (e.g. %1)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg435"> + <trans-unit id="_msg443"> <source xml:space="preserve">Unroutable</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">660</context></context-group> </trans-unit> - <trans-unit id="_msg436"> + <trans-unit id="_msg444"> <source xml:space="preserve">Internal</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">666</context></context-group> </trans-unit> - <trans-unit id="_msg437"> + <trans-unit id="_msg445"> <source xml:space="preserve">Inbound</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">676</context></context-group> </trans-unit> - <trans-unit id="_msg438"> + <trans-unit id="_msg446"> <source xml:space="preserve">Outbound</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">676</context></context-group> </trans-unit> - <trans-unit id="_msg439"> + <trans-unit id="_msg447"> <source xml:space="preserve">Full Relay</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">680</context></context-group> </trans-unit> - <trans-unit id="_msg440"> + <trans-unit id="_msg448"> <source xml:space="preserve">Block Relay</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg441"> + <trans-unit id="_msg449"> <source xml:space="preserve">Manual</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">682</context></context-group> </trans-unit> - <trans-unit id="_msg442"> + <trans-unit id="_msg450"> <source xml:space="preserve">Feeler</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">683</context></context-group> </trans-unit> - <trans-unit id="_msg443"> + <trans-unit id="_msg451"> <source xml:space="preserve">Address Fetch</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">684</context></context-group> </trans-unit> - <trans-unit id="_msg444"> + <trans-unit id="_msg452"> <source xml:space="preserve">%1 d</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">698</context></context-group> </trans-unit> - <trans-unit id="_msg445"> + <trans-unit id="_msg453"> <source xml:space="preserve">%1 h</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">700</context></context-group> </trans-unit> - <trans-unit id="_msg446"> + <trans-unit id="_msg454"> <source xml:space="preserve">%1 m</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">702</context></context-group> </trans-unit> - <trans-unit id="_msg447"> + <trans-unit id="_msg455"> <source xml:space="preserve">%1 s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">704</context></context-group> <context-group purpose="location"><context context-type="linenumber">732</context></context-group> </trans-unit> - <trans-unit id="_msg448"> + <trans-unit id="_msg456"> <source xml:space="preserve">None</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">720</context></context-group> </trans-unit> - <trans-unit id="_msg449"> + <trans-unit id="_msg457"> <source xml:space="preserve">N/A</source> <target xml:space="preserve" state="needs-review-translation">N/A</target> <context-group purpose="location"><context context-type="linenumber">726</context></context-group> </trans-unit> - <trans-unit id="_msg450"> + <trans-unit id="_msg458"> <source xml:space="preserve">%1 ms</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">727</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">745</context></context-group> - <trans-unit id="_msg451[0]" approved="yes"> + <trans-unit id="_msg459[0]" approved="yes"> <source xml:space="preserve">%n second(s)</source> <target xml:space="preserve">%n second</target> </trans-unit> - <trans-unit id="_msg451[1]" approved="yes"> + <trans-unit id="_msg459[1]" approved="yes"> <source xml:space="preserve">%n second(s)</source> <target xml:space="preserve">%n seconds</target> </trans-unit> </group> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">749</context></context-group> - <trans-unit id="_msg452[0]" approved="yes"> + <trans-unit id="_msg460[0]" approved="yes"> <source xml:space="preserve">%n minute(s)</source> <target xml:space="preserve">%n minute</target> </trans-unit> - <trans-unit id="_msg452[1]" approved="yes"> + <trans-unit id="_msg460[1]" approved="yes"> <source xml:space="preserve">%n minute(s)</source> <target xml:space="preserve">%n minutes</target> </trans-unit> </group> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">753</context></context-group> - <trans-unit id="_msg453[0]"> + <trans-unit id="_msg461[0]"> <source xml:space="preserve">%n hour(s)</source> <target xml:space="preserve" state="needs-review-translation">%n hour</target> </trans-unit> - <trans-unit id="_msg453[1]"> + <trans-unit id="_msg461[1]"> <source xml:space="preserve">%n hour(s)</source> <target xml:space="preserve" state="needs-review-translation">%n hours</target> </trans-unit> </group> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">757</context></context-group> - <trans-unit id="_msg454[0]"> + <trans-unit id="_msg462[0]"> <source xml:space="preserve">%n day(s)</source> <target xml:space="preserve" state="needs-review-translation">%n day</target> </trans-unit> - <trans-unit id="_msg454[1]"> + <trans-unit id="_msg462[1]"> <source xml:space="preserve">%n day(s)</source> <target xml:space="preserve" state="needs-review-translation">%n days</target> </trans-unit> @@ -2505,47 +2547,47 @@ If you are receiving this error you should request the merchant provide a BIP21 <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">761</context></context-group> <context-group purpose="location"><context context-type="linenumber">767</context></context-group> - <trans-unit id="_msg455[0]"> + <trans-unit id="_msg463[0]"> <source xml:space="preserve">%n week(s)</source> <target xml:space="preserve" state="needs-review-translation">%n week</target> </trans-unit> - <trans-unit id="_msg455[1]"> + <trans-unit id="_msg463[1]"> <source xml:space="preserve">%n week(s)</source> <target xml:space="preserve" state="needs-review-translation">%n weeks</target> </trans-unit> </group> - <trans-unit id="_msg456"> + <trans-unit id="_msg464"> <source xml:space="preserve">%1 and %2</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">767</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">767</context></context-group> - <trans-unit id="_msg457[0]"> + <trans-unit id="_msg465[0]"> <source xml:space="preserve">%n year(s)</source> <target xml:space="preserve" state="needs-review-translation">%n year</target> </trans-unit> - <trans-unit id="_msg457[1]"> + <trans-unit id="_msg465[1]"> <source xml:space="preserve">%n year(s)</source> <target xml:space="preserve" state="needs-review-translation">%n years</target> </trans-unit> </group> - <trans-unit id="_msg458"> + <trans-unit id="_msg466"> <source xml:space="preserve">%1 B</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">775</context></context-group> </trans-unit> - <trans-unit id="_msg459"> + <trans-unit id="_msg467"> <source xml:space="preserve">%1 kB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">777</context></context-group> </trans-unit> - <trans-unit id="_msg460"> + <trans-unit id="_msg468"> <source xml:space="preserve">%1 MB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">779</context></context-group> </trans-unit> - <trans-unit id="_msg461"> + <trans-unit id="_msg469"> <source xml:space="preserve">%1 GB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">781</context></context-group> @@ -2554,37 +2596,37 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../qrimagewidget.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QRImageWidget"> - <trans-unit id="_msg462"> - <source xml:space="preserve">Save Image…</source> + <trans-unit id="_msg470"> + <source xml:space="preserve">&Save Image…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">30</context></context-group> </trans-unit> - <trans-unit id="_msg463"> - <source xml:space="preserve">Copy Image</source> + <trans-unit id="_msg471"> + <source xml:space="preserve">&Copy Image</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">31</context></context-group> </trans-unit> - <trans-unit id="_msg464"> + <trans-unit id="_msg472"> <source xml:space="preserve">Resulting URI too long, try to reduce the text for label / message.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">42</context></context-group> </trans-unit> - <trans-unit id="_msg465"> + <trans-unit id="_msg473"> <source xml:space="preserve">Error encoding URI into QR Code.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg466"> + <trans-unit id="_msg474"> <source xml:space="preserve">QR code support not available.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg467"> + <trans-unit id="_msg475"> <source xml:space="preserve">Save QR Code</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg468"> + <trans-unit id="_msg476"> <source xml:space="preserve">PNG Image</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> @@ -2594,7 +2636,7 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../forms/debugwindow.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg469" approved="yes"> + <trans-unit id="_msg477" approved="yes"> <source xml:space="preserve">N/A</source> <target xml:space="preserve">N/A</target> <context-group purpose="location"><context context-type="linenumber">75</context></context-group> @@ -2608,480 +2650,480 @@ If you are receiving this error you should request the merchant provide a BIP21 <context-group purpose="location"><context context-type="linenumber">300</context></context-group> <context-group purpose="location"><context context-type="linenumber">336</context></context-group> <context-group purpose="location"><context context-type="linenumber">359</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1081</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1107</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1133</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1156</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1179</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1202</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1231</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1257</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1280</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1303</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1326</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1349</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1375</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1401</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1424</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1447</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1470</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1493</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1516</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1542</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1565</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1588</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1614</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">138</context></context-group> - </trans-unit> - <trans-unit id="_msg470" approved="yes"> + <context-group purpose="location"><context context-type="linenumber">1051</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1077</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1103</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1126</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1149</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1172</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1201</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1227</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1250</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1273</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1296</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1319</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1345</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1371</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1394</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1417</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1440</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1463</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1486</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1512</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1535</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1558</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1584</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">139</context></context-group> + </trans-unit> + <trans-unit id="_msg478" approved="yes"> <source xml:space="preserve">Client version</source> <target xml:space="preserve">Client version</target> <context-group purpose="location"><context context-type="linenumber">65</context></context-group> </trans-unit> - <trans-unit id="_msg471" approved="yes"> + <trans-unit id="_msg479" approved="yes"> <source xml:space="preserve">&Information</source> <target xml:space="preserve">&Information</target> <context-group purpose="location"><context context-type="linenumber">43</context></context-group> </trans-unit> - <trans-unit id="_msg472"> + <trans-unit id="_msg480"> <source xml:space="preserve">General</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg473"> + <trans-unit id="_msg481"> <source xml:space="preserve">Datadir</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">114</context></context-group> </trans-unit> - <trans-unit id="_msg474"> + <trans-unit id="_msg482"> <source xml:space="preserve">To specify a non-default location of the data directory use the '%1' option.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg475"> + <trans-unit id="_msg483"> <source xml:space="preserve">Blocksdir</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg476"> + <trans-unit id="_msg484"> <source xml:space="preserve">To specify a non-default location of the blocks directory use the '%1' option.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">153</context></context-group> </trans-unit> - <trans-unit id="_msg477" approved="yes"> + <trans-unit id="_msg485" approved="yes"> <source xml:space="preserve">Startup time</source> <target xml:space="preserve">Startup time</target> <context-group purpose="location"><context context-type="linenumber">172</context></context-group> </trans-unit> - <trans-unit id="_msg478" approved="yes"> + <trans-unit id="_msg486" approved="yes"> <source xml:space="preserve">Network</source> <target xml:space="preserve">Network</target> <context-group purpose="location"><context context-type="linenumber">201</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1123</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1093</context></context-group> </trans-unit> - <trans-unit id="_msg479"> + <trans-unit id="_msg487"> <source xml:space="preserve">Name</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">208</context></context-group> </trans-unit> - <trans-unit id="_msg480" approved="yes"> + <trans-unit id="_msg488" approved="yes"> <source xml:space="preserve">Number of connections</source> <target xml:space="preserve">Number of connections</target> <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> - <trans-unit id="_msg481" approved="yes"> + <trans-unit id="_msg489" approved="yes"> <source xml:space="preserve">Block chain</source> <target xml:space="preserve">Block chain</target> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg482"> + <trans-unit id="_msg490"> <source xml:space="preserve">Memory Pool</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">319</context></context-group> </trans-unit> - <trans-unit id="_msg483"> + <trans-unit id="_msg491"> <source xml:space="preserve">Current number of transactions</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg484"> + <trans-unit id="_msg492"> <source xml:space="preserve">Memory usage</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">349</context></context-group> </trans-unit> - <trans-unit id="_msg485"> + <trans-unit id="_msg493"> <source xml:space="preserve">Wallet: </source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">443</context></context-group> </trans-unit> - <trans-unit id="_msg486"> + <trans-unit id="_msg494"> <source xml:space="preserve">(none)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">454</context></context-group> </trans-unit> - <trans-unit id="_msg487"> + <trans-unit id="_msg495"> <source xml:space="preserve">&Reset</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">695</context></context-group> + <context-group purpose="location"><context context-type="linenumber">665</context></context-group> </trans-unit> - <trans-unit id="_msg488"> + <trans-unit id="_msg496"> <source xml:space="preserve">Received</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">775</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1483</context></context-group> + <context-group purpose="location"><context context-type="linenumber">745</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1453</context></context-group> </trans-unit> - <trans-unit id="_msg489"> + <trans-unit id="_msg497"> <source xml:space="preserve">Sent</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">855</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1460</context></context-group> + <context-group purpose="location"><context context-type="linenumber">825</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1430</context></context-group> </trans-unit> - <trans-unit id="_msg490"> + <trans-unit id="_msg498"> <source xml:space="preserve">&Peers</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">896</context></context-group> + <context-group purpose="location"><context context-type="linenumber">866</context></context-group> </trans-unit> - <trans-unit id="_msg491"> + <trans-unit id="_msg499"> <source xml:space="preserve">Banned peers</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">972</context></context-group> + <context-group purpose="location"><context context-type="linenumber">942</context></context-group> </trans-unit> - <trans-unit id="_msg492"> + <trans-unit id="_msg500"> <source xml:space="preserve">Select a peer to view detailed information.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1040</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1091</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1010</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1124</context></context-group> </trans-unit> - <trans-unit id="_msg493"> + <trans-unit id="_msg501"> <source xml:space="preserve">Version</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1146</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1116</context></context-group> </trans-unit> - <trans-unit id="_msg494"> + <trans-unit id="_msg502"> <source xml:space="preserve">Starting Block</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1270</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1240</context></context-group> </trans-unit> - <trans-unit id="_msg495"> + <trans-unit id="_msg503"> <source xml:space="preserve">Synced Headers</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1293</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1263</context></context-group> </trans-unit> - <trans-unit id="_msg496"> + <trans-unit id="_msg504"> <source xml:space="preserve">Synced Blocks</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1316</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1286</context></context-group> </trans-unit> - <trans-unit id="_msg497"> + <trans-unit id="_msg505"> <source xml:space="preserve">The mapped Autonomous System used for diversifying peer selection.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1601</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1571</context></context-group> </trans-unit> - <trans-unit id="_msg498"> + <trans-unit id="_msg506"> <source xml:space="preserve">Mapped AS</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1604</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1574</context></context-group> </trans-unit> - <trans-unit id="_msg499"> + <trans-unit id="_msg507"> <source xml:space="preserve">User Agent</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1169</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1139</context></context-group> </trans-unit> - <trans-unit id="_msg500"> + <trans-unit id="_msg508"> <source xml:space="preserve">Node window</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg501"> + <trans-unit id="_msg509"> <source xml:space="preserve">Current block height</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> - <trans-unit id="_msg502"> + <trans-unit id="_msg510"> <source xml:space="preserve">Open the %1 debug log file from the current data directory. This can take a few seconds for large log files.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">397</context></context-group> </trans-unit> - <trans-unit id="_msg503"> + <trans-unit id="_msg511"> <source xml:space="preserve">Decrease font size</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">481</context></context-group> + <context-group purpose="location"><context context-type="linenumber">475</context></context-group> </trans-unit> - <trans-unit id="_msg504"> + <trans-unit id="_msg512"> <source xml:space="preserve">Increase font size</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">513</context></context-group> + <context-group purpose="location"><context context-type="linenumber">495</context></context-group> </trans-unit> - <trans-unit id="_msg505"> + <trans-unit id="_msg513"> <source xml:space="preserve">Permissions</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1071</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1041</context></context-group> </trans-unit> - <trans-unit id="_msg506"> + <trans-unit id="_msg514"> <source xml:space="preserve">The direction and type of peer connection: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1094</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1064</context></context-group> </trans-unit> - <trans-unit id="_msg507"> + <trans-unit id="_msg515"> <source xml:space="preserve">Direction/Type</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1097</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1067</context></context-group> </trans-unit> - <trans-unit id="_msg508"> + <trans-unit id="_msg516"> <source xml:space="preserve">The network protocol this peer is connected through: IPv4, IPv6, Onion, I2P, or CJDNS.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1120</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1090</context></context-group> </trans-unit> - <trans-unit id="_msg509"> + <trans-unit id="_msg517"> <source xml:space="preserve">Services</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1192</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1162</context></context-group> </trans-unit> - <trans-unit id="_msg510"> + <trans-unit id="_msg518"> <source xml:space="preserve">Whether the peer requested us to relay transactions.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1218</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1188</context></context-group> </trans-unit> - <trans-unit id="_msg511"> + <trans-unit id="_msg519"> <source xml:space="preserve">Wants Tx Relay</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1221</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1191</context></context-group> </trans-unit> - <trans-unit id="_msg512"> + <trans-unit id="_msg520"> <source xml:space="preserve">High bandwidth BIP152 compact block relay: %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1244</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1214</context></context-group> </trans-unit> - <trans-unit id="_msg513"> + <trans-unit id="_msg521"> <source xml:space="preserve">High Bandwidth</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1247</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1217</context></context-group> </trans-unit> - <trans-unit id="_msg514"> + <trans-unit id="_msg522"> <source xml:space="preserve">Connection Time</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1339</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1309</context></context-group> </trans-unit> - <trans-unit id="_msg515"> + <trans-unit id="_msg523"> <source xml:space="preserve">Elapsed time since a novel block passing initial validity checks was received from this peer.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1362</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1332</context></context-group> </trans-unit> - <trans-unit id="_msg516"> + <trans-unit id="_msg524"> <source xml:space="preserve">Last Block</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1365</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1335</context></context-group> </trans-unit> - <trans-unit id="_msg517"> + <trans-unit id="_msg525"> <source xml:space="preserve">Elapsed time since a novel transaction accepted into our mempool was received from this peer.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1388</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1358</context></context-group> </trans-unit> - <trans-unit id="_msg518"> + <trans-unit id="_msg526"> <source xml:space="preserve">Last Tx</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1391</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1361</context></context-group> </trans-unit> - <trans-unit id="_msg519"> + <trans-unit id="_msg527"> <source xml:space="preserve">Last Send</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1414</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1384</context></context-group> </trans-unit> - <trans-unit id="_msg520"> + <trans-unit id="_msg528"> <source xml:space="preserve">Last Receive</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1437</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1407</context></context-group> </trans-unit> - <trans-unit id="_msg521"> + <trans-unit id="_msg529"> <source xml:space="preserve">Ping Time</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1506</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1476</context></context-group> </trans-unit> - <trans-unit id="_msg522"> + <trans-unit id="_msg530"> <source xml:space="preserve">The duration of a currently outstanding ping.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1529</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1499</context></context-group> </trans-unit> - <trans-unit id="_msg523"> + <trans-unit id="_msg531"> <source xml:space="preserve">Ping Wait</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1532</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1502</context></context-group> </trans-unit> - <trans-unit id="_msg524"> + <trans-unit id="_msg532"> <source xml:space="preserve">Min Ping</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1555</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1525</context></context-group> </trans-unit> - <trans-unit id="_msg525"> + <trans-unit id="_msg533"> <source xml:space="preserve">Time Offset</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1578</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1548</context></context-group> </trans-unit> - <trans-unit id="_msg526" approved="yes"> + <trans-unit id="_msg534" approved="yes"> <source xml:space="preserve">Last block time</source> <target xml:space="preserve">Last block time</target> <context-group purpose="location"><context context-type="linenumber">290</context></context-group> </trans-unit> - <trans-unit id="_msg527" approved="yes"> + <trans-unit id="_msg535" approved="yes"> <source xml:space="preserve">&Open</source> <target xml:space="preserve">&Open</target> <context-group purpose="location"><context context-type="linenumber">400</context></context-group> </trans-unit> - <trans-unit id="_msg528" approved="yes"> + <trans-unit id="_msg536" approved="yes"> <source xml:space="preserve">&Console</source> <target xml:space="preserve">&Console</target> <context-group purpose="location"><context context-type="linenumber">426</context></context-group> </trans-unit> - <trans-unit id="_msg529"> + <trans-unit id="_msg537"> <source xml:space="preserve">&Network Traffic</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">643</context></context-group> + <context-group purpose="location"><context context-type="linenumber">613</context></context-group> </trans-unit> - <trans-unit id="_msg530"> + <trans-unit id="_msg538"> <source xml:space="preserve">Totals</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">711</context></context-group> + <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg531" approved="yes"> + <trans-unit id="_msg539" approved="yes"> <source xml:space="preserve">Debug log file</source> <target xml:space="preserve">Debug log file</target> <context-group purpose="location"><context context-type="linenumber">390</context></context-group> </trans-unit> - <trans-unit id="_msg532" approved="yes"> + <trans-unit id="_msg540" approved="yes"> <source xml:space="preserve">Clear console</source> <target xml:space="preserve">Clear console</target> - <context-group purpose="location"><context context-type="linenumber">545</context></context-group> + <context-group purpose="location"><context context-type="linenumber">515</context></context-group> </trans-unit> </group> </body></file> <file original="../rpcconsole.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg533"> + <trans-unit id="_msg541"> <source xml:space="preserve">In:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">890</context></context-group> + <context-group purpose="location"><context context-type="linenumber">923</context></context-group> </trans-unit> - <trans-unit id="_msg534"> + <trans-unit id="_msg542"> <source xml:space="preserve">Out:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">891</context></context-group> + <context-group purpose="location"><context context-type="linenumber">924</context></context-group> </trans-unit> - <trans-unit id="_msg535"> + <trans-unit id="_msg543"> <source xml:space="preserve">Inbound: initiated by peer</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">474</context></context-group> + <context-group purpose="location"><context context-type="linenumber">495</context></context-group> </trans-unit> - <trans-unit id="_msg536"> + <trans-unit id="_msg544"> <source xml:space="preserve">Outbound Full Relay: default</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">475</context></context-group> + <context-group purpose="location"><context context-type="linenumber">496</context></context-group> </trans-unit> - <trans-unit id="_msg537"> + <trans-unit id="_msg545"> <source xml:space="preserve">Outbound Block Relay: does not relay transactions or addresses</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">476</context></context-group> + <context-group purpose="location"><context context-type="linenumber">497</context></context-group> </trans-unit> - <trans-unit id="_msg538"> + <trans-unit id="_msg546"> <source xml:space="preserve">Outbound Manual: added using RPC %1 or %2/%3 configuration options</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">477</context></context-group> + <context-group purpose="location"><context context-type="linenumber">498</context></context-group> </trans-unit> - <trans-unit id="_msg539"> + <trans-unit id="_msg547"> <source xml:space="preserve">Outbound Feeler: short-lived, for testing addresses</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">481</context></context-group> + <context-group purpose="location"><context context-type="linenumber">502</context></context-group> </trans-unit> - <trans-unit id="_msg540"> + <trans-unit id="_msg548"> <source xml:space="preserve">Outbound Address Fetch: short-lived, for soliciting addresses</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">482</context></context-group> + <context-group purpose="location"><context context-type="linenumber">503</context></context-group> </trans-unit> - <trans-unit id="_msg541"> + <trans-unit id="_msg549"> <source xml:space="preserve">we selected the peer for high bandwidth relay</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">486</context></context-group> + <context-group purpose="location"><context context-type="linenumber">507</context></context-group> </trans-unit> - <trans-unit id="_msg542"> + <trans-unit id="_msg550"> <source xml:space="preserve">the peer selected us for high bandwidth relay</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">487</context></context-group> + <context-group purpose="location"><context context-type="linenumber">508</context></context-group> </trans-unit> - <trans-unit id="_msg543"> + <trans-unit id="_msg551"> <source xml:space="preserve">no high bandwidth relay selected</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">488</context></context-group> + <context-group purpose="location"><context context-type="linenumber">509</context></context-group> </trans-unit> - <trans-unit id="_msg544"> + <trans-unit id="_msg552"> <source xml:space="preserve">Ctrl++</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">501</context></context-group> + <context-group purpose="location"><context context-type="linenumber">522</context></context-group> <note annotates="source" from="developer">Main shortcut to increase the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg545"> + <trans-unit id="_msg553"> <source xml:space="preserve">Ctrl+=</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">503</context></context-group> + <context-group purpose="location"><context context-type="linenumber">524</context></context-group> <note annotates="source" from="developer">Secondary shortcut to increase the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg546"> + <trans-unit id="_msg554"> <source xml:space="preserve">Ctrl+-</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">507</context></context-group> + <context-group purpose="location"><context context-type="linenumber">528</context></context-group> <note annotates="source" from="developer">Main shortcut to decrease the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg547"> + <trans-unit id="_msg555"> <source xml:space="preserve">Ctrl+_</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">509</context></context-group> + <context-group purpose="location"><context context-type="linenumber">530</context></context-group> <note annotates="source" from="developer">Secondary shortcut to decrease the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg548"> - <source xml:space="preserve">Network activity disabled</source> + <trans-unit id="_msg556"> + <source xml:space="preserve">&Disconnect</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">894</context></context-group> + <context-group purpose="location"><context context-type="linenumber">680</context></context-group> </trans-unit> - <trans-unit id="_msg549"> - <source xml:space="preserve">Executing command without any wallet</source> + <trans-unit id="_msg557"> + <source xml:space="preserve">1 &hour</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">971</context></context-group> + <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg550"> - <source xml:space="preserve">Executing command using "%1" wallet</source> + <trans-unit id="_msg558"> + <source xml:space="preserve">1 d&ay</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">969</context></context-group> + <context-group purpose="location"><context context-type="linenumber">682</context></context-group> </trans-unit> - <trans-unit id="_msg551"> - <source xml:space="preserve">Disconnect</source> + <trans-unit id="_msg559"> + <source xml:space="preserve">1 &week</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">650</context></context-group> + <context-group purpose="location"><context context-type="linenumber">683</context></context-group> </trans-unit> - <trans-unit id="_msg552"> - <source xml:space="preserve">1 hour</source> + <trans-unit id="_msg560"> + <source xml:space="preserve">1 &year</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">651</context></context-group> + <context-group purpose="location"><context context-type="linenumber">684</context></context-group> </trans-unit> - <trans-unit id="_msg553"> - <source xml:space="preserve">1 day</source> + <trans-unit id="_msg561"> + <source xml:space="preserve">&Unban</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">652</context></context-group> + <context-group purpose="location"><context context-type="linenumber">706</context></context-group> </trans-unit> - <trans-unit id="_msg554"> - <source xml:space="preserve">1 week</source> + <trans-unit id="_msg562"> + <source xml:space="preserve">Network activity disabled</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">653</context></context-group> + <context-group purpose="location"><context context-type="linenumber">927</context></context-group> </trans-unit> - <trans-unit id="_msg555"> - <source xml:space="preserve">1 year</source> + <trans-unit id="_msg563"> + <source xml:space="preserve">Executing command without any wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">654</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1004</context></context-group> </trans-unit> - <trans-unit id="_msg556"> - <source xml:space="preserve">Unban</source> + <trans-unit id="_msg564"> + <source xml:space="preserve">Executing command using "%1" wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">673</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1002</context></context-group> </trans-unit> - <trans-unit id="_msg557"> + <trans-unit id="_msg565"> <source xml:space="preserve">Welcome to the %1 RPC console. Use up and down arrows to navigate history, and %2 to clear screen. Use %3 and %4 to increase or decrease the font size. @@ -3090,160 +3132,160 @@ For more information on using this console, type %6. %7WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.%8</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">823</context></context-group> + <context-group purpose="location"><context context-type="linenumber">856</context></context-group> <note annotates="source" from="developer">RPC console welcome message. Placeholders %7 and %8 are style tags for the warning content, and they are not space separated from the rest of the text intentionally.</note> </trans-unit> - <trans-unit id="_msg558"> + <trans-unit id="_msg566"> <source xml:space="preserve">Executing…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">979</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1012</context></context-group> <note annotates="source" from="developer">A console message indicating an entered command is currently being executed.</note> </trans-unit> - <trans-unit id="_msg559"> + <trans-unit id="_msg567"> <source xml:space="preserve">(peer: %1)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1097</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1130</context></context-group> </trans-unit> - <trans-unit id="_msg560"> + <trans-unit id="_msg568"> <source xml:space="preserve">via %1</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">1099</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1132</context></context-group> </trans-unit> </group> </body></file> <file original="../rpcconsole.h" datatype="c" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg561"> + <trans-unit id="_msg569"> <source xml:space="preserve">Yes</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg562"> + <trans-unit id="_msg570"> <source xml:space="preserve">No</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg563"> + <trans-unit id="_msg571"> <source xml:space="preserve">To</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg564"> + <trans-unit id="_msg572"> <source xml:space="preserve">From</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg565"> + <trans-unit id="_msg573"> <source xml:space="preserve">Ban for</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg566"> + <trans-unit id="_msg574"> <source xml:space="preserve">Never</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">176</context></context-group> + <context-group purpose="location"><context context-type="linenumber">179</context></context-group> </trans-unit> - <trans-unit id="_msg567"> + <trans-unit id="_msg575"> <source xml:space="preserve">Unknown</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/receivecoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog"> - <trans-unit id="_msg568"> + <trans-unit id="_msg576"> <source xml:space="preserve">&Amount:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">37</context></context-group> </trans-unit> - <trans-unit id="_msg569"> + <trans-unit id="_msg577"> <source xml:space="preserve">&Label:</source> <target xml:space="preserve" state="needs-review-translation">&Label:</target> <context-group purpose="location"><context context-type="linenumber">83</context></context-group> </trans-unit> - <trans-unit id="_msg570"> + <trans-unit id="_msg578"> <source xml:space="preserve">&Message:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">53</context></context-group> </trans-unit> - <trans-unit id="_msg571"> + <trans-unit id="_msg579"> <source xml:space="preserve">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.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg572"> + <trans-unit id="_msg580"> <source xml:space="preserve">An optional label to associate with the new receiving address.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">80</context></context-group> </trans-unit> - <trans-unit id="_msg573"> + <trans-unit id="_msg581"> <source xml:space="preserve">Use this form to request payments. All fields are <b>optional</b>.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg574"> + <trans-unit id="_msg582"> <source xml:space="preserve">An optional amount to request. Leave this empty or zero to not request a specific amount.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">34</context></context-group> <context-group purpose="location"><context context-type="linenumber">193</context></context-group> </trans-unit> - <trans-unit id="_msg575"> + <trans-unit id="_msg583"> <source xml:space="preserve">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.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> - <trans-unit id="_msg576"> + <trans-unit id="_msg584"> <source xml:space="preserve">An optional message that is attached to the payment request and may be displayed to the sender.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg577"> + <trans-unit id="_msg585"> <source xml:space="preserve">&Create new receiving address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg578"> + <trans-unit id="_msg586"> <source xml:space="preserve">Clear all fields of the form.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg579"> + <trans-unit id="_msg587"> <source xml:space="preserve">Clear</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">137</context></context-group> </trans-unit> - <trans-unit id="_msg580"> + <trans-unit id="_msg588"> <source xml:space="preserve">Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg581"> + <trans-unit id="_msg589"> <source xml:space="preserve">Generate native segwit (Bech32) address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">218</context></context-group> </trans-unit> - <trans-unit id="_msg582"> + <trans-unit id="_msg590"> <source xml:space="preserve">Requested payments history</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg583"> + <trans-unit id="_msg591"> <source xml:space="preserve">Show the selected request (does the same as double clicking an entry)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> - <trans-unit id="_msg584"> + <trans-unit id="_msg592"> <source xml:space="preserve">Show</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">307</context></context-group> </trans-unit> - <trans-unit id="_msg585"> + <trans-unit id="_msg593"> <source xml:space="preserve">Remove the selected entries from the list</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> - <trans-unit id="_msg586"> + <trans-unit id="_msg594"> <source xml:space="preserve">Remove</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">327</context></context-group> @@ -3252,37 +3294,37 @@ For more information on using this console, type %6. </body></file> <file original="../receivecoinsdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog"> - <trans-unit id="_msg587"> - <source xml:space="preserve">Copy URI</source> + <trans-unit id="_msg595"> + <source xml:space="preserve">Copy &URI</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg588"> - <source xml:space="preserve">Copy address</source> + <trans-unit id="_msg596"> + <source xml:space="preserve">&Copy address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">48</context></context-group> </trans-unit> - <trans-unit id="_msg589"> - <source xml:space="preserve">Copy label</source> + <trans-unit id="_msg597"> + <source xml:space="preserve">Copy &label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg590"> - <source xml:space="preserve">Copy message</source> + <trans-unit id="_msg598"> + <source xml:space="preserve">Copy &message</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg591"> - <source xml:space="preserve">Copy amount</source> + <trans-unit id="_msg599"> + <source xml:space="preserve">Copy &amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg592"> + <trans-unit id="_msg600"> <source xml:space="preserve">Could not unlock wallet.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg593"> + <trans-unit id="_msg601"> <source xml:space="preserve">Could not generate new %1 address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> @@ -3291,52 +3333,62 @@ For more information on using this console, type %6. </body></file> <file original="../forms/receiverequestdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog"> - <trans-unit id="_msg594"> + <trans-unit id="_msg602"> <source xml:space="preserve">Request payment to …</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg595"> + <trans-unit id="_msg603"> <source xml:space="preserve">Address:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg596"> + <trans-unit id="_msg604"> <source xml:space="preserve">Amount:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">119</context></context-group> </trans-unit> - <trans-unit id="_msg597"> + <trans-unit id="_msg605"> <source xml:space="preserve">Label:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">148</context></context-group> </trans-unit> - <trans-unit id="_msg598"> + <trans-unit id="_msg606"> <source xml:space="preserve">Message:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg599"> + <trans-unit id="_msg607"> <source xml:space="preserve">Wallet:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg600"> + <trans-unit id="_msg608"> <source xml:space="preserve">Copy &URI</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg601"> + <trans-unit id="_msg609"> <source xml:space="preserve">Copy &Address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> - <trans-unit id="_msg602"> - <source xml:space="preserve">&Save Image…</source> + <trans-unit id="_msg610"> + <source xml:space="preserve">&Verify</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg603"> + <trans-unit id="_msg611"> + <source xml:space="preserve">Verify this address on e.g. a hardware wallet screen</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">263</context></context-group> + </trans-unit> + <trans-unit id="_msg612"> + <source xml:space="preserve">&Save Image…</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">273</context></context-group> + </trans-unit> + <trans-unit id="_msg613"> <source xml:space="preserve">Payment information</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">39</context></context-group> @@ -3345,7 +3397,7 @@ For more information on using this console, type %6. </body></file> <file original="../receiverequestdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog"> - <trans-unit id="_msg604"> + <trans-unit id="_msg614"> <source xml:space="preserve">Request payment to %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> @@ -3354,229 +3406,229 @@ For more information on using this console, type %6. </body></file> <file original="../recentrequeststablemodel.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RecentRequestsTableModel"> - <trans-unit id="_msg605"> + <trans-unit id="_msg615"> <source xml:space="preserve">Date</source> <target xml:space="preserve" state="needs-review-translation">Date</target> - <context-group purpose="location"><context context-type="linenumber">30</context></context-group> + <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg606"> + <trans-unit id="_msg616"> <source xml:space="preserve">Label</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">30</context></context-group> + <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg607"> + <trans-unit id="_msg617"> <source xml:space="preserve">Message</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">30</context></context-group> + <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg608"> + <trans-unit id="_msg618"> <source xml:space="preserve">(no label)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">71</context></context-group> + <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg609"> + <trans-unit id="_msg619"> <source xml:space="preserve">(no message)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">80</context></context-group> + <context-group purpose="location"><context context-type="linenumber">82</context></context-group> </trans-unit> - <trans-unit id="_msg610"> + <trans-unit id="_msg620"> <source xml:space="preserve">(no amount requested)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">88</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg611"> + <trans-unit id="_msg621"> <source xml:space="preserve">Requested</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">131</context></context-group> + <context-group purpose="location"><context context-type="linenumber">133</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/sendcoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsDialog"> - <trans-unit id="_msg612" approved="yes"> + <trans-unit id="_msg622" approved="yes"> <source xml:space="preserve">Send Coins</source> <target xml:space="preserve">Send Coins</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">674</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">738</context></context-group> </trans-unit> - <trans-unit id="_msg613"> + <trans-unit id="_msg623"> <source xml:space="preserve">Coin Control Features</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg614"> + <trans-unit id="_msg624"> <source xml:space="preserve">automatically selected</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg615"> + <trans-unit id="_msg625"> <source xml:space="preserve">Insufficient funds!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg616"> + <trans-unit id="_msg626"> <source xml:space="preserve">Quantity:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">228</context></context-group> </trans-unit> - <trans-unit id="_msg617"> + <trans-unit id="_msg627"> <source xml:space="preserve">Bytes:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> - <trans-unit id="_msg618"> + <trans-unit id="_msg628"> <source xml:space="preserve">Amount:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">311</context></context-group> </trans-unit> - <trans-unit id="_msg619"> + <trans-unit id="_msg629"> <source xml:space="preserve">Fee:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">391</context></context-group> </trans-unit> - <trans-unit id="_msg620"> + <trans-unit id="_msg630"> <source xml:space="preserve">After Fee:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">442</context></context-group> </trans-unit> - <trans-unit id="_msg621"> + <trans-unit id="_msg631"> <source xml:space="preserve">Change:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">474</context></context-group> </trans-unit> - <trans-unit id="_msg622"> + <trans-unit id="_msg632"> <source xml:space="preserve">If this is activated, but the change address is empty or invalid, change will be sent to a newly generated address.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">518</context></context-group> </trans-unit> - <trans-unit id="_msg623"> + <trans-unit id="_msg633"> <source xml:space="preserve">Custom change address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">521</context></context-group> </trans-unit> - <trans-unit id="_msg624"> + <trans-unit id="_msg634"> <source xml:space="preserve">Transaction Fee:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">727</context></context-group> </trans-unit> - <trans-unit id="_msg625"> + <trans-unit id="_msg635"> <source xml:space="preserve">Using the fallbackfee can result in sending a transaction that will take several hours or days (or never) to confirm. Consider choosing your fee manually or wait until you have validated the complete chain.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">765</context></context-group> </trans-unit> - <trans-unit id="_msg626"> + <trans-unit id="_msg636"> <source xml:space="preserve">Warning: Fee estimation is currently not possible.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">774</context></context-group> </trans-unit> - <trans-unit id="_msg627"> + <trans-unit id="_msg637"> <source xml:space="preserve">per kilobyte</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">856</context></context-group> </trans-unit> - <trans-unit id="_msg628"> + <trans-unit id="_msg638"> <source xml:space="preserve">Hide</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">803</context></context-group> </trans-unit> - <trans-unit id="_msg629"> + <trans-unit id="_msg639"> <source xml:space="preserve">Recommended:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">915</context></context-group> </trans-unit> - <trans-unit id="_msg630"> + <trans-unit id="_msg640"> <source xml:space="preserve">Custom:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">945</context></context-group> </trans-unit> - <trans-unit id="_msg631" approved="yes"> + <trans-unit id="_msg641" approved="yes"> <source xml:space="preserve">Send to multiple recipients at once</source> <target xml:space="preserve">Send to multiple recipients at once</target> <context-group purpose="location"><context context-type="linenumber">1160</context></context-group> </trans-unit> - <trans-unit id="_msg632" approved="yes"> + <trans-unit id="_msg642" approved="yes"> <source xml:space="preserve">Add &Recipient</source> <target xml:space="preserve">Add &Recipient</target> <context-group purpose="location"><context context-type="linenumber">1163</context></context-group> </trans-unit> - <trans-unit id="_msg633"> + <trans-unit id="_msg643"> <source xml:space="preserve">Clear all fields of the form.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">1143</context></context-group> </trans-unit> - <trans-unit id="_msg634"> + <trans-unit id="_msg644"> <source xml:space="preserve">Inputs…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> - <trans-unit id="_msg635"> + <trans-unit id="_msg645"> <source xml:space="preserve">Dust:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">343</context></context-group> </trans-unit> - <trans-unit id="_msg636"> + <trans-unit id="_msg646"> <source xml:space="preserve">Choose…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">741</context></context-group> </trans-unit> - <trans-unit id="_msg637"> + <trans-unit id="_msg647"> <source xml:space="preserve">Hide transaction fee settings</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">800</context></context-group> </trans-unit> - <trans-unit id="_msg638"> + <trans-unit id="_msg648"> <source xml:space="preserve">Specify a custom fee per kB (1,000 bytes) of the transaction's virtual size. Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 satoshis per kvB" for a transaction size of 500 virtual bytes (half of 1 kvB) would ultimately yield a fee of only 50 satoshis.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">851</context></context-group> </trans-unit> - <trans-unit id="_msg639"> + <trans-unit id="_msg649"> <source xml:space="preserve">When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">886</context></context-group> </trans-unit> - <trans-unit id="_msg640"> + <trans-unit id="_msg650"> <source xml:space="preserve">A too low fee might result in a never confirming transaction (read the tooltip)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">889</context></context-group> </trans-unit> - <trans-unit id="_msg641"> + <trans-unit id="_msg651"> <source xml:space="preserve">(Smart fee not initialized yet. This usually takes a few blocks…)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">994</context></context-group> </trans-unit> - <trans-unit id="_msg642"> + <trans-unit id="_msg652"> <source xml:space="preserve">Confirmation time target:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">1020</context></context-group> </trans-unit> - <trans-unit id="_msg643"> + <trans-unit id="_msg653"> <source xml:space="preserve">Enable Replace-By-Fee</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">1078</context></context-group> </trans-unit> - <trans-unit id="_msg644"> + <trans-unit id="_msg654"> <source xml:space="preserve">With Replace-By-Fee (BIP-125) you can increase a transaction's fee after it is sent. Without this, a higher fee may be recommended to compensate for increased transaction delay risk.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">1081</context></context-group> </trans-unit> - <trans-unit id="_msg645" approved="yes"> + <trans-unit id="_msg655" approved="yes"> <source xml:space="preserve">Clear &All</source> <target xml:space="preserve">Clear &All</target> <context-group purpose="location"><context context-type="linenumber">1146</context></context-group> </trans-unit> - <trans-unit id="_msg646" approved="yes"> + <trans-unit id="_msg656" approved="yes"> <source xml:space="preserve">Balance:</source> <target xml:space="preserve">Balance:</target> <context-group purpose="location"><context context-type="linenumber">1201</context></context-group> </trans-unit> - <trans-unit id="_msg647" approved="yes"> + <trans-unit id="_msg657" approved="yes"> <source xml:space="preserve">Confirm the send action</source> <target xml:space="preserve">Confirm the send action</target> <context-group purpose="location"><context context-type="linenumber">1117</context></context-group> </trans-unit> - <trans-unit id="_msg648" approved="yes"> + <trans-unit id="_msg658" approved="yes"> <source xml:space="preserve">S&end</source> <target xml:space="preserve">S&end</target> <context-group purpose="location"><context context-type="linenumber">1120</context></context-group> @@ -3585,344 +3637,383 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../sendcoinsdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsDialog"> - <trans-unit id="_msg649"> + <trans-unit id="_msg659"> <source xml:space="preserve">Copy quantity</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">92</context></context-group> </trans-unit> - <trans-unit id="_msg650"> + <trans-unit id="_msg660"> <source xml:space="preserve">Copy amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">93</context></context-group> </trans-unit> - <trans-unit id="_msg651"> + <trans-unit id="_msg661"> <source xml:space="preserve">Copy fee</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">94</context></context-group> </trans-unit> - <trans-unit id="_msg652"> + <trans-unit id="_msg662"> <source xml:space="preserve">Copy after fee</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg653"> + <trans-unit id="_msg663"> <source xml:space="preserve">Copy bytes</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg654"> + <trans-unit id="_msg664"> <source xml:space="preserve">Copy dust</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">97</context></context-group> </trans-unit> - <trans-unit id="_msg655"> + <trans-unit id="_msg665"> <source xml:space="preserve">Copy change</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg656"> + <trans-unit id="_msg666"> <source xml:space="preserve">%1 (%2 blocks)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg657"> + <trans-unit id="_msg667"> + <source xml:space="preserve">Sign on device</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">204</context></context-group> + <note annotates="source" from="developer">"device" usually means a hardware wallet</note> + </trans-unit> + <trans-unit id="_msg668"> + <source xml:space="preserve">Connect your hardware wallet first.</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">207</context></context-group> + </trans-unit> + <trans-unit id="_msg669"> + <source xml:space="preserve">Set external signer script path in Options -> Wallet</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">211</context></context-group> + <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> + </trans-unit> + <trans-unit id="_msg670"> <source xml:space="preserve">Cr&eate Unsigned</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">203</context></context-group> + <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg658"> + <trans-unit id="_msg671"> <source xml:space="preserve">Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">204</context></context-group> + <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg659"> + <trans-unit id="_msg672"> <source xml:space="preserve"> from wallet '%1'</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">294</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg660"> + <trans-unit id="_msg673"> <source xml:space="preserve">%1 to '%2'</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">305</context></context-group> + <context-group purpose="location"><context context-type="linenumber">316</context></context-group> </trans-unit> - <trans-unit id="_msg661"> + <trans-unit id="_msg674"> <source xml:space="preserve">%1 to %2</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">310</context></context-group> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg662"> + <trans-unit id="_msg675"> <source xml:space="preserve">Do you want to draft this transaction?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">317</context></context-group> + <context-group purpose="location"><context context-type="linenumber">328</context></context-group> </trans-unit> - <trans-unit id="_msg663"> + <trans-unit id="_msg676"> <source xml:space="preserve">Are you sure you want to send?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">319</context></context-group> + <context-group purpose="location"><context context-type="linenumber">330</context></context-group> </trans-unit> - <trans-unit id="_msg664"> + <trans-unit id="_msg677"> <source xml:space="preserve">To review recipient list click "Show Details…"</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">371</context></context-group> + <context-group purpose="location"><context context-type="linenumber">382</context></context-group> </trans-unit> - <trans-unit id="_msg665"> + <trans-unit id="_msg678"> <source xml:space="preserve">Create Unsigned</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">390</context></context-group> + <context-group purpose="location"><context context-type="linenumber">401</context></context-group> </trans-unit> - <trans-unit id="_msg666"> + <trans-unit id="_msg679"> + <source xml:space="preserve">Sign and send</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">401</context></context-group> + </trans-unit> + <trans-unit id="_msg680"> + <source xml:space="preserve">Sign failed</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">426</context></context-group> + </trans-unit> + <trans-unit id="_msg681"> + <source xml:space="preserve">External signer not found</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">432</context></context-group> + <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> + </trans-unit> + <trans-unit id="_msg682"> + <source xml:space="preserve">External signer failure</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">438</context></context-group> + <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> + </trans-unit> + <trans-unit id="_msg683"> <source xml:space="preserve">Save Transaction Data</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">434</context></context-group> + <context-group purpose="location"><context context-type="linenumber">496</context></context-group> </trans-unit> - <trans-unit id="_msg667"> + <trans-unit id="_msg684"> <source xml:space="preserve">Partially Signed Transaction (Binary)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">436</context></context-group> + <context-group purpose="location"><context context-type="linenumber">498</context></context-group> <note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note> </trans-unit> - <trans-unit id="_msg668"> + <trans-unit id="_msg685"> <source xml:space="preserve">PSBT saved</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">443</context></context-group> + <context-group purpose="location"><context context-type="linenumber">505</context></context-group> </trans-unit> - <trans-unit id="_msg669"> + <trans-unit id="_msg686"> + <source xml:space="preserve">External balance:</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">680</context></context-group> + </trans-unit> + <trans-unit id="_msg687"> <source xml:space="preserve">or</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">367</context></context-group> + <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> - <trans-unit id="_msg670"> + <trans-unit id="_msg688"> <source xml:space="preserve">You can increase the fee later (signals Replace-By-Fee, BIP-125).</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">348</context></context-group> + <context-group purpose="location"><context context-type="linenumber">359</context></context-group> </trans-unit> - <trans-unit id="_msg671"> + <trans-unit id="_msg689"> <source xml:space="preserve">Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg672"> + <trans-unit id="_msg690"> <source xml:space="preserve">Please, review your transaction.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">326</context></context-group> + <context-group purpose="location"><context context-type="linenumber">337</context></context-group> </trans-unit> - <trans-unit id="_msg673"> + <trans-unit id="_msg691"> <source xml:space="preserve">Transaction fee</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">334</context></context-group> + <context-group purpose="location"><context context-type="linenumber">345</context></context-group> </trans-unit> - <trans-unit id="_msg674"> + <trans-unit id="_msg692"> <source xml:space="preserve">Not signalling Replace-By-Fee, BIP-125.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">350</context></context-group> + <context-group purpose="location"><context context-type="linenumber">361</context></context-group> </trans-unit> - <trans-unit id="_msg675"> + <trans-unit id="_msg693"> <source xml:space="preserve">Total Amount</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">364</context></context-group> + <context-group purpose="location"><context context-type="linenumber">375</context></context-group> </trans-unit> - <trans-unit id="_msg676"> + <trans-unit id="_msg694"> <source xml:space="preserve">Confirm send coins</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">389</context></context-group> + <context-group purpose="location"><context context-type="linenumber">400</context></context-group> </trans-unit> - <trans-unit id="_msg677"> + <trans-unit id="_msg695"> <source xml:space="preserve">Confirm transaction proposal</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">389</context></context-group> - </trans-unit> - <trans-unit id="_msg678"> - <source xml:space="preserve">Send</source> - <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">390</context></context-group> + <context-group purpose="location"><context context-type="linenumber">400</context></context-group> </trans-unit> - <trans-unit id="_msg679"> + <trans-unit id="_msg696"> <source xml:space="preserve">Watch-only balance:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">619</context></context-group> + <context-group purpose="location"><context context-type="linenumber">683</context></context-group> </trans-unit> - <trans-unit id="_msg680"> + <trans-unit id="_msg697"> <source xml:space="preserve">The recipient address is not valid. Please recheck.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">643</context></context-group> + <context-group purpose="location"><context context-type="linenumber">707</context></context-group> </trans-unit> - <trans-unit id="_msg681"> + <trans-unit id="_msg698"> <source xml:space="preserve">The amount to pay must be larger than 0.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">646</context></context-group> + <context-group purpose="location"><context context-type="linenumber">710</context></context-group> </trans-unit> - <trans-unit id="_msg682"> + <trans-unit id="_msg699"> <source xml:space="preserve">The amount exceeds your balance.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">649</context></context-group> + <context-group purpose="location"><context context-type="linenumber">713</context></context-group> </trans-unit> - <trans-unit id="_msg683"> + <trans-unit id="_msg700"> <source xml:space="preserve">The total exceeds your balance when the %1 transaction fee is included.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">652</context></context-group> + <context-group purpose="location"><context context-type="linenumber">716</context></context-group> </trans-unit> - <trans-unit id="_msg684"> + <trans-unit id="_msg701"> <source xml:space="preserve">Duplicate address found: addresses should only be used once each.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">655</context></context-group> + <context-group purpose="location"><context context-type="linenumber">719</context></context-group> </trans-unit> - <trans-unit id="_msg685"> + <trans-unit id="_msg702"> <source xml:space="preserve">Transaction creation failed!</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">658</context></context-group> + <context-group purpose="location"><context context-type="linenumber">722</context></context-group> </trans-unit> - <trans-unit id="_msg686"> + <trans-unit id="_msg703"> <source xml:space="preserve">A fee higher than %1 is considered an absurdly high fee.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">662</context></context-group> + <context-group purpose="location"><context context-type="linenumber">726</context></context-group> </trans-unit> - <trans-unit id="_msg687"> + <trans-unit id="_msg704"> <source xml:space="preserve">Payment request expired.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">665</context></context-group> + <context-group purpose="location"><context context-type="linenumber">729</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">789</context></context-group> - <trans-unit id="_msg688[0]" approved="yes"> + <context-group purpose="location"><context context-type="linenumber">853</context></context-group> + <trans-unit id="_msg705[0]" approved="yes"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> <target xml:space="preserve">Estimated to begin confirmation within %n block.</target> </trans-unit> - <trans-unit id="_msg688[1]" approved="yes"> + <trans-unit id="_msg705[1]" approved="yes"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> <target xml:space="preserve">Estimated to begin confirmation within %n blocks.</target> </trans-unit> </group> - <trans-unit id="_msg689"> + <trans-unit id="_msg706"> <source xml:space="preserve">Warning: Invalid Bitcoin address</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">890</context></context-group> + <context-group purpose="location"><context context-type="linenumber">954</context></context-group> </trans-unit> - <trans-unit id="_msg690"> + <trans-unit id="_msg707"> <source xml:space="preserve">Warning: Unknown change address</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">895</context></context-group> + <context-group purpose="location"><context context-type="linenumber">959</context></context-group> </trans-unit> - <trans-unit id="_msg691"> + <trans-unit id="_msg708"> <source xml:space="preserve">Confirm custom change address</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">898</context></context-group> + <context-group purpose="location"><context context-type="linenumber">962</context></context-group> </trans-unit> - <trans-unit id="_msg692"> + <trans-unit id="_msg709"> <source xml:space="preserve">The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">898</context></context-group> + <context-group purpose="location"><context context-type="linenumber">962</context></context-group> </trans-unit> - <trans-unit id="_msg693"> + <trans-unit id="_msg710"> <source xml:space="preserve">(no label)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">919</context></context-group> + <context-group purpose="location"><context context-type="linenumber">983</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/sendcoinsentry.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsEntry"> - <trans-unit id="_msg694" approved="yes"> + <trans-unit id="_msg711" approved="yes"> <source xml:space="preserve">A&mount:</source> <target xml:space="preserve">A&mount:</target> <context-group purpose="location"><context context-type="linenumber">155</context></context-group> <context-group purpose="location"><context context-type="linenumber">705</context></context-group> <context-group purpose="location"><context context-type="linenumber">1238</context></context-group> </trans-unit> - <trans-unit id="_msg695" approved="yes"> + <trans-unit id="_msg712" approved="yes"> <source xml:space="preserve">Pay &To:</source> <target xml:space="preserve">Pay &To:</target> <context-group purpose="location"><context context-type="linenumber">39</context></context-group> </trans-unit> - <trans-unit id="_msg696" approved="yes"> + <trans-unit id="_msg713" approved="yes"> <source xml:space="preserve">&Label:</source> <target xml:space="preserve">&Label:</target> <context-group purpose="location"><context context-type="linenumber">132</context></context-group> </trans-unit> - <trans-unit id="_msg697"> + <trans-unit id="_msg714"> <source xml:space="preserve">Choose previously used address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">64</context></context-group> </trans-unit> - <trans-unit id="_msg698"> + <trans-unit id="_msg715"> <source xml:space="preserve">The Bitcoin address to send the payment to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">57</context></context-group> </trans-unit> - <trans-unit id="_msg699" approved="yes"> + <trans-unit id="_msg716" approved="yes"> <source xml:space="preserve">Alt+A</source> <target xml:space="preserve">Alt+A</target> <context-group purpose="location"><context context-type="linenumber">80</context></context-group> </trans-unit> - <trans-unit id="_msg700" approved="yes"> + <trans-unit id="_msg717" approved="yes"> <source xml:space="preserve">Paste address from clipboard</source> <target xml:space="preserve">Paste address from clipboard</target> <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> - <trans-unit id="_msg701" approved="yes"> + <trans-unit id="_msg718" approved="yes"> <source xml:space="preserve">Alt+P</source> <target xml:space="preserve">Alt+P</target> <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg702"> + <trans-unit id="_msg719"> <source xml:space="preserve">Remove this entry</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> <context-group purpose="location"><context context-type="linenumber">672</context></context-group> <context-group purpose="location"><context context-type="linenumber">1205</context></context-group> </trans-unit> - <trans-unit id="_msg703"> + <trans-unit id="_msg720"> <source xml:space="preserve">The amount to send in the selected unit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg704"> + <trans-unit id="_msg721"> <source xml:space="preserve">The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">177</context></context-group> </trans-unit> - <trans-unit id="_msg705"> + <trans-unit id="_msg722"> <source xml:space="preserve">S&ubtract fee from amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg706"> + <trans-unit id="_msg723"> <source xml:space="preserve">Use available balance</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">187</context></context-group> </trans-unit> - <trans-unit id="_msg707"> + <trans-unit id="_msg724"> <source xml:space="preserve">Message:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">196</context></context-group> </trans-unit> - <trans-unit id="_msg708"> + <trans-unit id="_msg725"> <source xml:space="preserve">This is an unauthenticated payment request.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">639</context></context-group> </trans-unit> - <trans-unit id="_msg709"> + <trans-unit id="_msg726"> <source xml:space="preserve">This is an authenticated payment request.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">1168</context></context-group> </trans-unit> - <trans-unit id="_msg710"> + <trans-unit id="_msg727"> <source xml:space="preserve">Enter a label for this address to add it to the list of used addresses</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">145</context></context-group> <context-group purpose="location"><context context-type="linenumber">148</context></context-group> </trans-unit> - <trans-unit id="_msg711"> + <trans-unit id="_msg728"> <source xml:space="preserve">A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">206</context></context-group> </trans-unit> - <trans-unit id="_msg712"> + <trans-unit id="_msg729"> <source xml:space="preserve">Pay To:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">654</context></context-group> <context-group purpose="location"><context context-type="linenumber">1183</context></context-group> </trans-unit> - <trans-unit id="_msg713"> + <trans-unit id="_msg730"> <source xml:space="preserve">Memo:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">688</context></context-group> @@ -3932,128 +4023,128 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../forms/signverifymessagedialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog"> - <trans-unit id="_msg714" approved="yes"> + <trans-unit id="_msg731" approved="yes"> <source xml:space="preserve">Signatures - Sign / Verify a Message</source> <target xml:space="preserve">Signatures - Sign / Verify a Message</target> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg715" approved="yes"> + <trans-unit id="_msg732" approved="yes"> <source xml:space="preserve">&Sign Message</source> <target xml:space="preserve">&Sign Message</target> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg716"> + <trans-unit id="_msg733"> <source xml:space="preserve">You can sign messages/agreements with your addresses to prove you can receive bitcoins sent to them. Be careful not to sign anything vague or random, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">33</context></context-group> </trans-unit> - <trans-unit id="_msg717"> + <trans-unit id="_msg734"> <source xml:space="preserve">The Bitcoin address to sign the message with</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg718"> + <trans-unit id="_msg735"> <source xml:space="preserve">Choose previously used address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg719" approved="yes"> + <trans-unit id="_msg736" approved="yes"> <source xml:space="preserve">Alt+A</source> <target xml:space="preserve">Alt+A</target> <context-group purpose="location"><context context-type="linenumber">68</context></context-group> <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg720" approved="yes"> + <trans-unit id="_msg737" approved="yes"> <source xml:space="preserve">Paste address from clipboard</source> <target xml:space="preserve">Paste address from clipboard</target> <context-group purpose="location"><context context-type="linenumber">78</context></context-group> </trans-unit> - <trans-unit id="_msg721" approved="yes"> + <trans-unit id="_msg738" approved="yes"> <source xml:space="preserve">Alt+P</source> <target xml:space="preserve">Alt+P</target> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> - <trans-unit id="_msg722" approved="yes"> + <trans-unit id="_msg739" approved="yes"> <source xml:space="preserve">Enter the message you want to sign here</source> <target xml:space="preserve">Enter the message you want to sign here</target> <context-group purpose="location"><context context-type="linenumber">100</context></context-group> <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg723" approved="yes"> + <trans-unit id="_msg740" approved="yes"> <source xml:space="preserve">Signature</source> <target xml:space="preserve">Signature</target> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> - <trans-unit id="_msg724" approved="yes"> + <trans-unit id="_msg741" approved="yes"> <source xml:space="preserve">Copy the current signature to the system clipboard</source> <target xml:space="preserve">Copy the current signature to the system clipboard</target> <context-group purpose="location"><context context-type="linenumber">140</context></context-group> </trans-unit> - <trans-unit id="_msg725" approved="yes"> + <trans-unit id="_msg742" approved="yes"> <source xml:space="preserve">Sign the message to prove you own this Bitcoin address</source> <target xml:space="preserve">Sign the message to prove you own this Bitcoin address</target> <context-group purpose="location"><context context-type="linenumber">161</context></context-group> </trans-unit> - <trans-unit id="_msg726" approved="yes"> + <trans-unit id="_msg743" approved="yes"> <source xml:space="preserve">Sign &Message</source> <target xml:space="preserve">Sign &Message</target> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg727" approved="yes"> + <trans-unit id="_msg744" approved="yes"> <source xml:space="preserve">Reset all sign message fields</source> <target xml:space="preserve">Reset all sign message fields</target> <context-group purpose="location"><context context-type="linenumber">178</context></context-group> </trans-unit> - <trans-unit id="_msg728" approved="yes"> + <trans-unit id="_msg745" approved="yes"> <source xml:space="preserve">Clear &All</source> <target xml:space="preserve">Clear &All</target> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> - <trans-unit id="_msg729" approved="yes"> + <trans-unit id="_msg746" approved="yes"> <source xml:space="preserve">&Verify Message</source> <target xml:space="preserve">&Verify Message</target> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg730"> + <trans-unit id="_msg747"> <source xml:space="preserve">Enter the receiver's address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. Note that this only proves the signing party receives with the address, it cannot prove sendership of any transaction!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg731"> + <trans-unit id="_msg748"> <source xml:space="preserve">The Bitcoin address the message was signed with</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> - <trans-unit id="_msg732"> + <trans-unit id="_msg749"> <source xml:space="preserve">The signed message to verify</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">296</context></context-group> <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> - <trans-unit id="_msg733"> + <trans-unit id="_msg750"> <source xml:space="preserve">The signature given when the message was signed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> - <trans-unit id="_msg734" approved="yes"> + <trans-unit id="_msg751" approved="yes"> <source xml:space="preserve">Verify the message to ensure it was signed with the specified Bitcoin address</source> <target xml:space="preserve">Verify the message to ensure it was signed with the specified Bitcoin address</target> <context-group purpose="location"><context context-type="linenumber">318</context></context-group> </trans-unit> - <trans-unit id="_msg735" approved="yes"> + <trans-unit id="_msg752" approved="yes"> <source xml:space="preserve">Verify &Message</source> <target xml:space="preserve">Verify &Message</target> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg736" approved="yes"> + <trans-unit id="_msg753" approved="yes"> <source xml:space="preserve">Reset all verify message fields</source> <target xml:space="preserve">Reset all verify message fields</target> <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg737"> + <trans-unit id="_msg754"> <source xml:space="preserve">Click "Sign Message" to generate signature</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">125</context></context-group> @@ -4062,13 +4153,13 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../signverifymessagedialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog"> - <trans-unit id="_msg738"> + <trans-unit id="_msg755"> <source xml:space="preserve">The entered address is invalid.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> - <trans-unit id="_msg739"> + <trans-unit id="_msg756"> <source xml:space="preserve">Please check the address and try again.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> @@ -4076,59 +4167,59 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context-group purpose="location"><context context-type="linenumber">220</context></context-group> <context-group purpose="location"><context context-type="linenumber">227</context></context-group> </trans-unit> - <trans-unit id="_msg740"> + <trans-unit id="_msg757"> <source xml:space="preserve">The entered address does not refer to a key.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">127</context></context-group> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg741"> + <trans-unit id="_msg758"> <source xml:space="preserve">Wallet unlock was cancelled.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg742"> + <trans-unit id="_msg759"> <source xml:space="preserve">No error</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg743"> + <trans-unit id="_msg760"> <source xml:space="preserve">Private key for the entered address is not available.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">149</context></context-group> </trans-unit> - <trans-unit id="_msg744"> + <trans-unit id="_msg761"> <source xml:space="preserve">Message signing failed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg745"> + <trans-unit id="_msg762"> <source xml:space="preserve">Message signed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg746"> + <trans-unit id="_msg763"> <source xml:space="preserve">The signature could not be decoded.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">233</context></context-group> </trans-unit> - <trans-unit id="_msg747"> + <trans-unit id="_msg764"> <source xml:space="preserve">Please check the signature and try again.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">234</context></context-group> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg748"> + <trans-unit id="_msg765"> <source xml:space="preserve">The signature did not match the message digest.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg749"> + <trans-unit id="_msg766"> <source xml:space="preserve">Message verification failed.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg750"> + <trans-unit id="_msg767"> <source xml:space="preserve">Message verified.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">214</context></context-group> @@ -4137,7 +4228,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../trafficgraphwidget.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TrafficGraphWidget"> - <trans-unit id="_msg751"> + <trans-unit id="_msg768"> <source xml:space="preserve">kB/s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">82</context></context-group> @@ -4148,111 +4239,111 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <group restype="x-trolltech-linguist-context" resname="TransactionDesc"> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> - <trans-unit id="_msg752[0]" approved="yes"> + <trans-unit id="_msg769[0]" approved="yes"> <source xml:space="preserve">Open for %n more block(s)</source> <target xml:space="preserve">Open for %n more block</target> </trans-unit> - <trans-unit id="_msg752[1]" approved="yes"> + <trans-unit id="_msg769[1]" approved="yes"> <source xml:space="preserve">Open for %n more block(s)</source> <target xml:space="preserve">Open for %n more blocks</target> </trans-unit> </group> - <trans-unit id="_msg753"> + <trans-unit id="_msg770"> <source xml:space="preserve">Open until %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">38</context></context-group> </trans-unit> - <trans-unit id="_msg754"> + <trans-unit id="_msg771"> <source xml:space="preserve">conflicted with a transaction with %1 confirmations</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">44</context></context-group> </trans-unit> - <trans-unit id="_msg755"> + <trans-unit id="_msg772"> <source xml:space="preserve">0/unconfirmed, %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg756"> + <trans-unit id="_msg773"> <source xml:space="preserve">in memory pool</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg757"> + <trans-unit id="_msg774"> <source xml:space="preserve">not in memory pool</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg758"> + <trans-unit id="_msg775"> <source xml:space="preserve">abandoned</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">46</context></context-group> </trans-unit> - <trans-unit id="_msg759"> + <trans-unit id="_msg776"> <source xml:space="preserve">%1/unconfirmed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg760"> + <trans-unit id="_msg777"> <source xml:space="preserve">%1 confirmations</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg761"> + <trans-unit id="_msg778"> <source xml:space="preserve">Status</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> - <trans-unit id="_msg762"> + <trans-unit id="_msg779"> <source xml:space="preserve">Date</source> <target xml:space="preserve" state="needs-review-translation">Date</target> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg763"> + <trans-unit id="_msg780"> <source xml:space="preserve">Source</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">112</context></context-group> </trans-unit> - <trans-unit id="_msg764"> + <trans-unit id="_msg781"> <source xml:space="preserve">Generated</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">112</context></context-group> </trans-unit> - <trans-unit id="_msg765"> + <trans-unit id="_msg782"> <source xml:space="preserve">From</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">117</context></context-group> <context-group purpose="location"><context context-type="linenumber">131</context></context-group> <context-group purpose="location"><context context-type="linenumber">203</context></context-group> </trans-unit> - <trans-unit id="_msg766"> + <trans-unit id="_msg783"> <source xml:space="preserve">unknown</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">131</context></context-group> </trans-unit> - <trans-unit id="_msg767"> + <trans-unit id="_msg784"> <source xml:space="preserve">To</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">132</context></context-group> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg768"> + <trans-unit id="_msg785"> <source xml:space="preserve">own address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg769"> + <trans-unit id="_msg786"> <source xml:space="preserve">watch-only</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">134</context></context-group> <context-group purpose="location"><context context-type="linenumber">203</context></context-group> </trans-unit> - <trans-unit id="_msg770"> + <trans-unit id="_msg787"> <source xml:space="preserve">label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">136</context></context-group> </trans-unit> - <trans-unit id="_msg771"> + <trans-unit id="_msg788"> <source xml:space="preserve">Credit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">172</context></context-group> @@ -4263,120 +4354,120 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </trans-unit> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> - <trans-unit id="_msg772[0]" approved="yes"> + <trans-unit id="_msg789[0]" approved="yes"> <source xml:space="preserve">matures in %n more block(s)</source> <target xml:space="preserve">matures in %n more block</target> </trans-unit> - <trans-unit id="_msg772[1]" approved="yes"> + <trans-unit id="_msg789[1]" approved="yes"> <source xml:space="preserve">matures in %n more block(s)</source> <target xml:space="preserve">matures in %n more blocks</target> </trans-unit> </group> - <trans-unit id="_msg773"> + <trans-unit id="_msg790"> <source xml:space="preserve">not accepted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg774"> + <trans-unit id="_msg791"> <source xml:space="preserve">Debit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">236</context></context-group> <context-group purpose="location"><context context-type="linenumber">262</context></context-group> <context-group purpose="location"><context context-type="linenumber">325</context></context-group> </trans-unit> - <trans-unit id="_msg775"> + <trans-unit id="_msg792"> <source xml:space="preserve">Total debit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg776"> + <trans-unit id="_msg793"> <source xml:space="preserve">Total credit</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> - <trans-unit id="_msg777"> + <trans-unit id="_msg794"> <source xml:space="preserve">Transaction fee</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">252</context></context-group> </trans-unit> - <trans-unit id="_msg778"> + <trans-unit id="_msg795"> <source xml:space="preserve">Net amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg779"> + <trans-unit id="_msg796"> <source xml:space="preserve">Message</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">280</context></context-group> <context-group purpose="location"><context context-type="linenumber">292</context></context-group> </trans-unit> - <trans-unit id="_msg780"> + <trans-unit id="_msg797"> <source xml:space="preserve">Comment</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">282</context></context-group> </trans-unit> - <trans-unit id="_msg781"> + <trans-unit id="_msg798"> <source xml:space="preserve">Transaction ID</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg782"> + <trans-unit id="_msg799"> <source xml:space="preserve">Transaction total size</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> - <trans-unit id="_msg783"> + <trans-unit id="_msg800"> <source xml:space="preserve">Transaction virtual size</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">286</context></context-group> </trans-unit> - <trans-unit id="_msg784"> + <trans-unit id="_msg801"> <source xml:space="preserve">Output index</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> - <trans-unit id="_msg785"> + <trans-unit id="_msg802"> <source xml:space="preserve"> (Certificate was not verified)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> - <trans-unit id="_msg786"> + <trans-unit id="_msg803"> <source xml:space="preserve">Merchant</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> - <trans-unit id="_msg787"> + <trans-unit id="_msg804"> <source xml:space="preserve">Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> - <trans-unit id="_msg788"> + <trans-unit id="_msg805"> <source xml:space="preserve">Debug information</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">322</context></context-group> </trans-unit> - <trans-unit id="_msg789"> + <trans-unit id="_msg806"> <source xml:space="preserve">Transaction</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">330</context></context-group> </trans-unit> - <trans-unit id="_msg790"> + <trans-unit id="_msg807"> <source xml:space="preserve">Inputs</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">333</context></context-group> </trans-unit> - <trans-unit id="_msg791"> + <trans-unit id="_msg808"> <source xml:space="preserve">Amount</source> <target xml:space="preserve" state="needs-review-translation">Amount</target> <context-group purpose="location"><context context-type="linenumber">354</context></context-group> </trans-unit> - <trans-unit id="_msg792"> + <trans-unit id="_msg809"> <source xml:space="preserve">true</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">355</context></context-group> <context-group purpose="location"><context context-type="linenumber">356</context></context-group> </trans-unit> - <trans-unit id="_msg793"> + <trans-unit id="_msg810"> <source xml:space="preserve">false</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">355</context></context-group> @@ -4386,7 +4477,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../forms/transactiondescdialog.ui" datatype="x-trolltech-designer-ui" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionDescDialog"> - <trans-unit id="_msg794" approved="yes"> + <trans-unit id="_msg811" approved="yes"> <source xml:space="preserve">This pane shows a detailed description of the transaction</source> <target xml:space="preserve">This pane shows a detailed description of the transaction</target> <context-group purpose="location"><context context-type="linenumber">20</context></context-group> @@ -4395,7 +4486,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactiondescdialog.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionDescDialog"> - <trans-unit id="_msg795"> + <trans-unit id="_msg812"> <source xml:space="preserve">Details for %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">18</context></context-group> @@ -4404,138 +4495,138 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactiontablemodel.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionTableModel"> - <trans-unit id="_msg796"> + <trans-unit id="_msg813"> <source xml:space="preserve">Date</source> <target xml:space="preserve" state="needs-review-translation">Date</target> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg797"> + <trans-unit id="_msg814"> <source xml:space="preserve">Type</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg798"> + <trans-unit id="_msg815"> <source xml:space="preserve">Label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">320</context></context-group> - <trans-unit id="_msg799[0]" approved="yes"> + <trans-unit id="_msg816[0]" approved="yes"> <source xml:space="preserve">Open for %n more block(s)</source> <target xml:space="preserve">Open for %n more block</target> </trans-unit> - <trans-unit id="_msg799[1]" approved="yes"> + <trans-unit id="_msg816[1]" approved="yes"> <source xml:space="preserve">Open for %n more block(s)</source> <target xml:space="preserve">Open for %n more blocks</target> </trans-unit> </group> - <trans-unit id="_msg800"> + <trans-unit id="_msg817"> <source xml:space="preserve">Open until %1</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg801"> + <trans-unit id="_msg818"> <source xml:space="preserve">Unconfirmed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg802"> + <trans-unit id="_msg819"> <source xml:space="preserve">Abandoned</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">329</context></context-group> </trans-unit> - <trans-unit id="_msg803"> + <trans-unit id="_msg820"> <source xml:space="preserve">Confirming (%1 of %2 recommended confirmations)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">332</context></context-group> </trans-unit> - <trans-unit id="_msg804"> + <trans-unit id="_msg821"> <source xml:space="preserve">Confirmed (%1 confirmations)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg805"> + <trans-unit id="_msg822"> <source xml:space="preserve">Conflicted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> - <trans-unit id="_msg806"> + <trans-unit id="_msg823"> <source xml:space="preserve">Immature (%1 confirmations, will be available after %2)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">341</context></context-group> </trans-unit> - <trans-unit id="_msg807"> + <trans-unit id="_msg824"> <source xml:space="preserve">Generated but not accepted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">344</context></context-group> </trans-unit> - <trans-unit id="_msg808"> + <trans-unit id="_msg825"> <source xml:space="preserve">Received with</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">383</context></context-group> </trans-unit> - <trans-unit id="_msg809"> + <trans-unit id="_msg826"> <source xml:space="preserve">Received from</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">385</context></context-group> </trans-unit> - <trans-unit id="_msg810"> + <trans-unit id="_msg827"> <source xml:space="preserve">Sent to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">388</context></context-group> </trans-unit> - <trans-unit id="_msg811"> + <trans-unit id="_msg828"> <source xml:space="preserve">Payment to yourself</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">390</context></context-group> </trans-unit> - <trans-unit id="_msg812"> + <trans-unit id="_msg829"> <source xml:space="preserve">Mined</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">392</context></context-group> </trans-unit> - <trans-unit id="_msg813"> + <trans-unit id="_msg830"> <source xml:space="preserve">watch-only</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">420</context></context-group> </trans-unit> - <trans-unit id="_msg814"> + <trans-unit id="_msg831"> <source xml:space="preserve">(n/a)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">436</context></context-group> </trans-unit> - <trans-unit id="_msg815"> + <trans-unit id="_msg832"> <source xml:space="preserve">(no label)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">646</context></context-group> </trans-unit> - <trans-unit id="_msg816"> + <trans-unit id="_msg833"> <source xml:space="preserve">Transaction status. Hover over this field to show number of confirmations.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">685</context></context-group> </trans-unit> - <trans-unit id="_msg817"> + <trans-unit id="_msg834"> <source xml:space="preserve">Date and time that the transaction was received.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">687</context></context-group> </trans-unit> - <trans-unit id="_msg818"> + <trans-unit id="_msg835"> <source xml:space="preserve">Type of transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">689</context></context-group> </trans-unit> - <trans-unit id="_msg819"> + <trans-unit id="_msg836"> <source xml:space="preserve">Whether or not a watch-only address is involved in this transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">691</context></context-group> </trans-unit> - <trans-unit id="_msg820"> + <trans-unit id="_msg837"> <source xml:space="preserve">User-defined intent/purpose of the transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">693</context></context-group> </trans-unit> - <trans-unit id="_msg821"> + <trans-unit id="_msg838"> <source xml:space="preserve">Amount removed from or added to balance.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">695</context></context-group> @@ -4544,199 +4635,199 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactionview.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionView"> - <trans-unit id="_msg822"> + <trans-unit id="_msg839"> <source xml:space="preserve">All</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">70</context></context-group> <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg823"> + <trans-unit id="_msg840"> <source xml:space="preserve">Today</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">71</context></context-group> </trans-unit> - <trans-unit id="_msg824"> + <trans-unit id="_msg841"> <source xml:space="preserve">This week</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">72</context></context-group> </trans-unit> - <trans-unit id="_msg825"> + <trans-unit id="_msg842"> <source xml:space="preserve">This month</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg826"> + <trans-unit id="_msg843"> <source xml:space="preserve">Last month</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">74</context></context-group> </trans-unit> - <trans-unit id="_msg827"> + <trans-unit id="_msg844"> <source xml:space="preserve">This year</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">75</context></context-group> </trans-unit> - <trans-unit id="_msg828"> + <trans-unit id="_msg845"> <source xml:space="preserve">Received with</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> - <trans-unit id="_msg829"> + <trans-unit id="_msg846"> <source xml:space="preserve">Sent to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">89</context></context-group> </trans-unit> - <trans-unit id="_msg830"> + <trans-unit id="_msg847"> <source xml:space="preserve">To yourself</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">91</context></context-group> </trans-unit> - <trans-unit id="_msg831"> + <trans-unit id="_msg848"> <source xml:space="preserve">Mined</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">92</context></context-group> </trans-unit> - <trans-unit id="_msg832"> + <trans-unit id="_msg849"> <source xml:space="preserve">Other</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">93</context></context-group> </trans-unit> - <trans-unit id="_msg833"> + <trans-unit id="_msg850"> <source xml:space="preserve">Enter address, transaction id, or label to search</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg834"> + <trans-unit id="_msg851"> <source xml:space="preserve">Min amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> - <trans-unit id="_msg835"> - <source xml:space="preserve">Abandon transaction</source> - <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">177</context></context-group> - </trans-unit> - <trans-unit id="_msg836"> - <source xml:space="preserve">Increase transaction fee</source> + <trans-unit id="_msg852"> + <source xml:space="preserve">Range…</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">174</context></context-group> + <context-group purpose="location"><context context-type="linenumber">76</context></context-group> </trans-unit> - <trans-unit id="_msg837"> - <source xml:space="preserve">Copy address</source> + <trans-unit id="_msg853"> + <source xml:space="preserve">&Copy address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">166</context></context-group> </trans-unit> - <trans-unit id="_msg838"> - <source xml:space="preserve">Copy label</source> + <trans-unit id="_msg854"> + <source xml:space="preserve">Copy &label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">167</context></context-group> </trans-unit> - <trans-unit id="_msg839"> - <source xml:space="preserve">Copy amount</source> + <trans-unit id="_msg855"> + <source xml:space="preserve">Copy &amount</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">168</context></context-group> </trans-unit> - <trans-unit id="_msg840"> - <source xml:space="preserve">Copy transaction ID</source> + <trans-unit id="_msg856"> + <source xml:space="preserve">Copy transaction &ID</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">169</context></context-group> </trans-unit> - <trans-unit id="_msg841"> - <source xml:space="preserve">Copy raw transaction</source> + <trans-unit id="_msg857"> + <source xml:space="preserve">Copy &raw transaction</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg842"> - <source xml:space="preserve">Copy full transaction details</source> + <trans-unit id="_msg858"> + <source xml:space="preserve">Copy full transaction &details</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">171</context></context-group> </trans-unit> - <trans-unit id="_msg843"> - <source xml:space="preserve">Edit address label</source> + <trans-unit id="_msg859"> + <source xml:space="preserve">&Show transaction details</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">178</context></context-group> + <context-group purpose="location"><context context-type="linenumber">172</context></context-group> </trans-unit> - <trans-unit id="_msg844"> - <source xml:space="preserve">Show transaction details</source> + <trans-unit id="_msg860"> + <source xml:space="preserve">Increase transaction &fee</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">172</context></context-group> + <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg845"> - <source xml:space="preserve">Range…</source> + <trans-unit id="_msg861"> + <source xml:space="preserve">A&bandon transaction</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">76</context></context-group> + <context-group purpose="location"><context context-type="linenumber">177</context></context-group> </trans-unit> - <trans-unit id="_msg846"> + <trans-unit id="_msg862"> + <source xml:space="preserve">&Edit address label</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">178</context></context-group> + </trans-unit> + <trans-unit id="_msg863"> <source xml:space="preserve">Export Transaction History</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">352</context></context-group> </trans-unit> - <trans-unit id="_msg847"> + <trans-unit id="_msg864"> <source xml:space="preserve">Comma separated file</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">355</context></context-group> <note annotates="source" from="developer">Expanded name of the CSV file format. See https://en.wikipedia.org/wiki/Comma-separated_values</note> </trans-unit> - <trans-unit id="_msg848"> + <trans-unit id="_msg865"> <source xml:space="preserve">Confirmed</source> <target xml:space="preserve" state="needs-review-translation">Confirmed</target> <context-group purpose="location"><context context-type="linenumber">364</context></context-group> </trans-unit> - <trans-unit id="_msg849"> + <trans-unit id="_msg866"> <source xml:space="preserve">Watch-only</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">366</context></context-group> </trans-unit> - <trans-unit id="_msg850"> + <trans-unit id="_msg867"> <source xml:space="preserve">Date</source> <target xml:space="preserve" state="needs-review-translation">Date</target> <context-group purpose="location"><context context-type="linenumber">367</context></context-group> </trans-unit> - <trans-unit id="_msg851"> + <trans-unit id="_msg868"> <source xml:space="preserve">Type</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">368</context></context-group> </trans-unit> - <trans-unit id="_msg852"> + <trans-unit id="_msg869"> <source xml:space="preserve">Label</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">369</context></context-group> </trans-unit> - <trans-unit id="_msg853"> + <trans-unit id="_msg870"> <source xml:space="preserve">Address</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">370</context></context-group> </trans-unit> - <trans-unit id="_msg854"> + <trans-unit id="_msg871"> <source xml:space="preserve">ID</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">372</context></context-group> </trans-unit> - <trans-unit id="_msg855"> + <trans-unit id="_msg872"> <source xml:space="preserve">Exporting Failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">375</context></context-group> </trans-unit> - <trans-unit id="_msg856"> + <trans-unit id="_msg873"> <source xml:space="preserve">There was an error trying to save the transaction history to %1.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">375</context></context-group> </trans-unit> - <trans-unit id="_msg857"> + <trans-unit id="_msg874"> <source xml:space="preserve">Exporting Successful</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">379</context></context-group> </trans-unit> - <trans-unit id="_msg858"> + <trans-unit id="_msg875"> <source xml:space="preserve">The transaction history was successfully saved to %1.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">379</context></context-group> </trans-unit> - <trans-unit id="_msg859"> + <trans-unit id="_msg876"> <source xml:space="preserve">Range:</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">551</context></context-group> </trans-unit> - <trans-unit id="_msg860"> + <trans-unit id="_msg877"> <source xml:space="preserve">to</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">559</context></context-group> @@ -4745,178 +4836,183 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../walletframe.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletFrame"> - <trans-unit id="_msg861"> + <trans-unit id="_msg878"> <source xml:space="preserve">No wallet has been loaded. Go to File > Open Wallet to load a wallet. - OR -</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">39</context></context-group> + <context-group purpose="location"><context context-type="linenumber">35</context></context-group> </trans-unit> - <trans-unit id="_msg862"> + <trans-unit id="_msg879"> <source xml:space="preserve">Create a new wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">44</context></context-group> + <context-group purpose="location"><context context-type="linenumber">40</context></context-group> </trans-unit> </group> </body></file> <file original="../walletmodel.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletModel"> - <trans-unit id="_msg863"> + <trans-unit id="_msg880"> <source xml:space="preserve">Send Coins</source> <target xml:space="preserve" state="needs-review-translation">Send Coins</target> <context-group purpose="location"><context context-type="linenumber">218</context></context-group> </trans-unit> - <trans-unit id="_msg864"> + <trans-unit id="_msg881"> <source xml:space="preserve">Fee bump error</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">497</context></context-group> - <context-group purpose="location"><context context-type="linenumber">549</context></context-group> - <context-group purpose="location"><context context-type="linenumber">562</context></context-group> - <context-group purpose="location"><context context-type="linenumber">567</context></context-group> + <context-group purpose="location"><context context-type="linenumber">478</context></context-group> + <context-group purpose="location"><context context-type="linenumber">530</context></context-group> + <context-group purpose="location"><context context-type="linenumber">543</context></context-group> + <context-group purpose="location"><context context-type="linenumber">548</context></context-group> </trans-unit> - <trans-unit id="_msg865"> + <trans-unit id="_msg882"> <source xml:space="preserve">Increasing transaction fee failed</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">497</context></context-group> + <context-group purpose="location"><context context-type="linenumber">478</context></context-group> </trans-unit> - <trans-unit id="_msg866"> + <trans-unit id="_msg883"> <source xml:space="preserve">Do you want to increase the fee?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">505</context></context-group> + <context-group purpose="location"><context context-type="linenumber">486</context></context-group> </trans-unit> - <trans-unit id="_msg867"> + <trans-unit id="_msg884"> <source xml:space="preserve">Do you want to draft a transaction with fee increase?</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">505</context></context-group> + <context-group purpose="location"><context context-type="linenumber">486</context></context-group> </trans-unit> - <trans-unit id="_msg868"> + <trans-unit id="_msg885"> <source xml:space="preserve">Current fee:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">509</context></context-group> + <context-group purpose="location"><context context-type="linenumber">490</context></context-group> </trans-unit> - <trans-unit id="_msg869"> + <trans-unit id="_msg886"> <source xml:space="preserve">Increase:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">513</context></context-group> + <context-group purpose="location"><context context-type="linenumber">494</context></context-group> </trans-unit> - <trans-unit id="_msg870"> + <trans-unit id="_msg887"> <source xml:space="preserve">New fee:</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">517</context></context-group> + <context-group purpose="location"><context context-type="linenumber">498</context></context-group> </trans-unit> - <trans-unit id="_msg871"> + <trans-unit id="_msg888"> <source xml:space="preserve">Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">525</context></context-group> + <context-group purpose="location"><context context-type="linenumber">506</context></context-group> </trans-unit> - <trans-unit id="_msg872"> + <trans-unit id="_msg889"> <source xml:space="preserve">Confirm fee bump</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">528</context></context-group> + <context-group purpose="location"><context context-type="linenumber">509</context></context-group> </trans-unit> - <trans-unit id="_msg873"> + <trans-unit id="_msg890"> <source xml:space="preserve">Can't draft transaction.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">549</context></context-group> + <context-group purpose="location"><context context-type="linenumber">530</context></context-group> </trans-unit> - <trans-unit id="_msg874"> + <trans-unit id="_msg891"> <source xml:space="preserve">PSBT copied</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">556</context></context-group> + <context-group purpose="location"><context context-type="linenumber">537</context></context-group> </trans-unit> - <trans-unit id="_msg875"> + <trans-unit id="_msg892"> <source xml:space="preserve">Can't sign transaction.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">562</context></context-group> + <context-group purpose="location"><context context-type="linenumber">543</context></context-group> </trans-unit> - <trans-unit id="_msg876"> + <trans-unit id="_msg893"> <source xml:space="preserve">Could not commit transaction</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">567</context></context-group> + <context-group purpose="location"><context context-type="linenumber">548</context></context-group> </trans-unit> - <trans-unit id="_msg877"> + <trans-unit id="_msg894"> + <source xml:space="preserve">Can't display address</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">562</context></context-group> + </trans-unit> + <trans-unit id="_msg895"> <source xml:space="preserve">default wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">587</context></context-group> + <context-group purpose="location"><context context-type="linenumber">580</context></context-group> </trans-unit> </group> </body></file> <file original="../walletview.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletView"> - <trans-unit id="_msg878"> + <trans-unit id="_msg896"> <source xml:space="preserve">&Export</source> <target xml:space="preserve" state="needs-review-translation">&Export</target> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg879"> + <trans-unit id="_msg897"> <source xml:space="preserve">Export the data in the current tab to a file</source> <target xml:space="preserve" state="needs-review-translation">Export the data in the current tab to a file</target> <context-group purpose="location"><context context-type="linenumber">52</context></context-group> </trans-unit> - <trans-unit id="_msg880"> + <trans-unit id="_msg898"> <source xml:space="preserve">Error</source> <target xml:space="preserve" state="needs-review-translation">Error</target> <context-group purpose="location"><context context-type="linenumber">217</context></context-group> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> <context-group purpose="location"><context context-type="linenumber">236</context></context-group> </trans-unit> - <trans-unit id="_msg881"> + <trans-unit id="_msg899"> <source xml:space="preserve">Unable to decode PSBT from clipboard (invalid base64)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">217</context></context-group> </trans-unit> - <trans-unit id="_msg882"> + <trans-unit id="_msg900"> <source xml:space="preserve">Load Transaction Data</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg883"> + <trans-unit id="_msg901"> <source xml:space="preserve">Partially Signed Transaction (*.psbt)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">223</context></context-group> </trans-unit> - <trans-unit id="_msg884"> + <trans-unit id="_msg902"> <source xml:space="preserve">PSBT file must be smaller than 100 MiB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg885"> + <trans-unit id="_msg903"> <source xml:space="preserve">Unable to decode PSBT</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">236</context></context-group> </trans-unit> - <trans-unit id="_msg886"> + <trans-unit id="_msg904"> <source xml:space="preserve">Backup Wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> - <trans-unit id="_msg887"> + <trans-unit id="_msg905"> <source xml:space="preserve">Wallet Data</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">277</context></context-group> <note annotates="source" from="developer">Name of the wallet data file format.</note> </trans-unit> - <trans-unit id="_msg888"> + <trans-unit id="_msg906"> <source xml:space="preserve">Backup Failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg889"> + <trans-unit id="_msg907"> <source xml:space="preserve">There was an error trying to save the wallet data to %1.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg890"> + <trans-unit id="_msg908"> <source xml:space="preserve">Backup Successful</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> - <trans-unit id="_msg891"> + <trans-unit id="_msg909"> <source xml:space="preserve">The wallet data was successfully saved to %1.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> - <trans-unit id="_msg892"> + <trans-unit id="_msg910"> <source xml:space="preserve">Cancel</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">331</context></context-group> @@ -4925,766 +5021,776 @@ Go to File > Open Wallet to load a wallet. </body></file> <file original="../bitcoinstrings.cpp" datatype="cpp" source-language="en" target-language="en"><body> <group restype="x-trolltech-linguist-context" resname="bitcoin-core"> - <trans-unit id="_msg893"> + <trans-unit id="_msg911"> <source xml:space="preserve">The %s developers</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">12</context></context-group> </trans-unit> - <trans-unit id="_msg894"> + <trans-unit id="_msg912"> <source xml:space="preserve">%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">13</context></context-group> </trans-unit> - <trans-unit id="_msg895"> + <trans-unit id="_msg913"> <source xml:space="preserve">-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">16</context></context-group> </trans-unit> - <trans-unit id="_msg896"> + <trans-unit id="_msg914"> <source xml:space="preserve">Cannot downgrade wallet from version %i to version %i. Wallet version unchanged.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">19</context></context-group> </trans-unit> - <trans-unit id="_msg897"> + <trans-unit id="_msg915"> <source xml:space="preserve">Cannot obtain a lock on data directory %s. %s is probably already running.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">22</context></context-group> </trans-unit> - <trans-unit id="_msg898"> + <trans-unit id="_msg916"> <source xml:space="preserve">Cannot provide specific connections and have addrman find outgoing connections at the same.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">24</context></context-group> </trans-unit> - <trans-unit id="_msg899"> + <trans-unit id="_msg917"> <source xml:space="preserve">Cannot upgrade a non HD split wallet from version %i to version %i without upgrading to support pre-split keypool. Please use version %i or no version specified.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg900"> + <trans-unit id="_msg918"> <source xml:space="preserve">Distributed under the MIT software license, see the accompanying file %s or %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">31</context></context-group> </trans-unit> - <trans-unit id="_msg901"> + <trans-unit id="_msg919"> <source xml:space="preserve">Error reading %s! All keys read correctly, but transaction data or address book entries might be missing or incorrect.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">34</context></context-group> </trans-unit> - <trans-unit id="_msg902"> + <trans-unit id="_msg920"> <source xml:space="preserve">Error: Dumpfile format record is incorrect. Got "%s", expected "format".</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">37</context></context-group> </trans-unit> - <trans-unit id="_msg903"> + <trans-unit id="_msg921"> <source xml:space="preserve">Error: Dumpfile identifier record is incorrect. Got "%s", expected "%s".</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">39</context></context-group> </trans-unit> - <trans-unit id="_msg904"> + <trans-unit id="_msg922"> <source xml:space="preserve">Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">41</context></context-group> </trans-unit> - <trans-unit id="_msg905"> - <source xml:space="preserve">Error: Listening for incoming connections failed (listen returned error %s)</source> + <trans-unit id="_msg923"> + <source xml:space="preserve">Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">44</context></context-group> </trans-unit> - <trans-unit id="_msg906"> + <trans-unit id="_msg924"> + <source xml:space="preserve">Error: Listening for incoming connections failed (listen returned error %s)</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">47</context></context-group> + </trans-unit> + <trans-unit id="_msg925"> <source xml:space="preserve">Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">46</context></context-group> + <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg907"> + <trans-unit id="_msg926"> <source xml:space="preserve">File %s already exists. If you are sure this is what you want, move it out of the way first.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">49</context></context-group> + <context-group purpose="location"><context context-type="linenumber">52</context></context-group> </trans-unit> - <trans-unit id="_msg908"> + <trans-unit id="_msg927"> <source xml:space="preserve">Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">52</context></context-group> + <context-group purpose="location"><context context-type="linenumber">55</context></context-group> </trans-unit> - <trans-unit id="_msg909"> + <trans-unit id="_msg928"> <source xml:space="preserve">More than one onion bind address is provided. Using %s for the automatically created Tor onion service.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">55</context></context-group> + <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg910"> + <trans-unit id="_msg929"> <source xml:space="preserve">No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">58</context></context-group> + <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> - <trans-unit id="_msg911"> + <trans-unit id="_msg930"> <source xml:space="preserve">No dump file provided. To use dump, -dumpfile=<filename> must be provided.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">61</context></context-group> + <context-group purpose="location"><context context-type="linenumber">64</context></context-group> </trans-unit> - <trans-unit id="_msg912"> + <trans-unit id="_msg931"> <source xml:space="preserve">No wallet file format provided. To use createfromdump, -format=<format> must be provided.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">63</context></context-group> + <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> - <trans-unit id="_msg913"> + <trans-unit id="_msg932"> <source xml:space="preserve">Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">66</context></context-group> + <context-group purpose="location"><context context-type="linenumber">69</context></context-group> </trans-unit> - <trans-unit id="_msg914"> + <trans-unit id="_msg933"> <source xml:space="preserve">Please contribute if you find %s useful. Visit %s for further information about the software.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">69</context></context-group> + <context-group purpose="location"><context context-type="linenumber">72</context></context-group> </trans-unit> - <trans-unit id="_msg915"> + <trans-unit id="_msg934"> <source xml:space="preserve">Prune configured below the minimum of %d MiB. Please use a higher number.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">72</context></context-group> + <context-group purpose="location"><context context-type="linenumber">75</context></context-group> </trans-unit> - <trans-unit id="_msg916"> + <trans-unit id="_msg935"> <source xml:space="preserve">Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">74</context></context-group> + <context-group purpose="location"><context context-type="linenumber">77</context></context-group> </trans-unit> - <trans-unit id="_msg917"> + <trans-unit id="_msg936"> <source xml:space="preserve">SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">77</context></context-group> + <context-group purpose="location"><context context-type="linenumber">80</context></context-group> </trans-unit> - <trans-unit id="_msg918"> + <trans-unit id="_msg937"> <source xml:space="preserve">The block database contains a block which appears to be from the future. This may be due to your computer's date and time being set incorrectly. Only rebuild the block database if you are sure that your computer's date and time are correct</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">80</context></context-group> + <context-group purpose="location"><context context-type="linenumber">83</context></context-group> </trans-unit> - <trans-unit id="_msg919"> + <trans-unit id="_msg938"> <source xml:space="preserve">The transaction amount is too small to send after the fee has been deducted</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">85</context></context-group> + <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> - <trans-unit id="_msg920"> + <trans-unit id="_msg939"> <source xml:space="preserve">This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">87</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg921"> + <trans-unit id="_msg940"> <source xml:space="preserve">This is a pre-release test build - use at your own risk - do not use for mining or merchant applications</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">91</context></context-group> + <context-group purpose="location"><context context-type="linenumber">94</context></context-group> </trans-unit> - <trans-unit id="_msg922"> + <trans-unit id="_msg941"> <source xml:space="preserve">This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">94</context></context-group> + <context-group purpose="location"><context context-type="linenumber">97</context></context-group> </trans-unit> - <trans-unit id="_msg923"> + <trans-unit id="_msg942"> <source xml:space="preserve">This is the transaction fee you may discard if change is smaller than dust at this level</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">97</context></context-group> + <context-group purpose="location"><context context-type="linenumber">100</context></context-group> </trans-unit> - <trans-unit id="_msg924"> + <trans-unit id="_msg943"> <source xml:space="preserve">This is the transaction fee you may pay when fee estimates are not available.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">100</context></context-group> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg925"> + <trans-unit id="_msg944"> <source xml:space="preserve">Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.</source> <target xml:space="preserve"></target> - <context-group purpose="location"><context context-type="linenumber">102</context></context-group> - </trans-unit> - <trans-unit id="_msg926"> - <source xml:space="preserve">Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.</source> - <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg927"> + <trans-unit id="_msg945"> <source xml:space="preserve">Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> - <trans-unit id="_msg928"> + <trans-unit id="_msg946"> <source xml:space="preserve">Unknown wallet file format "%s" provided. Please provide one of "bdb" or "sqlite".</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg929"> + <trans-unit id="_msg947"> <source xml:space="preserve">Warning: Dumpfile wallet format "%s" does not match command line specified format "%s".</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">114</context></context-group> </trans-unit> - <trans-unit id="_msg930"> + <trans-unit id="_msg948"> <source xml:space="preserve">Warning: Private keys detected in wallet {%s} with disabled private keys</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">117</context></context-group> </trans-unit> - <trans-unit id="_msg931"> + <trans-unit id="_msg949"> <source xml:space="preserve">Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">119</context></context-group> </trans-unit> - <trans-unit id="_msg932"> + <trans-unit id="_msg950"> <source xml:space="preserve">Witness data for blocks after height %d requires validation. Please restart with -reindex.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg933"> + <trans-unit id="_msg951"> <source xml:space="preserve">You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">125</context></context-group> </trans-unit> - <trans-unit id="_msg934"> + <trans-unit id="_msg952"> <source xml:space="preserve">%s is set very high!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">128</context></context-group> </trans-unit> - <trans-unit id="_msg935"> + <trans-unit id="_msg953"> <source xml:space="preserve">-maxmempool must be at least %d MB</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">129</context></context-group> </trans-unit> - <trans-unit id="_msg936"> + <trans-unit id="_msg954"> <source xml:space="preserve">A fatal internal error occurred, see debug.log for details</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">130</context></context-group> </trans-unit> - <trans-unit id="_msg937"> + <trans-unit id="_msg955"> <source xml:space="preserve">Cannot resolve -%s address: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">131</context></context-group> </trans-unit> - <trans-unit id="_msg938"> + <trans-unit id="_msg956"> <source xml:space="preserve">Cannot set -peerblockfilters without -blockfilterindex.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">132</context></context-group> </trans-unit> - <trans-unit id="_msg939"> + <trans-unit id="_msg957"> <source xml:space="preserve">Cannot write to data directory '%s'; check permissions.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">133</context></context-group> </trans-unit> - <trans-unit id="_msg940"> + <trans-unit id="_msg958"> <source xml:space="preserve">Change index out of range</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg941"> + <trans-unit id="_msg959"> <source xml:space="preserve">Config setting for %s only applied on %s network when in [%s] section.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg942"> + <trans-unit id="_msg960"> <source xml:space="preserve">Copyright (C) %i-%i</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">136</context></context-group> </trans-unit> - <trans-unit id="_msg943"> + <trans-unit id="_msg961"> <source xml:space="preserve">Corrupted block database detected</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">137</context></context-group> </trans-unit> - <trans-unit id="_msg944"> + <trans-unit id="_msg962"> <source xml:space="preserve">Could not find asmap file %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg945"> + <trans-unit id="_msg963"> <source xml:space="preserve">Could not parse asmap file %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg946"> + <trans-unit id="_msg964"> <source xml:space="preserve">Disk space is too low!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">140</context></context-group> </trans-unit> - <trans-unit id="_msg947"> + <trans-unit id="_msg965"> <source xml:space="preserve">Do you want to rebuild the block database now?</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">141</context></context-group> </trans-unit> - <trans-unit id="_msg948"> + <trans-unit id="_msg966"> <source xml:space="preserve">Done loading</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">142</context></context-group> </trans-unit> - <trans-unit id="_msg949"> + <trans-unit id="_msg967"> <source xml:space="preserve">Dump file %s does not exist.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg950"> + <trans-unit id="_msg968"> <source xml:space="preserve">Error creating %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">144</context></context-group> </trans-unit> - <trans-unit id="_msg951"> + <trans-unit id="_msg969"> <source xml:space="preserve">Error initializing block database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">145</context></context-group> </trans-unit> - <trans-unit id="_msg952"> + <trans-unit id="_msg970"> <source xml:space="preserve">Error initializing wallet database environment %s!</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg953"> + <trans-unit id="_msg971"> <source xml:space="preserve">Error loading %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">147</context></context-group> </trans-unit> - <trans-unit id="_msg954"> + <trans-unit id="_msg972"> <source xml:space="preserve">Error loading %s: Private keys can only be disabled during creation</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">148</context></context-group> </trans-unit> - <trans-unit id="_msg955"> + <trans-unit id="_msg973"> <source xml:space="preserve">Error loading %s: Wallet corrupted</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">149</context></context-group> </trans-unit> - <trans-unit id="_msg956"> + <trans-unit id="_msg974"> <source xml:space="preserve">Error loading %s: Wallet requires newer version of %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">150</context></context-group> </trans-unit> - <trans-unit id="_msg957"> + <trans-unit id="_msg975"> <source xml:space="preserve">Error loading block database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">151</context></context-group> </trans-unit> - <trans-unit id="_msg958"> + <trans-unit id="_msg976"> <source xml:space="preserve">Error opening block database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg959"> + <trans-unit id="_msg977"> <source xml:space="preserve">Error reading from database, shutting down.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">153</context></context-group> </trans-unit> - <trans-unit id="_msg960"> + <trans-unit id="_msg978"> <source xml:space="preserve">Error reading next record from wallet database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">154</context></context-group> </trans-unit> - <trans-unit id="_msg961"> + <trans-unit id="_msg979"> <source xml:space="preserve">Error upgrading chainstate database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">155</context></context-group> </trans-unit> - <trans-unit id="_msg962"> + <trans-unit id="_msg980"> <source xml:space="preserve">Error: Couldn't create cursor into database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">156</context></context-group> </trans-unit> - <trans-unit id="_msg963"> + <trans-unit id="_msg981"> <source xml:space="preserve">Error: Disk space is low for %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">157</context></context-group> </trans-unit> - <trans-unit id="_msg964"> + <trans-unit id="_msg982"> <source xml:space="preserve">Error: Dumpfile checksum does not match. Computed %s, expected %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> - <trans-unit id="_msg965"> + <trans-unit id="_msg983"> <source xml:space="preserve">Error: Got key that was not hex: %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">159</context></context-group> </trans-unit> - <trans-unit id="_msg966"> + <trans-unit id="_msg984"> <source xml:space="preserve">Error: Got value that was not hex: %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">160</context></context-group> </trans-unit> - <trans-unit id="_msg967"> + <trans-unit id="_msg985"> <source xml:space="preserve">Error: Keypool ran out, please call keypoolrefill first</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">161</context></context-group> </trans-unit> - <trans-unit id="_msg968"> + <trans-unit id="_msg986"> <source xml:space="preserve">Error: Missing checksum</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">162</context></context-group> </trans-unit> - <trans-unit id="_msg969"> - <source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source> + <trans-unit id="_msg987"> + <source xml:space="preserve">Error: No %s addresses available.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">163</context></context-group> </trans-unit> - <trans-unit id="_msg970"> - <source xml:space="preserve">Error: Unable to write record to new wallet</source> + <trans-unit id="_msg988"> + <source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg971"> - <source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source> + <trans-unit id="_msg989"> + <source xml:space="preserve">Error: Unable to write record to new wallet</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">165</context></context-group> </trans-unit> - <trans-unit id="_msg972"> - <source xml:space="preserve">Failed to rescan the wallet during initialization</source> + <trans-unit id="_msg990"> + <source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">166</context></context-group> </trans-unit> - <trans-unit id="_msg973"> - <source xml:space="preserve">Failed to verify database</source> + <trans-unit id="_msg991"> + <source xml:space="preserve">Failed to rescan the wallet during initialization</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">167</context></context-group> </trans-unit> - <trans-unit id="_msg974"> - <source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source> + <trans-unit id="_msg992"> + <source xml:space="preserve">Failed to verify database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">168</context></context-group> </trans-unit> - <trans-unit id="_msg975"> - <source xml:space="preserve">Ignoring duplicate -wallet %s.</source> + <trans-unit id="_msg993"> + <source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">169</context></context-group> </trans-unit> - <trans-unit id="_msg976"> - <source xml:space="preserve">Importing…</source> + <trans-unit id="_msg994"> + <source xml:space="preserve">Ignoring duplicate -wallet %s.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg977"> - <source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source> + <trans-unit id="_msg995"> + <source xml:space="preserve">Importing…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">171</context></context-group> </trans-unit> - <trans-unit id="_msg978"> - <source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source> + <trans-unit id="_msg996"> + <source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">172</context></context-group> </trans-unit> - <trans-unit id="_msg979"> - <source xml:space="preserve">Insufficient funds</source> + <trans-unit id="_msg997"> + <source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg980"> - <source xml:space="preserve">Invalid -i2psam address or hostname: '%s'</source> + <trans-unit id="_msg998"> + <source xml:space="preserve">Insufficient funds</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg981"> - <source xml:space="preserve">Invalid -onion address or hostname: '%s'</source> + <trans-unit id="_msg999"> + <source xml:space="preserve">Invalid -i2psam address or hostname: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">175</context></context-group> </trans-unit> - <trans-unit id="_msg982"> - <source xml:space="preserve">Invalid -proxy address or hostname: '%s'</source> + <trans-unit id="_msg1000"> + <source xml:space="preserve">Invalid -onion address or hostname: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg983"> - <source xml:space="preserve">Invalid P2P permission: '%s'</source> + <trans-unit id="_msg1001"> + <source xml:space="preserve">Invalid -proxy address or hostname: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">177</context></context-group> </trans-unit> - <trans-unit id="_msg984"> - <source xml:space="preserve">Invalid amount for -%s=<amount>: '%s'</source> + <trans-unit id="_msg1002"> + <source xml:space="preserve">Invalid P2P permission: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">178</context></context-group> </trans-unit> - <trans-unit id="_msg985"> - <source xml:space="preserve">Invalid amount for -discardfee=<amount>: '%s'</source> + <trans-unit id="_msg1003"> + <source xml:space="preserve">Invalid amount for -%s=<amount>: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">179</context></context-group> </trans-unit> - <trans-unit id="_msg986"> - <source xml:space="preserve">Invalid amount for -fallbackfee=<amount>: '%s'</source> + <trans-unit id="_msg1004"> + <source xml:space="preserve">Invalid amount for -discardfee=<amount>: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg987"> - <source xml:space="preserve">Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> + <trans-unit id="_msg1005"> + <source xml:space="preserve">Invalid amount for -fallbackfee=<amount>: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> </trans-unit> - <trans-unit id="_msg988"> - <source xml:space="preserve">Invalid netmask specified in -whitelist: '%s'</source> + <trans-unit id="_msg1006"> + <source xml:space="preserve">Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">182</context></context-group> </trans-unit> - <trans-unit id="_msg989"> - <source xml:space="preserve">Loading P2P addresses…</source> + <trans-unit id="_msg1007"> + <source xml:space="preserve">Invalid netmask specified in -whitelist: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">183</context></context-group> </trans-unit> - <trans-unit id="_msg990"> - <source xml:space="preserve">Loading banlist…</source> + <trans-unit id="_msg1008"> + <source xml:space="preserve">Loading P2P addresses…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">184</context></context-group> </trans-unit> - <trans-unit id="_msg991"> - <source xml:space="preserve">Loading block index…</source> + <trans-unit id="_msg1009"> + <source xml:space="preserve">Loading banlist…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">185</context></context-group> </trans-unit> - <trans-unit id="_msg992"> - <source xml:space="preserve">Loading wallet…</source> + <trans-unit id="_msg1010"> + <source xml:space="preserve">Loading block index…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">186</context></context-group> </trans-unit> - <trans-unit id="_msg993"> - <source xml:space="preserve">Need to specify a port with -whitebind: '%s'</source> + <trans-unit id="_msg1011"> + <source xml:space="preserve">Loading wallet…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">187</context></context-group> </trans-unit> - <trans-unit id="_msg994"> - <source xml:space="preserve">No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.</source> + <trans-unit id="_msg1012"> + <source xml:space="preserve">Need to specify a port with -whitebind: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">188</context></context-group> </trans-unit> - <trans-unit id="_msg995"> - <source xml:space="preserve">Not enough file descriptors available.</source> + <trans-unit id="_msg1013"> + <source xml:space="preserve">No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">189</context></context-group> </trans-unit> - <trans-unit id="_msg996"> - <source xml:space="preserve">Prune cannot be configured with a negative value.</source> + <trans-unit id="_msg1014"> + <source xml:space="preserve">Not enough file descriptors available.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">190</context></context-group> </trans-unit> - <trans-unit id="_msg997"> - <source xml:space="preserve">Prune mode is incompatible with -coinstatsindex.</source> + <trans-unit id="_msg1015"> + <source xml:space="preserve">Prune cannot be configured with a negative value.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">191</context></context-group> </trans-unit> - <trans-unit id="_msg998"> - <source xml:space="preserve">Prune mode is incompatible with -txindex.</source> + <trans-unit id="_msg1016"> + <source xml:space="preserve">Prune mode is incompatible with -coinstatsindex.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">192</context></context-group> </trans-unit> - <trans-unit id="_msg999"> - <source xml:space="preserve">Pruning blockstore…</source> + <trans-unit id="_msg1017"> + <source xml:space="preserve">Prune mode is incompatible with -txindex.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">193</context></context-group> </trans-unit> - <trans-unit id="_msg1000"> - <source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source> + <trans-unit id="_msg1018"> + <source xml:space="preserve">Pruning blockstore…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">194</context></context-group> </trans-unit> - <trans-unit id="_msg1001"> - <source xml:space="preserve">Replaying blocks…</source> + <trans-unit id="_msg1019"> + <source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">195</context></context-group> </trans-unit> - <trans-unit id="_msg1002"> - <source xml:space="preserve">Rescanning…</source> + <trans-unit id="_msg1020"> + <source xml:space="preserve">Replaying blocks…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">196</context></context-group> </trans-unit> - <trans-unit id="_msg1003"> - <source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source> + <trans-unit id="_msg1021"> + <source xml:space="preserve">Rescanning…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">197</context></context-group> </trans-unit> - <trans-unit id="_msg1004"> - <source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source> + <trans-unit id="_msg1022"> + <source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">198</context></context-group> </trans-unit> - <trans-unit id="_msg1005"> - <source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source> + <trans-unit id="_msg1023"> + <source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">199</context></context-group> </trans-unit> - <trans-unit id="_msg1006"> - <source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source> + <trans-unit id="_msg1024"> + <source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">200</context></context-group> </trans-unit> - <trans-unit id="_msg1007"> - <source xml:space="preserve">Section [%s] is not recognized.</source> + <trans-unit id="_msg1025"> + <source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">201</context></context-group> </trans-unit> - <trans-unit id="_msg1008"> - <source xml:space="preserve">Signing transaction failed</source> + <trans-unit id="_msg1026"> + <source xml:space="preserve">Section [%s] is not recognized.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> - <trans-unit id="_msg1009"> - <source xml:space="preserve">Specified -walletdir "%s" does not exist</source> + <trans-unit id="_msg1027"> + <source xml:space="preserve">Signing transaction failed</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">203</context></context-group> </trans-unit> - <trans-unit id="_msg1010"> - <source xml:space="preserve">Specified -walletdir "%s" is a relative path</source> + <trans-unit id="_msg1028"> + <source xml:space="preserve">Specified -walletdir "%s" does not exist</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">204</context></context-group> </trans-unit> - <trans-unit id="_msg1011"> - <source xml:space="preserve">Specified -walletdir "%s" is not a directory</source> + <trans-unit id="_msg1029"> + <source xml:space="preserve">Specified -walletdir "%s" is a relative path</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">205</context></context-group> </trans-unit> - <trans-unit id="_msg1012"> - <source xml:space="preserve">Specified blocks directory "%s" does not exist.</source> + <trans-unit id="_msg1030"> + <source xml:space="preserve">Specified -walletdir "%s" is not a directory</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">206</context></context-group> </trans-unit> - <trans-unit id="_msg1013"> - <source xml:space="preserve">Starting network threads…</source> + <trans-unit id="_msg1031"> + <source xml:space="preserve">Specified blocks directory "%s" does not exist.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">207</context></context-group> </trans-unit> - <trans-unit id="_msg1014"> - <source xml:space="preserve">The source code is available from %s.</source> + <trans-unit id="_msg1032"> + <source xml:space="preserve">Starting network threads…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">208</context></context-group> </trans-unit> - <trans-unit id="_msg1015"> - <source xml:space="preserve">The specified config file %s does not exist</source> + <trans-unit id="_msg1033"> + <source xml:space="preserve">The source code is available from %s.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">209</context></context-group> </trans-unit> - <trans-unit id="_msg1016"> - <source xml:space="preserve">The transaction amount is too small to pay the fee</source> + <trans-unit id="_msg1034"> + <source xml:space="preserve">The specified config file %s does not exist</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">210</context></context-group> </trans-unit> - <trans-unit id="_msg1017"> - <source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source> + <trans-unit id="_msg1035"> + <source xml:space="preserve">The transaction amount is too small to pay the fee</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">211</context></context-group> </trans-unit> - <trans-unit id="_msg1018"> - <source xml:space="preserve">This is experimental software.</source> + <trans-unit id="_msg1036"> + <source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg1019"> - <source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source> + <trans-unit id="_msg1037"> + <source xml:space="preserve">This is experimental software.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">213</context></context-group> </trans-unit> - <trans-unit id="_msg1020"> - <source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source> + <trans-unit id="_msg1038"> + <source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg1021"> - <source xml:space="preserve">Transaction amount too small</source> + <trans-unit id="_msg1039"> + <source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg1022"> - <source xml:space="preserve">Transaction amounts must not be negative</source> + <trans-unit id="_msg1040"> + <source xml:space="preserve">Transaction amount too small</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg1023"> - <source xml:space="preserve">Transaction has too long of a mempool chain</source> + <trans-unit id="_msg1041"> + <source xml:space="preserve">Transaction amounts must not be negative</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">217</context></context-group> </trans-unit> - <trans-unit id="_msg1024"> - <source xml:space="preserve">Transaction must have at least one recipient</source> + <trans-unit id="_msg1042"> + <source xml:space="preserve">Transaction has too long of a mempool chain</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">218</context></context-group> </trans-unit> - <trans-unit id="_msg1025"> - <source xml:space="preserve">Transaction too large</source> + <trans-unit id="_msg1043"> + <source xml:space="preserve">Transaction must have at least one recipient</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> - <trans-unit id="_msg1026"> - <source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source> + <trans-unit id="_msg1044"> + <source xml:space="preserve">Transaction needs a change address, but we can't generate it. %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">220</context></context-group> </trans-unit> - <trans-unit id="_msg1027"> - <source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source> + <trans-unit id="_msg1045"> + <source xml:space="preserve">Transaction too large</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">221</context></context-group> </trans-unit> - <trans-unit id="_msg1028"> - <source xml:space="preserve">Unable to create the PID file '%s': %s</source> + <trans-unit id="_msg1046"> + <source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg1029"> - <source xml:space="preserve">Unable to generate initial keys</source> + <trans-unit id="_msg1047"> + <source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">223</context></context-group> </trans-unit> - <trans-unit id="_msg1030"> - <source xml:space="preserve">Unable to generate keys</source> + <trans-unit id="_msg1048"> + <source xml:space="preserve">Unable to create the PID file '%s': %s</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">224</context></context-group> </trans-unit> - <trans-unit id="_msg1031"> - <source xml:space="preserve">Unable to open %s for writing</source> + <trans-unit id="_msg1049"> + <source xml:space="preserve">Unable to generate initial keys</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> - <trans-unit id="_msg1032"> - <source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source> + <trans-unit id="_msg1050"> + <source xml:space="preserve">Unable to generate keys</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg1033"> - <source xml:space="preserve">Unknown -blockfilterindex value %s.</source> + <trans-unit id="_msg1051"> + <source xml:space="preserve">Unable to open %s for writing</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">227</context></context-group> </trans-unit> - <trans-unit id="_msg1034"> - <source xml:space="preserve">Unknown address type '%s'</source> + <trans-unit id="_msg1052"> + <source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">228</context></context-group> </trans-unit> - <trans-unit id="_msg1035"> - <source xml:space="preserve">Unknown change type '%s'</source> + <trans-unit id="_msg1053"> + <source xml:space="preserve">Unknown -blockfilterindex value %s.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">229</context></context-group> </trans-unit> - <trans-unit id="_msg1036"> - <source xml:space="preserve">Unknown network specified in -onlynet: '%s'</source> + <trans-unit id="_msg1054"> + <source xml:space="preserve">Unknown address type '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">230</context></context-group> </trans-unit> - <trans-unit id="_msg1037"> - <source xml:space="preserve">Unsupported logging category %s=%s.</source> + <trans-unit id="_msg1055"> + <source xml:space="preserve">Unknown change type '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> - <trans-unit id="_msg1038"> - <source xml:space="preserve">Upgrading UTXO database</source> + <trans-unit id="_msg1056"> + <source xml:space="preserve">Unknown network specified in -onlynet: '%s'</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">232</context></context-group> </trans-unit> - <trans-unit id="_msg1039"> - <source xml:space="preserve">Upgrading txindex database</source> + <trans-unit id="_msg1057"> + <source xml:space="preserve">Unknown new rules activated (versionbit %i)</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">233</context></context-group> </trans-unit> - <trans-unit id="_msg1040"> - <source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source> + <trans-unit id="_msg1058"> + <source xml:space="preserve">Unsupported logging category %s=%s.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">234</context></context-group> </trans-unit> - <trans-unit id="_msg1041"> - <source xml:space="preserve">Verifying blocks…</source> + <trans-unit id="_msg1059"> + <source xml:space="preserve">Upgrading UTXO database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">235</context></context-group> </trans-unit> - <trans-unit id="_msg1042"> - <source xml:space="preserve">Verifying wallet(s)…</source> + <trans-unit id="_msg1060"> + <source xml:space="preserve">Upgrading txindex database</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">236</context></context-group> </trans-unit> - <trans-unit id="_msg1043"> - <source xml:space="preserve">Wallet needed to be rewritten: restart %s to complete</source> + <trans-unit id="_msg1061"> + <source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">237</context></context-group> </trans-unit> - <trans-unit id="_msg1044"> - <source xml:space="preserve">Warning: unknown new rules activated (versionbit %i)</source> + <trans-unit id="_msg1062"> + <source xml:space="preserve">Verifying blocks…</source> <target xml:space="preserve"></target> <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> + <trans-unit id="_msg1063"> + <source xml:space="preserve">Verifying wallet(s)…</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">239</context></context-group> + </trans-unit> + <trans-unit id="_msg1064"> + <source xml:space="preserve">Wallet needed to be rewritten: restart %s to complete</source> + <target xml:space="preserve"></target> + <context-group purpose="location"><context context-type="linenumber">240</context></context-group> + </trans-unit> </group> </body></file> </xliff> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 6ad8db4348..b12fe96567 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -92,6 +92,11 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->thirdPartyTxUrls->setVisible(false); } +#ifndef ENABLE_EXTERNAL_SIGNER + //: "External signing" means using devices such as hardware wallets. + ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)")); + ui->externalSignerPath->setEnabled(false); +#endif /* Display elements init */ QDir translations(":translations"); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 27783bdf87..864a62edc8 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -69,20 +69,18 @@ public: foreground = brush.color(); } - painter->setPen(foreground); - QRect boundingRect; - painter->drawText(addressRect, Qt::AlignLeft | Qt::AlignVCenter, address, &boundingRect); - int address_rect_min_width = boundingRect.width(); - - if (index.data(TransactionTableModel::WatchonlyRole).toBool()) - { + if (index.data(TransactionTableModel::WatchonlyRole).toBool()) { QIcon iconWatchonly = qvariant_cast<QIcon>(index.data(TransactionTableModel::WatchonlyDecorationRole)); - QRect watchonlyRect(boundingRect.right() + 5, mainRect.top()+ypad+halfheight, 16, halfheight); + QRect watchonlyRect(addressRect.left(), addressRect.top(), 16, addressRect.height()); iconWatchonly = platformStyle->TextColorIcon(iconWatchonly); iconWatchonly.paint(painter, watchonlyRect); - address_rect_min_width += 5 + watchonlyRect.width(); + addressRect.setLeft(addressRect.left() + watchonlyRect.width() + 5); } + painter->setPen(foreground); + QRect boundingRect; + painter->drawText(addressRect, Qt::AlignLeft | Qt::AlignVCenter, address, &boundingRect); + if(amount < 0) { foreground = COLOR_NEGATIVE; @@ -109,7 +107,8 @@ public: QRect date_bounding_rect; painter->drawText(amountRect, Qt::AlignLeft | Qt::AlignVCenter, GUIUtil::dateTimeStr(date), &date_bounding_rect); - const int minimum_width = std::max(address_rect_min_width, amount_bounding_rect.width() + date_bounding_rect.width()); + // 0.4*date_bounding_rect.width() is used to visually distinguish a date from an amount. + const int minimum_width = 1.4 * date_bounding_rect.width() + amount_bounding_rect.width(); const auto search = m_minimum_width.find(index.row()); if (search == m_minimum_width.end() || search->second != minimum_width) { m_minimum_width[index.row()] = minimum_width; @@ -166,8 +165,8 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) // start with displaying the "out of sync" warnings showOutOfSyncWarning(true); - connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); - connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); + connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked); + connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked); } void OverviewPage::handleTransactionClicked(const QModelIndex &index) @@ -176,11 +175,6 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) Q_EMIT transactionClicked(filter->mapToSource(index)); } -void OverviewPage::handleOutOfSyncWarningClicks() -{ - Q_EMIT outOfSyncWarningClicked(); -} - void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; @@ -302,13 +296,13 @@ void OverviewPage::setWalletModel(WalletModel *model) void OverviewPage::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning")); ui->labelTransactionsStatus->setIcon(icon); ui->labelWalletStatus->setIcon(icon); } -#endif + + QWidget::changeEvent(e); } void OverviewPage::updateDisplayUnit() diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 755a107a00..5270741c0d 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -65,7 +65,6 @@ private Q_SLOTS: void handleTransactionClicked(const QModelIndex &index); void updateAlerts(const QString &warnings); void updateWatchOnlyLabels(bool showWatchOnly); - void handleOutOfSyncWarningClicks(); void setMonospacedFont(bool use_embedded_font); }; diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index b324693692..1b7fda6e77 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -179,5 +179,7 @@ void PeerTableModel::refresh() m_peers_data.swap(new_peers_data); } - Q_EMIT changed(); + const auto top_left = index(0, 0); + const auto bottom_right = index(rowCount() - 1, columnCount() - 1); + Q_EMIT dataChanged(top_left, bottom_right); } diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 0ff1b5dba7..0d841ebf28 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -73,9 +73,6 @@ public: public Q_SLOTS: void refresh(); -Q_SIGNALS: - void changed(); - private: //! Internal peer data structure. QList<CNodeCombinedStats> m_peers_data{}; diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 99318c3bc0..2adfeeaaf0 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -50,7 +50,7 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx) bool complete; size_t n_could_sign; FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness. - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_could_sign); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, &n_could_sign, m_transaction_data, complete); if (err != TransactionError::OK) { showStatus(tr("Failed to load transaction: %1") .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR); @@ -67,7 +67,7 @@ void PSBTOperationsDialog::signTransaction() { bool complete; size_t n_signed; - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_signed); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, &n_signed, m_transaction_data, complete); if (err != TransactionError::OK) { showStatus(tr("Failed to sign transaction: %1") @@ -226,7 +226,7 @@ void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) { size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) { size_t n_signed; bool complete; - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, m_transaction_data, complete, &n_signed); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, &n_signed, m_transaction_data, complete); if (err != TransactionError::OK) { return 0; diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index f5200bb5c0..7cdd568644 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -27,8 +27,8 @@ QRImageWidget::QRImageWidget(QWidget *parent): QLabel(parent), contextMenu(nullptr) { contextMenu = new QMenu(this); - contextMenu->addAction(tr("Save Image…"), this, &QRImageWidget::saveImage); - contextMenu->addAction(tr("Copy Image"), this, &QRImageWidget::copyImage); + contextMenu->addAction(tr("&Save Image…"), this, &QRImageWidget::saveImage); + contextMenu->addAction(tr("&Copy Image"), this, &QRImageWidget::copyImage); } bool QRImageWidget::setQR(const QString& data, const QString& text) diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 3f4d7f85e6..d47ee95826 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -44,11 +44,11 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid // context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy URI"), this, &ReceiveCoinsDialog::copyURI); - contextMenu->addAction(tr("Copy address"), this, &ReceiveCoinsDialog::copyAddress); - copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &ReceiveCoinsDialog::copyLabel); - copyMessageAction = contextMenu->addAction(tr("Copy message"), this, &ReceiveCoinsDialog::copyMessage); - copyAmountAction = contextMenu->addAction(tr("Copy amount"), this, &ReceiveCoinsDialog::copyAmount); + contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI); + contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel); + copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage); + copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount); connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu); connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index abe7de8f89..41f22e9c34 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -90,7 +90,7 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) ui->wallet_content->hide(); } - ui->btnVerify->setVisible(this->model->wallet().hasExternalSigner()); + ui->btnVerify->setVisible(model->wallet().hasExternalSigner()); connect(ui->btnVerify, &QPushButton::clicked, [this] { model->displayAddress(info.address.toStdString()); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 9579e6dc24..9c57816f91 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -35,6 +35,7 @@ #endif #include <QAbstractButton> +#include <QAbstractItemModel> #include <QDateTime> #include <QFont> #include <QKeyEvent> @@ -529,6 +530,8 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty //: Secondary shortcut to decrease the RPC console font size. GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_")); + ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon"))); + // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); ui->lineEdit->setMaxLength(16 * 1024 * 1024); @@ -675,16 +678,16 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create peer table context menu peersTableContextMenu = new QMenu(this); - peersTableContextMenu->addAction(tr("Disconnect"), this, &RPCConsole::disconnectSelectedNode); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 hour"), [this] { banSelectedNode(60 * 60); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 day"), [this] { banSelectedNode(60 * 60 * 24); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 week"), [this] { banSelectedNode(60 * 60 * 24 * 7); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 year"), [this] { banSelectedNode(60 * 60 * 24 * 365); }); + peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &year"), [this] { banSelectedNode(60 * 60 * 24 * 365); }); connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu); // peer table signal handling - update peer details when selecting new node connect(ui->peerWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RPCConsole::updateDetailWidget); - connect(model->getPeerTableModel(), &PeerTableModel::changed, this, &RPCConsole::updateDetailWidget); + connect(model->getPeerTableModel(), &QAbstractItemModel::dataChanged, [this] { updateDetailWidget(); }); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); @@ -701,7 +704,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create ban table context menu banTableContextMenu = new QMenu(this); - banTableContextMenu->addAction(tr("Unban"), this, &RPCConsole::unbanSelectedNode); + banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode); connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu); // ban table signal handling - clear peer details when clicking a peer in the ban table @@ -882,7 +885,6 @@ void RPCConsole::keyPressEvent(QKeyEvent *event) void RPCConsole::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { ui->clearButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontbigger"))); @@ -896,7 +898,8 @@ void RPCConsole::changeEvent(QEvent* e) platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } -#endif + + QWidget::changeEvent(e); } void RPCConsole::message(int category, const QString &message, bool html) diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index e87a2b97bc..c9bf757dfc 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -200,12 +200,14 @@ void SendCoinsDialog::setModel(WalletModel *_model) ui->optInRBF->setCheckState(Qt::Checked); if (model->wallet().hasExternalSigner()) { + //: "device" usually means a hardware wallet ui->sendButton->setText(tr("Sign on device")); if (gArgs.GetArg("-signer", "") != "") { ui->sendButton->setEnabled(true); ui->sendButton->setToolTip(tr("Connect your hardware wallet first.")); } else { ui->sendButton->setEnabled(false); + //: "External signer" means using devices such as hardware wallets. ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet")); } } else if (model->wallet().privateKeysDisabled()) { @@ -414,23 +416,25 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) bool complete = false; // Always fill without signing first. This prevents an external signer // from being called prematurely and is not expensive. - TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); assert(!complete); assert(err == TransactionError::OK); if (model->wallet().hasExternalSigner()) { try { - err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); } catch (const std::runtime_error& e) { QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); send_failure = true; return; } if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { + //: "External signer" means using devices such as hardware wallets. QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); send_failure = true; return; } if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { + //: "External signer" means using devices such as hardware wallets. QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); send_failure = true; return; diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index f701bb9615..683c0441fa 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -238,7 +238,6 @@ void SendCoinsEntry::updateDisplayUnit() void SendCoinsEntry::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book"))); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste"))); @@ -246,7 +245,8 @@ void SendCoinsEntry::changeEvent(QEvent* e) ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); } -#endif + + QStackedWidget::changeEvent(e); } bool SendCoinsEntry::updateLabel(const QString &address) diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index b982cc577d..33589f09bf 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -286,7 +286,6 @@ bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event) void SignVerifyMessageDialog::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { ui->addressBookButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book"))); ui->pasteButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste"))); @@ -297,5 +296,6 @@ void SignVerifyMessageDialog::changeEvent(QEvent* e) ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/transaction_0"))); ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); } -#endif + + QDialog::changeEvent(e); } diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index cb3dbd2267..9c31cd50df 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -85,11 +85,6 @@ void AppTests::appTests() // Reset global state to avoid interfering with later tests. LogInstance().DisconnectTestLogger(); AbortShutdown(); - { - LOCK(cs_main); - UnloadBlockIndex(/* mempool */ nullptr, g_chainman); - g_chainman.Reset(); - } } //! Entry point for BitcoinGUI tests. diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index ea35f80cf5..e883337fb5 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -69,7 +69,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe ->findChild<QCheckBox*>("optInRBF") ->setCheckState(rbf ? Qt::Checked : Qt::Unchecked); uint256 txid; - boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) { + boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const uint256& hash, ChangeType status) { if (status == CT_NEW) txid = hash; })); ConfirmSend(); @@ -146,14 +146,14 @@ void TestGUI(interfaces::Node& node) LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); - wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(105, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); } { WalletRescanReserver reserver(*wallet); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */); QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); - QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); + QCOMPARE(result.last_scanned_block, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); QVERIFY(result.last_failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 1e8e012dcf..83d17a32c0 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -163,19 +163,19 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu = new QMenu(this); contextMenu->setObjectName("contextMenu"); - copyAddressAction = contextMenu->addAction(tr("Copy address"), this, &TransactionView::copyAddress); - copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &TransactionView::copyLabel); - contextMenu->addAction(tr("Copy amount"), this, &TransactionView::copyAmount); - contextMenu->addAction(tr("Copy transaction ID"), this, &TransactionView::copyTxID); - contextMenu->addAction(tr("Copy raw transaction"), this, &TransactionView::copyTxHex); - contextMenu->addAction(tr("Copy full transaction details"), this, &TransactionView::copyTxPlainText); - contextMenu->addAction(tr("Show transaction details"), this, &TransactionView::showDetails); + copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount); + contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID); + contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex); + contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText); + contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails); contextMenu->addSeparator(); - bumpFeeAction = contextMenu->addAction(tr("Increase transaction fee")); + bumpFeeAction = contextMenu->addAction(tr("Increase transaction &fee")); GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee); bumpFeeAction->setObjectName("bumpFeeAction"); - abandonAction = contextMenu->addAction(tr("Abandon transaction"), this, &TransactionView::abandonTx); - contextMenu->addAction(tr("Edit address label"), this, &TransactionView::editLabel); + abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx); + contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel); connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate); connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType); @@ -245,7 +245,6 @@ void TransactionView::setModel(WalletModel *_model) void TransactionView::changeEvent(QEvent* e) { -#ifdef Q_OS_MACOS if (e->type() == QEvent::PaletteChange) { watchOnlyWidget->setItemIcon( TransactionFilterProxy::WatchOnlyFilter_Yes, @@ -254,7 +253,8 @@ void TransactionView::changeEvent(QEvent* e) TransactionFilterProxy::WatchOnlyFilter_No, m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_minus"))); } -#endif + + QWidget::changeEvent(e); } void TransactionView::chooseDate(int idx) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 7e5790fd87..3cceb5ca5a 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -11,6 +11,7 @@ #include <qt/guiutil.h> #include <qt/walletmodel.h> +#include <external_signer.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <util/string.h> @@ -295,7 +296,6 @@ void CreateWalletActivity::create() { m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); -#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> signers; try { signers = node().externalSigners(); @@ -303,7 +303,6 @@ void CreateWalletActivity::create() QMessageBox::critical(nullptr, tr("Can't list signers"), e.what()); } m_create_wallet_dialog->setSigners(signers); -#endif m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); m_create_wallet_dialog->show(); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 0f2ea99685..a1f357e0db 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -242,8 +242,3 @@ WalletModel* WalletFrame::currentWalletModel() const WalletView* wallet_view = currentWalletView(); return wallet_view ? wallet_view->getWalletModel() : nullptr; } - -void WalletFrame::outOfSyncWarningClicked() -{ - Q_EMIT requestedSyncWarningInfo(); -} diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 844ed121a0..4f77bd716f 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -47,9 +47,6 @@ public: QSize sizeHint() const override { return m_size_hint; } Q_SIGNALS: - /** Notify that the user has requested more information about the out-of-sync warning */ - void requestedSyncWarningInfo(); - void createWalletButtonClicked(); private: @@ -98,8 +95,6 @@ public Q_SLOTS: void usedSendingAddresses(); /** Show used receiving addresses */ void usedReceivingAddresses(); - /** Pass on signal over requested out-of-sync-warning information */ - void outOfSyncWarningClicked(); }; #endif // BITCOIN_QT_WALLETFRAME_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e32b7c2807..967dd588b4 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -525,7 +525,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) if (create_psbt) { PartiallySignedTransaction psbtx(mtx); bool complete = false; - const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); if (err != TransactionError::OK || complete) { QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); return false; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index cc9e1502f0..3b8cf4c7ed 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -73,7 +73,7 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page connect(overviewPage, &OverviewPage::transactionClicked, transactionView, qOverload<const QModelIndex&>(&TransactionView::focusTransaction)); - connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); + connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::outOfSyncWarningClicked); connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); // Highlight transaction after send @@ -347,8 +347,3 @@ void WalletView::showProgress(const QString &title, int nProgress) } } } - -void WalletView::requestedSyncWarningInfo() -{ - Q_EMIT outOfSyncWarningClicked(); -} diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 68f8a5e95b..fedf06b710 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -111,9 +111,6 @@ public Q_SLOTS: /** Show progress dialog e.g. for rescan */ void showProgress(const QString &title, int nProgress); - /** User has requested more information about the out of sync state */ - void requestedSyncWarningInfo(); - Q_SIGNALS: void setPrivacy(bool privacy); void transactionClicked(); diff --git a/src/rest.cpp b/src/rest.cpp index 747c7aea19..d599f381e3 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -125,7 +125,7 @@ static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req) __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)); return nullptr; } - return node_context->chainman; + return node_context->chainman.get(); } static RetFormat ParseDataFormat(std::string& param, const std::string& strReq) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 83c1975d38..c4a89c9772 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -10,8 +10,11 @@ #include <chain.h> #include <chainparams.h> #include <coins.h> +#include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> +#include <deploymentinfo.h> +#include <deploymentstatus.h> #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> @@ -37,6 +40,7 @@ #include <util/translation.h> #include <validation.h> #include <validationinterface.h> +#include <versionbits.h> #include <warnings.h> #include <stdint.h> @@ -84,7 +88,6 @@ ChainstateManager& EnsureChainman(const NodeContext& node) if (!node.chainman) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); } - WITH_LOCK(::cs_main, CHECK_NONFATAL(std::addressof(g_chainman) == std::addressof(*node.chainman))); return *node.chainman; } @@ -1344,32 +1347,29 @@ static RPCHelpMan verifychain() }; } -static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int softfork_height, int tip_height) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) { // For buried deployments. - // A buried deployment is one where the height of the activation has been hardcoded into - // the client implementation long after the consensus change has activated. See BIP 90. - // Buried deployments with activation height value of - // std::numeric_limits<int>::max() are disabled and thus hidden. - if (softfork_height == std::numeric_limits<int>::max()) return; + + if (!DeploymentEnabled(params, dep)) return; UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); // getblockchaininfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", tip_height + 1 >= softfork_height); - rv.pushKV("height", softfork_height); - softforks.pushKV(name, rv); + rv.pushKV("active", DeploymentActiveAfter(active_chain_tip, params, dep)); + rv.pushKV("height", params.DeploymentHeight(dep)); + softforks.pushKV(DeploymentName(dep), rv); } -static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) { // For BIP9 deployments. - // Deployments that are never active are hidden. - if (consensusParams.vDeployments[id].nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) return; + + if (!DeploymentEnabled(consensusParams, id)) return; UniValue bip9(UniValue::VOBJ); - const ThresholdState thresholdState = VersionBitsState(active_chain_tip, consensusParams, id, versionbitscache); + const ThresholdState thresholdState = g_versionbitscache.State(active_chain_tip, consensusParams, id); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; @@ -1383,12 +1383,12 @@ static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniVal } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - int64_t since_height = VersionBitsStateSinceHeight(active_chain_tip, consensusParams, id, versionbitscache); + int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); - BIP9Stats statsStruct = VersionBitsStatistics(active_chain_tip, consensusParams, id); + BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); @@ -1406,7 +1406,7 @@ static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniVal } rv.pushKV("active", ThresholdState::ACTIVE == thresholdState); - softforks.pushKV(name, rv); + softforks.pushKV(DeploymentName(id), rv); } RPCHelpMan getblockchaininfo() @@ -1503,14 +1503,14 @@ RPCHelpMan getblockchaininfo() const Consensus::Params& consensusParams = Params().GetConsensus(); UniValue softforks(UniValue::VOBJ); - BuriedForkDescPushBack(softforks, "bip34", consensusParams.BIP34Height, height); - BuriedForkDescPushBack(softforks, "bip66", consensusParams.BIP66Height, height); - BuriedForkDescPushBack(softforks, "bip65", consensusParams.BIP65Height, height); - BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight, height); - BuriedForkDescPushBack(softforks, "segwit", consensusParams.SegwitHeight, height); - BIP9SoftForkDescPushBack(tip, softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); - BIP9SoftForkDescPushBack(tip, softforks, "taproot", consensusParams, Consensus::DEPLOYMENT_TAPROOT); - obj.pushKV("softforks", softforks); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_CSV); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); + SoftForkDescPushBack(tip, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); + obj.pushKV("softforks", softforks); obj.pushKV("warnings", GetWarnings(false).original); return obj; @@ -1707,7 +1707,7 @@ static RPCHelpMan preciousblock() } BlockValidationState state; - chainman.ActiveChainstate().PreciousBlock(state, Params(), pblockindex); + chainman.ActiveChainstate().PreciousBlock(state, pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -1744,10 +1744,10 @@ static RPCHelpMan invalidateblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } - chainman.ActiveChainstate().InvalidateBlock(state, Params(), pblockindex); + chainman.ActiveChainstate().InvalidateBlock(state, pblockindex); if (state.IsValid()) { - chainman.ActiveChainstate().ActivateBestChain(state, Params()); + chainman.ActiveChainstate().ActivateBestChain(state); } if (!state.IsValid()) { @@ -1788,7 +1788,7 @@ static RPCHelpMan reconsiderblock() } BlockValidationState state; - chainman.ActiveChainstate().ActivateBestChain(state, Params()); + chainman.ActiveChainstate().ActivateBestChain(state); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -2259,6 +2259,7 @@ public: if (g_scan_in_progress.exchange(true)) { return false; } + CHECK_NONFATAL(g_scan_progress == 0); m_could_reserve = true; return true; } @@ -2266,6 +2267,7 @@ public: ~CoinsViewScanReserver() { if (m_could_reserve) { g_scan_in_progress = false; + g_scan_progress = 0; } } }; @@ -2382,7 +2384,6 @@ static RPCHelpMan scantxoutset() std::vector<CTxOut> input_txos; std::map<COutPoint, Coin> coins; g_should_abort_scan = false; - g_scan_progress = 0; int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; CBlockIndex* tip; @@ -2392,7 +2393,7 @@ static RPCHelpMan scantxoutset() LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); - pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor()); + pcursor = active_chainstate.CoinsDB().Cursor(); CHECK_NONFATAL(pcursor); tip = active_chainstate.m_chain.Tip(); CHECK_NONFATAL(tip); @@ -2591,7 +2592,7 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } - pcursor = std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor()); + pcursor = chainstate.CoinsDB().Cursor(); tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock); CHECK_NONFATAL(tip); } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 6826e6fd07..2762d78493 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -10,6 +10,8 @@ #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> +#include <deploymentinfo.h> +#include <deploymentstatus.h> #include <key_io.h> #include <miner.h> #include <net.h> @@ -34,7 +36,6 @@ #include <util/translation.h> #include <validation.h> #include <validationinterface.h> -#include <versionbitsinfo.h> #include <warnings.h> #include <memory> @@ -114,7 +115,6 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& { LOCK(cs_main); - CHECK_NONFATAL(std::addressof(::ChainActive()) == std::addressof(chainman.ActiveChain())); IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce); } @@ -147,7 +147,6 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me { // Don't keep cs_main locked LOCK(cs_main); - CHECK_NONFATAL(std::addressof(::ChainActive()) == std::addressof(chainman.ActiveChain())); nHeight = chainman.ActiveChain().Height(); nHeightEnd = nHeight+nGenerate; } @@ -776,7 +775,7 @@ static RPCHelpMan getblocktemplate() pblock->nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration - const bool fPreSegWit = (pindexPrev->nHeight + 1 < consensusParams.SegwitHeight); + const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT); UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); @@ -842,7 +841,7 @@ static RPCHelpMan getblocktemplate() UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); - ThresholdState state = VersionBitsState(pindexPrev, consensusParams, pos, versionbitscache); + ThresholdState state = g_versionbitscache.State(pindexPrev, consensusParams, pos); switch (state) { case ThresholdState::DEFINED: case ThresholdState::FAILED: @@ -850,7 +849,7 @@ static RPCHelpMan getblocktemplate() break; case ThresholdState::LOCKED_IN: // Ensure bit is set in block version - pblock->nVersion |= VersionBitsMask(consensusParams, pos); + pblock->nVersion |= g_versionbitscache.Mask(consensusParams, pos); // FALL THROUGH to get vbavailable set... case ThresholdState::STARTED: { @@ -859,7 +858,7 @@ static RPCHelpMan getblocktemplate() if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (!vbinfo.gbt_force) { // If the client doesn't support this, don't indicate it in the [default] version - pblock->nVersion &= ~VersionBitsMask(consensusParams, pos); + pblock->nVersion &= ~g_versionbitscache.Mask(consensusParams, pos); } } break; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index ab239fe79c..5178ce60e8 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -131,6 +131,9 @@ static RPCHelpMan createmultisig() if (!ParseOutputType(request.params[2].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); } + if (output_type == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); + } } // Construct using pay-to-script-hash: diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 414c6637a5..617dfec98f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -753,7 +753,8 @@ static RPCHelpMan signrawtransactionwithkey() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -893,8 +894,7 @@ static RPCHelpMan testmempoolaccept() "\nThis checks if transactions violate the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", { - {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.\n" - " Length must be one for now.", + {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.", { {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, }, @@ -1655,6 +1655,7 @@ static RPCHelpMan utxoupdatepsbt() } // Fill the inputs + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs.at(i); @@ -1671,7 +1672,7 @@ static RPCHelpMan utxoupdatepsbt() // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1); } // Update script/keypath information using descriptor data. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 51cf8a7d62..682b55742a 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -166,7 +166,7 @@ public: * write_cache is the cache to write keys to (if not nullptr) * Caches are not exclusive but this is not tested. Currently we use them exclusively */ - virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) = 0; + virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const = 0; /** Whether this represent multiple public keys at different positions. */ virtual bool IsRange() const = 0; @@ -181,7 +181,7 @@ public: virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; /** Get the descriptor string form with the xpub at the last hardened derivation */ - virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const = 0; + virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0; /** Derive a private key, if private data is available in arg. */ virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0; @@ -199,7 +199,7 @@ class OriginPubkeyProvider final : public PubkeyProvider public: OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)) {} - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override { if (!m_provider->GetPubKey(pos, arg, key, info, read_cache, write_cache)) return false; std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint); @@ -216,10 +216,10 @@ public: ret = "[" + OriginString() + "]" + std::move(sub); return true; } - bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override + bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override { std::string sub; - if (!m_provider->ToNormalizedString(arg, sub, priv)) return false; + if (!m_provider->ToNormalizedString(arg, sub, cache)) return false; // If m_provider is a BIP32PubkeyProvider, we may get a string formatted like a OriginPubkeyProvider // In that case, we need to strip out the leading square bracket and fingerprint from the substring, // and append that to our own origin string. @@ -244,8 +244,8 @@ class ConstPubkeyProvider final : public PubkeyProvider bool m_xonly; public: - ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly = false) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {} - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override + ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {} + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override { key = m_pubkey; info.path.clear(); @@ -263,9 +263,8 @@ public: ret = EncodeSecret(key); return true; } - bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override + bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override { - if (priv) return ToPrivateString(arg, ret); ret = ToString(); return true; } @@ -288,9 +287,6 @@ class BIP32PubkeyProvider final : public PubkeyProvider CExtPubKey m_root_extkey; KeyPath m_path; DeriveType m_derive; - // Cache of the parent of the final derived pubkeys. - // Primarily useful for situations when no read_cache is provided - CExtPubKey m_cached_xpub; bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const { @@ -305,11 +301,14 @@ class BIP32PubkeyProvider final : public PubkeyProvider } // Derives the last xprv - bool GetDerivedExtKey(const SigningProvider& arg, CExtKey& xprv) const + bool GetDerivedExtKey(const SigningProvider& arg, CExtKey& xprv, CExtKey& last_hardened) const { if (!GetExtKey(arg, xprv)) return false; for (auto entry : m_path) { xprv.Derive(xprv, entry); + if (entry >> 31) { + last_hardened = xprv; + } } return true; } @@ -327,7 +326,7 @@ public: BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive) {} bool IsRange() const override { return m_derive != DeriveType::NO; } size_t GetSize() const override { return 33; } - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override { // Info of parent of the to be derived pubkey KeyOriginInfo parent_info; @@ -343,6 +342,7 @@ public: // Derive keys or fetch them from cache CExtPubKey final_extkey = m_root_extkey; CExtPubKey parent_extkey = m_root_extkey; + CExtPubKey last_hardened_extkey; bool der = true; if (read_cache) { if (!read_cache->GetCachedDerivedExtPubKey(m_expr_index, pos, final_extkey)) { @@ -352,16 +352,17 @@ public: final_extkey = parent_extkey; if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos); } - } else if (m_cached_xpub.pubkey.IsValid() && m_derive != DeriveType::HARDENED) { - parent_extkey = final_extkey = m_cached_xpub; - if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos); } else if (IsHardened()) { CExtKey xprv; - if (!GetDerivedExtKey(arg, xprv)) return false; + CExtKey lh_xprv; + if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return false; parent_extkey = xprv.Neuter(); if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos); if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL); final_extkey = xprv.Neuter(); + if (lh_xprv.key.IsValid()) { + last_hardened_extkey = lh_xprv.Neuter(); + } } else { for (auto entry : m_path) { der = parent_extkey.Derive(parent_extkey, entry); @@ -376,15 +377,14 @@ public: final_info_out = final_info_out_tmp; key_out = final_extkey.pubkey; - // We rely on the consumer to check that m_derive isn't HARDENED as above - // But we can't have already cached something in case we read something from the cache - // and parent_extkey isn't actually the parent. - if (!m_cached_xpub.pubkey.IsValid()) m_cached_xpub = parent_extkey; - if (write_cache) { // Only cache parent if there is any unhardened derivation if (m_derive != DeriveType::HARDENED) { write_cache->CacheParentExtPubKey(m_expr_index, parent_extkey); + // Cache last hardened xpub if we have it + if (last_hardened_extkey.pubkey.IsValid()) { + write_cache->CacheLastHardenedExtPubKey(m_expr_index, last_hardened_extkey); + } } else if (final_info_out.path.size() > 0) { write_cache->CacheDerivedExtPubKey(m_expr_index, pos, final_extkey); } @@ -412,11 +412,10 @@ public: } return true; } - bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override + bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override { // For hardened derivation type, just return the typical string, nothing to normalize if (m_derive == DeriveType::HARDENED) { - if (priv) return ToPrivateString(arg, out); out = ToString(); return true; } @@ -429,33 +428,42 @@ public: } // Either no derivation or all unhardened derivation if (i == -1) { - if (priv) return ToPrivateString(arg, out); out = ToString(); return true; } - // Derive the xpub at the last hardened step - CExtKey xprv; - if (!GetExtKey(arg, xprv)) return false; + // Get the path to the last hardened stup KeyOriginInfo origin; int k = 0; for (; k <= i; ++k) { - // Derive - xprv.Derive(xprv, m_path.at(k)); // Add to the path origin.path.push_back(m_path.at(k)); - // First derivation element, get the fingerprint for origin - if (k == 0) { - std::copy(xprv.vchFingerprint, xprv.vchFingerprint + 4, origin.fingerprint); - } } // Build the remaining path KeyPath end_path; for (; k < (int)m_path.size(); ++k) { end_path.push_back(m_path.at(k)); } + // Get the fingerprint + CKeyID id = m_root_extkey.pubkey.GetID(); + std::copy(id.begin(), id.begin() + 4, origin.fingerprint); + + CExtPubKey xpub; + CExtKey lh_xprv; + // If we have the cache, just get the parent xpub + if (cache != nullptr) { + cache->GetCachedLastHardenedExtPubKey(m_expr_index, xpub); + } + if (!xpub.pubkey.IsValid()) { + // Cache miss, or nor cache, or need privkey + CExtKey xprv; + if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return false; + xpub = lh_xprv.Neuter(); + } + assert(xpub.pubkey.IsValid()); + // Build the string std::string origin_str = HexStr(origin.fingerprint) + FormatHDKeypath(origin.path); - out = "[" + origin_str + "]" + (priv ? EncodeExtKey(xprv) : EncodeExtPubKey(xprv.Neuter())) + FormatHDKeypath(end_path); + out = "[" + origin_str + "]" + EncodeExtPubKey(xpub) + FormatHDKeypath(end_path); if (IsRange()) { out += "/*"; assert(m_derive == DeriveType::UNHARDENED); @@ -465,7 +473,8 @@ public: bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override { CExtKey extkey; - if (!GetDerivedExtKey(arg, extkey)) return false; + CExtKey dummy; + if (!GetDerivedExtKey(arg, extkey, dummy)) return false; if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL); key = extkey.key; @@ -508,6 +517,13 @@ public: DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(Vector(std::move(script))) {} DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::vector<std::unique_ptr<DescriptorImpl>> scripts, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(std::move(scripts)) {} + enum class StringType + { + PUBLIC, + PRIVATE, + NORMALIZED, + }; + bool IsSolvable() const override { for (const auto& arg : m_subdescriptor_args) { @@ -527,19 +543,19 @@ public: return false; } - virtual bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const + virtual bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const { size_t pos = 0; for (const auto& scriptarg : m_subdescriptor_args) { if (pos++) ret += ","; std::string tmp; - if (!scriptarg->ToStringHelper(arg, tmp, priv, normalized)) return false; + if (!scriptarg->ToStringHelper(arg, tmp, type, cache)) return false; ret += std::move(tmp); } return true; } - bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const + bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const { std::string extra = ToStringExtra(); size_t pos = extra.size() > 0 ? 1 : 0; @@ -547,17 +563,21 @@ public: for (const auto& pubkey : m_pubkey_args) { if (pos++) ret += ","; std::string tmp; - if (normalized) { - if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false; - } else if (priv) { - if (!pubkey->ToPrivateString(*arg, tmp)) return false; - } else { - tmp = pubkey->ToString(); + switch (type) { + case StringType::NORMALIZED: + if (!pubkey->ToNormalizedString(*arg, tmp, cache)) return false; + break; + case StringType::PRIVATE: + if (!pubkey->ToPrivateString(*arg, tmp)) return false; + break; + case StringType::PUBLIC: + tmp = pubkey->ToString(); + break; } ret += std::move(tmp); } std::string subscript; - if (!ToStringSubScriptHelper(arg, subscript, priv, normalized)) return false; + if (!ToStringSubScriptHelper(arg, subscript, type, cache)) return false; if (pos && subscript.size()) ret += ','; out = std::move(ret) + std::move(subscript) + ")"; return true; @@ -566,20 +586,20 @@ public: std::string ToString() const final { std::string ret; - ToStringHelper(nullptr, ret, false, false); + ToStringHelper(nullptr, ret, StringType::PUBLIC); return AddChecksum(ret); } bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { - bool ret = ToStringHelper(&arg, out, true, false); + bool ret = ToStringHelper(&arg, out, StringType::PRIVATE); out = AddChecksum(out); return ret; } - bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override final + bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override final { - bool ret = ToStringHelper(&arg, out, priv, true); + bool ret = ToStringHelper(&arg, out, StringType::NORMALIZED, cache); out = AddChecksum(out); return ret; } @@ -640,20 +660,6 @@ public: std::optional<OutputType> GetOutputType() const override { return std::nullopt; } }; -static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) { - if (std::holds_alternative<PKHash>(dest) || - std::holds_alternative<ScriptHash>(dest)) { - return OutputType::LEGACY; - } - if (std::holds_alternative<WitnessV0KeyHash>(dest) || - std::holds_alternative<WitnessV0ScriptHash>(dest) || - std::holds_alternative<WitnessV1Taproot>(dest) || - std::holds_alternative<WitnessUnknown>(dest)) { - return OutputType::BECH32; - } - return std::nullopt; -} - /** A parsed addr(A) descriptor. */ class AddressDescriptor final : public DescriptorImpl { @@ -843,9 +849,11 @@ protected: XOnlyPubKey xpk(keys[0]); if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); - return Vector(GetScriptForDestination(builder.GetOutput())); + WitnessV1Taproot output = builder.GetOutput(); + out.tr_spenddata[output].Merge(builder.GetSpendData()); + return Vector(GetScriptForDestination(output)); } - bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override + bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override { if (m_depths.empty()) return true; std::vector<bool> path; @@ -856,7 +864,7 @@ protected: path.push_back(false); } std::string tmp; - if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, priv, normalized)) return false; + if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, type, cache)) return false; ret += std::move(tmp); while (!path.empty() && path.back()) { if (path.size() > 1) ret += '}'; @@ -872,7 +880,7 @@ public: { assert(m_subdescriptor_args.size() == m_depths.size()); } - std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } bool IsSingleType() const final { return true; } }; @@ -929,7 +937,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S CPubKey pubkey(data); if (pubkey.IsFullyValid()) { if (permit_uncompressed || pubkey.IsCompressed()) { - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -950,7 +958,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S if (permit_uncompressed || key.IsCompressed()) { CPubKey pubkey = key.GetPubKey(); out.keys.emplace(pubkey.GetID(), key); - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -1219,7 +1227,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) { - std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); KeyOriginInfo info; if (provider.GetKeyOrigin(pubkey.GetID(), info)) { return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); @@ -1227,18 +1235,42 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo return key_provider; } +std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) +{ + unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; + std::copy(xkey.begin(), xkey.end(), full_key + 1); + CPubKey pubkey(full_key); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); + KeyOriginInfo info; + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } else { + full_key[0] = 0x03; + pubkey = CPubKey(full_key); + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } + } + return key_provider; +} + std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) { + if (ctx == ParseScriptContext::P2TR && script.size() == 34 && script[0] == 32 && script[33] == OP_CHECKSIG) { + XOnlyPubKey key{Span<const unsigned char>{script.data() + 1, script.data() + 33}}; + return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider)); + } + std::vector<std::vector<unsigned char>> data; TxoutType txntype = Solver(script, data); - if (txntype == TxoutType::PUBKEY) { + if (txntype == TxoutType::PUBKEY && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { CPubKey pubkey(data[0]); if (pubkey.IsValid()) { return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::PUBKEYHASH) { + if (txntype == TxoutType::PUBKEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { uint160 hash(data[0]); CKeyID keyid(hash); CPubKey pubkey; @@ -1246,7 +1278,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) { + if (txntype == TxoutType::WITNESS_V0_KEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH)) { uint160 hash(data[0]); CKeyID keyid(hash); CPubKey pubkey; @@ -1254,7 +1286,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo return std::make_unique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::MULTISIG) { + if (txntype == TxoutType::MULTISIG && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { std::vector<std::unique_ptr<PubkeyProvider>> providers; for (size_t i = 1; i + 1 < data.size(); ++i) { CPubKey pubkey(data[i]); @@ -1271,7 +1303,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (sub) return std::make_unique<SHDescriptor>(std::move(sub)); } } - if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) { + if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH)) { CScriptID scriptid; CRIPEMD160().Write(data[0].data(), data[0].size()).Finalize(scriptid.begin()); CScript subscript; @@ -1280,6 +1312,40 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (sub) return std::make_unique<WSHDescriptor>(std::move(sub)); } } + if (txntype == TxoutType::WITNESS_V1_TAPROOT && ctx == ParseScriptContext::TOP) { + // Extract x-only pubkey from output. + XOnlyPubKey pubkey; + std::copy(data[0].begin(), data[0].end(), pubkey.begin()); + // Request spending data. + TaprootSpendData tap; + if (provider.GetTaprootSpendData(pubkey, tap)) { + // If found, convert it back to tree form. + auto tree = InferTaprootTree(tap, pubkey); + if (tree) { + // If that works, try to infer subdescriptors for all leaves. + bool ok = true; + std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions + std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts) + for (const auto& [depth, script, leaf_ver] : *tree) { + std::unique_ptr<DescriptorImpl> subdesc; + if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) { + subdesc = InferScript(script, ParseScriptContext::P2TR, provider); + } + if (!subdesc) { + ok = false; + break; + } else { + subscripts.push_back(std::move(subdesc)); + depths.push_back(depth); + } + } + if (ok) { + auto key = InferXOnlyPubkey(tap.internal_key, ParseScriptContext::P2TR, provider); + return std::make_unique<TRDescriptor>(std::move(key), std::move(subscripts), std::move(depths)); + } + } + } + } CTxDestination dest; if (ExtractDestination(script, dest)) { @@ -1365,6 +1431,11 @@ void DescriptorCache::CacheDerivedExtPubKey(uint32_t key_exp_pos, uint32_t der_i xpubs[der_index] = xpub; } +void DescriptorCache::CacheLastHardenedExtPubKey(uint32_t key_exp_pos, const CExtPubKey& xpub) +{ + m_last_hardened_xpubs[key_exp_pos] = xpub; +} + bool DescriptorCache::GetCachedParentExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const { const auto& it = m_parent_xpubs.find(key_exp_pos); @@ -1383,6 +1454,55 @@ bool DescriptorCache::GetCachedDerivedExtPubKey(uint32_t key_exp_pos, uint32_t d return true; } +bool DescriptorCache::GetCachedLastHardenedExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const +{ + const auto& it = m_last_hardened_xpubs.find(key_exp_pos); + if (it == m_last_hardened_xpubs.end()) return false; + xpub = it->second; + return true; +} + +DescriptorCache DescriptorCache::MergeAndDiff(const DescriptorCache& other) +{ + DescriptorCache diff; + for (const auto& parent_xpub_pair : other.GetCachedParentExtPubKeys()) { + CExtPubKey xpub; + if (GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) { + if (xpub != parent_xpub_pair.second) { + throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub"); + } + continue; + } + CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second); + diff.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second); + } + for (const auto& derived_xpub_map_pair : other.GetCachedDerivedExtPubKeys()) { + for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) { + CExtPubKey xpub; + if (GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) { + if (xpub != derived_xpub_pair.second) { + throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub"); + } + continue; + } + CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second); + diff.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second); + } + } + for (const auto& lh_xpub_pair : other.GetCachedLastHardenedExtPubKeys()) { + CExtPubKey xpub; + if (GetCachedLastHardenedExtPubKey(lh_xpub_pair.first, xpub)) { + if (xpub != lh_xpub_pair.second) { + throw std::runtime_error(std::string(__func__) + ": New cached last hardened xpub does not match already cached last hardened xpub"); + } + continue; + } + CacheLastHardenedExtPubKey(lh_xpub_pair.first, lh_xpub_pair.second); + diff.CacheLastHardenedExtPubKey(lh_xpub_pair.first, lh_xpub_pair.second); + } + return diff; +} + const ExtPubKeyMap DescriptorCache::GetCachedParentExtPubKeys() const { return m_parent_xpubs; @@ -1392,3 +1512,8 @@ const std::unordered_map<uint32_t, ExtPubKeyMap> DescriptorCache::GetCachedDeriv { return m_derived_xpubs; } + +const ExtPubKeyMap DescriptorCache::GetCachedLastHardenedExtPubKeys() const +{ + return m_last_hardened_xpubs; +} diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 332ae2f230..ecd7c4eea5 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -22,6 +22,8 @@ private: std::unordered_map<uint32_t, ExtPubKeyMap> m_derived_xpubs; /** Map key expression index -> parent xpub */ ExtPubKeyMap m_parent_xpubs; + /** Map key expression index -> last hardened xpub */ + ExtPubKeyMap m_last_hardened_xpubs; public: /** Cache a parent xpub @@ -50,11 +52,30 @@ public: * @param[in] xpub The CExtPubKey to get from cache */ bool GetCachedDerivedExtPubKey(uint32_t key_exp_pos, uint32_t der_index, CExtPubKey& xpub) const; + /** Cache a last hardened xpub + * + * @param[in] key_exp_pos Position of the key expression within the descriptor + * @param[in] xpub The CExtPubKey to cache + */ + void CacheLastHardenedExtPubKey(uint32_t key_exp_pos, const CExtPubKey& xpub); + /** Retrieve a cached last hardened xpub + * + * @param[in] key_exp_pos Position of the key expression within the descriptor + * @param[in] xpub The CExtPubKey to get from cache + */ + bool GetCachedLastHardenedExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const; /** Retrieve all cached parent xpubs */ const ExtPubKeyMap GetCachedParentExtPubKeys() const; /** Retrieve all cached derived xpubs */ const std::unordered_map<uint32_t, ExtPubKeyMap> GetCachedDerivedExtPubKeys() const; + /** Retrieve all cached last hardened xpubs */ + const ExtPubKeyMap GetCachedLastHardenedExtPubKeys() const; + + /** Combine another DescriptorCache into this one. + * Returns a cache containing the items from the other cache unknown to current cache + */ + DescriptorCache MergeAndDiff(const DescriptorCache& other); }; /** \brief Interface for parsed descriptor objects. @@ -94,7 +115,7 @@ struct Descriptor { virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0; /** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */ - virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, bool priv) const = 0; + virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const = 0; /** Expand a descriptor at a specified position. * diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 3c3c3ac1a8..ef48f89965 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1420,7 +1420,7 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent) } // namespace template <class T> -void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs) +void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force) { assert(!m_spent_outputs_ready); @@ -1431,9 +1431,9 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent } // Determine which precomputation-impacting features this transaction uses. - bool uses_bip143_segwit = false; - bool uses_bip341_taproot = false; - for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) { + bool uses_bip143_segwit = force; + bool uses_bip341_taproot = force; + for (size_t inpos = 0; inpos < txTo.vin.size() && !(uses_bip143_segwit && uses_bip341_taproot); ++inpos) { if (!txTo.vin[inpos].scriptWitness.IsNull()) { if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE && m_spent_outputs[inpos].scriptPubKey[0] == OP_1) { @@ -1478,8 +1478,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs); -template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs); +template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); +template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); @@ -1711,7 +1711,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } uint256 sighash; - assert(this->txdata); + if (!this->txdata) return HandleMissingData(m_mdb); if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) { return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } @@ -1740,9 +1740,9 @@ bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLoc if (nLockTime > (int64_t)txTo->nLockTime) return false; - // Finally the nLockTime feature can be disabled and thus - // CHECKLOCKTIMEVERIFY bypassed if every txin has been - // finalized by setting nSequence to maxint. The + // Finally the nLockTime feature can be disabled in IsFinalTx() + // and thus CHECKLOCKTIMEVERIFY bypassed if every txin has + // been finalized by setting nSequence to maxint. The // transaction would be allowed into the blockchain, making // the opcode ineffective. // @@ -1847,16 +1847,14 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS return true; } -static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash) +uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script) +{ + return (CHashWriter(HASHER_TAPLEAF) << leaf_version << script).GetSHA256(); +} + +uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash) { const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; - //! The internal pubkey (x-only, so no Y coordinate parity). - const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; - //! The output pubkey (taken from the scriptPubKey). - const XOnlyPubKey q{uint256(program)}; - // Compute the tapleaf hash. - tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256(); - // Compute the Merkle root from the leaf and the provided path. uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { CHashWriter ss_branch{HASHER_TAPBRANCH}; @@ -1868,8 +1866,21 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c } k = ss_branch.GetSHA256(); } + return k; +} + +static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash) +{ + assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE); + assert(program.size() >= uint256::size()); + //! The internal pubkey (x-only, so no Y coordinate parity). + const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; + //! The output pubkey (taken from the scriptPubKey). + const XOnlyPubKey q{uint256(program)}; + // Compute the Merkle root from the leaf and the provided path. + const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash); // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. - return q.CheckTapTweak(p, k, control[0] & 1); + return q.CheckTapTweak(p, merkle_root, control[0] & 1); } static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh) @@ -1929,7 +1940,8 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) { return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); } - if (!VerifyTaprootCommitment(control, program, exec_script, execdata.m_tapleaf_hash)) { + execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, exec_script); + if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } execdata.m_tapleaf_hash_init = true; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index fa4ee83e04..034c937b99 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -168,7 +168,7 @@ struct PrecomputedTransactionData PrecomputedTransactionData() = default; template <class T> - void Init(const T& tx, std::vector<CTxOut>&& spent_outputs); + void Init(const T& tx, std::vector<CTxOut>&& spent_outputs, bool force = false); template <class T> explicit PrecomputedTransactionData(const T& tx); @@ -260,6 +260,9 @@ enum class MissingDataBehavior FAIL, //!< Just act as if the signature was invalid }; +template<typename T> +bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb); + template <class T> class GenericTransactionSignatureChecker : public BaseSignatureChecker { @@ -314,6 +317,12 @@ public: } }; +/** Compute the BIP341 tapleaf hash from leaf version & script. */ +uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script); +/** Compute the BIP341 taproot script tree Merkle root from control block and leaf hash. + * Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */ +uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash); + bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr); bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr); bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index da0092f9e3..65276f641f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -11,13 +11,28 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> +#include <util/vector.h> typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL), + m_txdata(nullptr) +{ +} + +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), + checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) : + MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)), + m_txdata(txdata) +{ +} bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0); + CKey key; if (!provider.GetKey(address, key)) return false; @@ -26,13 +41,61 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed()) return false; - // Signing for witness scripts needs the amount. - if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false; + // Signing without known amount does not work in witness scripts. + if (sigversion == SigVersion::WITNESS_V0 && !MoneyRange(amount)) return false; + + // BASE/WITNESS_V0 signatures don't support explicit SIGHASH_DEFAULT, use SIGHASH_ALL instead. + const int hashtype = nHashType == SIGHASH_DEFAULT ? SIGHASH_ALL : nHashType; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion); + uint256 hash = SignatureHash(scriptCode, *txTo, nIn, hashtype, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; - vchSig.push_back((unsigned char)nHashType); + vchSig.push_back((unsigned char)hashtype); + return true; +} + +bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const +{ + assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT); + + CKey key; + { + // For now, use the old full pubkey-based key derivation logic. As it indexed by + // Hash160(full pubkey), we need to try both a version prefixed with 0x02, and one + // with 0x03. + unsigned char b[33] = {0x02}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) { + b[0] = 0x03; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) return false; + } + } + + // BIP341/BIP342 signing needs lots of precomputed transaction data. While some + // (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset + // of data present, for now, only support signing when everything is provided. + if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false; + + ScriptExecutionData execdata; + execdata.m_annex_init = true; + execdata.m_annex_present = false; // Only support annex-less signing for now. + if (sigversion == SigVersion::TAPSCRIPT) { + execdata.m_codeseparator_pos_init = true; + execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now. + if (!leaf_hash) return false; // BIP342 signing needs leaf hash. + execdata.m_tapleaf_hash_init = true; + execdata.m_tapleaf_hash = *leaf_hash; + } + uint256 hash; + if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; + sig.resize(64); + if (!key.SignSchnorr(hash, sig, merkle_root, nullptr)) return false; + if (nHashType) sig.push_back(nHashType); return true; } @@ -92,6 +155,86 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat return false; } +static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion) +{ + auto lookup_key = std::make_pair(pubkey, leaf_hash); + auto it = sigdata.taproot_script_sigs.find(lookup_key); + if (it != sigdata.taproot_script_sigs.end()) { + sig_out = it->second; + } + if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { + sigdata.taproot_script_sigs[lookup_key] = sig_out; + return true; + } + return false; +} + +static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result) +{ + // Only BIP342 tapscript signing is supported for now. + if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; + SigVersion sigversion = SigVersion::TAPSCRIPT; + + uint256 leaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(leaf_version) << script).GetSHA256(); + + // <xonly pubkey> OP_CHECKSIG + if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { + XOnlyPubKey pubkey(MakeSpan(script).subspan(1, 32)); + std::vector<unsigned char> sig; + if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { + result = Vector(std::move(sig)); + return true; + } + } + + return false; +} + +static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) +{ + TaprootSpendData spenddata; + + // Gather information about this output. + if (provider.GetTaprootSpendData(output, spenddata)) { + sigdata.tr_spenddata.Merge(spenddata); + } + + // Try key path spending. + { + std::vector<unsigned char> sig; + if (sigdata.taproot_key_path_sig.size() == 0) { + if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + sigdata.taproot_key_path_sig = sig; + } + } + if (sigdata.taproot_key_path_sig.size()) { + result = Vector(sigdata.taproot_key_path_sig); + return true; + } + } + + // Try script path spending. + std::vector<std::vector<unsigned char>> smallest_result_stack; + for (const auto& [key, control_blocks] : sigdata.tr_spenddata.scripts) { + const auto& [script, leaf_ver] = key; + std::vector<std::vector<unsigned char>> result_stack; + if (SignTaprootScript(provider, creator, sigdata, leaf_ver, script, result_stack)) { + result_stack.emplace_back(std::begin(script), std::end(script)); // Push the script + result_stack.push_back(*control_blocks.begin()); // Push the smallest control block + if (smallest_result_stack.size() == 0 || + GetSerializeSize(result_stack, PROTOCOL_VERSION) < GetSerializeSize(smallest_result_stack, PROTOCOL_VERSION)) { + smallest_result_stack = std::move(result_stack); + } + } + } + if (smallest_result_stack.size() != 0) { + result = std::move(smallest_result_stack); + return true; + } + + return false; +} + /** * Sign scriptPubKey using signature made with creator. * Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed), @@ -113,7 +256,6 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TxoutType::NONSTANDARD: case TxoutType::NULL_DATA: case TxoutType::WITNESS_UNKNOWN: - case TxoutType::WITNESS_V1_TAPROOT: return false; case TxoutType::PUBKEY: if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false; @@ -175,6 +317,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator // Could not find witnessScript, add to missing sigdata.missing_witness_script = uint256(vSolutions[0]); return false; + + case TxoutType::WITNESS_V1_TAPROOT: + return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret); } // no default case, so the compiler can warn about missing cases assert(false); } @@ -205,7 +350,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); bool P2SH = false; CScript subscript; - sigdata.scriptWitness.stack.clear(); if (solved && whichType == TxoutType::SCRIPTHASH) { @@ -238,10 +382,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptWitness.stack = result; sigdata.witness = true; result.clear(); + } else if (whichType == TxoutType::WITNESS_V1_TAPROOT && !P2SH) { + sigdata.witness = true; + if (solved) { + sigdata.scriptWitness.stack = std::move(result); + } + result.clear(); } else if (solved && whichType == TxoutType::WITNESS_UNKNOWN) { sigdata.witness = true; } + if (!sigdata.witness) sigdata.scriptWitness.stack.clear(); if (P2SH) { result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end())); } @@ -402,6 +553,7 @@ class DummySignatureChecker final : public BaseSignatureChecker public: DummySignatureChecker() {} bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } + bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const override { return true; } }; const DummySignatureChecker DUMMY_CHECKER; @@ -427,6 +579,11 @@ public: vchSig[6 + m_r_len + m_s_len] = SIGHASH_ALL; return true; } + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* tweak, SigVersion sigversion) const override + { + sig.assign(64, '\000'); + return true; + } }; } @@ -476,6 +633,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, // Use CTransaction for the constant parts of the // transaction to avoid rehashing. const CTransaction txConst(mtx); + + PrecomputedTransactionData txdata; + std::vector<CTxOut> spent_outputs; + spent_outputs.resize(mtx.vin.size()); + bool have_all_spent_outputs = true; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + have_all_spent_outputs = false; + } else { + spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey); + } + } + if (have_all_spent_outputs) { + txdata.Init(txConst, std::move(spent_outputs), true); + } else { + txdata.Init(txConst, {}, true); + } + // Sign what we can: for (unsigned int i = 0; i < mtx.vin.size(); i++) { CTxIn& txin = mtx.vin[i]; @@ -490,7 +667,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); @@ -502,7 +679,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, } ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)"; diff --git a/src/script/sign.h b/src/script/sign.h index a1cfe1574d..b4e7318892 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -11,13 +11,13 @@ #include <pubkey.h> #include <script/interpreter.h> #include <script/keyorigin.h> +#include <script/standard.h> #include <span.h> #include <streams.h> class CKey; class CKeyID; class CScript; -class CScriptID; class CTransaction; class SigningProvider; @@ -31,6 +31,7 @@ public: /** Create a singular (non-script) signature. */ virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0; + virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0; }; /** A signature creator for transactions. */ @@ -40,11 +41,14 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { int nHashType; CAmount amount; const MutableTransactionSignatureChecker checker; + const PrecomputedTransactionData* m_txdata; public: MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override; }; /** A signature creator that just produces 71-byte empty signatures. */ @@ -64,8 +68,11 @@ struct SignatureData { CScript redeem_script; ///< The redeemScript (if any) for the input CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. + TaprootSpendData tr_spenddata; ///< Taproot spending data. std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; + std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending + std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 9781ec32af..b80fbe22ce 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -44,6 +44,11 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf return m_provider->GetKeyOrigin(keyid, info); } +bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return m_provider->GetTaprootSpendData(output_key, spenddata); +} + bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const @@ -54,6 +59,10 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) return ret; } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } +bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return LookupHelper(tr_spenddata, output_key, spenddata); +} FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) { @@ -66,6 +75,10 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide ret.keys.insert(b.keys.begin(), b.keys.end()); ret.origins = a.origins; ret.origins.insert(b.origins.begin(), b.origins.end()); + ret.tr_spenddata = a.tr_spenddata; + for (const auto& [output_key, spenddata] : b.tr_spenddata) { + ret.tr_spenddata[output_key].Merge(spenddata); + } return ret; } diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 76f31d2f6f..939ae10622 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -25,6 +25,7 @@ public: virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } virtual bool HaveKey(const CKeyID &address) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } + virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } }; extern const SigningProvider& DUMMY_SIGNING_PROVIDER; @@ -42,6 +43,7 @@ public: bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -50,11 +52,13 @@ struct FlatSigningProvider final : public SigningProvider std::map<CKeyID, CPubKey> pubkeys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, CKey> keys; + std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */ bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index a4b11cc0a9..b8349bb9ab 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -377,6 +377,16 @@ bool IsValidDestination(const CTxDestination& dest) { /*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b) { NodeInfo ret; + /* Iterate over all tracked leaves in a, add b's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : a.leaves) { + leaf.merkle_branch.push_back(b.hash); + ret.leaves.emplace_back(std::move(leaf)); + } + /* Iterate over all tracked leaves in b, add a's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : b.leaves) { + leaf.merkle_branch.push_back(a.hash); + ret.leaves.emplace_back(std::move(leaf)); + } /* Lexicographically sort a and b's hash, and compute parent hash. */ if (a.hash < b.hash) { ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256(); @@ -386,6 +396,27 @@ bool IsValidDestination(const CTxDestination& dest) { return ret; } +void TaprootSpendData::Merge(TaprootSpendData other) +{ + // TODO: figure out how to better deal with conflicting information + // being merged. + if (internal_key.IsNull() && !other.internal_key.IsNull()) { + internal_key = other.internal_key; + } + if (merkle_root.IsNull() && !other.merkle_root.IsNull()) { + merkle_root = other.merkle_root; + } + for (auto& [key, control_blocks] : other.scripts) { + // Once P0083R3 is supported by all our targeted platforms, + // this loop body can be replaced with: + // scripts[key].merge(std::move(control_blocks)); + auto& target = scripts[key]; + for (auto& control_block: control_blocks) { + target.insert(std::move(control_block)); + } + } +} + void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) { assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT); @@ -435,13 +466,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) return branch.size() == 0 || (branch.size() == 1 && branch[0]); } -TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version) +TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track) { assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0); if (!IsValid()) return *this; - /* Construct NodeInfo object with leaf hash. */ + /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); + if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); return *this; @@ -464,8 +496,168 @@ TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key) m_internal_key = internal_key; auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash); assert(ret.has_value()); - std::tie(m_output_key, std::ignore) = *ret; + std::tie(m_output_key, m_parity) = *ret; return *this; } WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; } + +TaprootSpendData TaprootBuilder::GetSpendData() const +{ + TaprootSpendData spd; + spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; + spd.internal_key = m_internal_key; + if (m_branch.size()) { + // If any script paths exist, they have been combined into the root m_branch[0] + // by now. Compute the control block for each of its tracked leaves, and put them in + // spd.scripts. + for (const auto& leaf : m_branch[0]->leaves) { + std::vector<unsigned char> control_block; + control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size()); + control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0); + std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1); + if (leaf.merkle_branch.size()) { + std::copy(leaf.merkle_branch[0].begin(), + leaf.merkle_branch[0].begin() + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size(), + control_block.begin() + TAPROOT_CONTROL_BASE_SIZE); + } + spd.scripts[{leaf.script, leaf.leaf_version}].insert(std::move(control_block)); + } + } + return spd; +} + +std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output) +{ + // Verify that the output matches the assumed Merkle root and internal key. + auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root); + if (!tweak || tweak->first != output) return std::nullopt; + // If the Merkle root is 0, the tree is empty, and we're done. + std::vector<std::tuple<int, CScript, int>> ret; + if (spenddata.merkle_root.IsNull()) return ret; + + /** Data structure to represent the nodes of the tree we're going to build. */ + struct TreeNode { + /** Hash of this node, if known; 0 otherwise. */ + uint256 hash; + /** The left and right subtrees (note that their order is irrelevant). */ + std::unique_ptr<TreeNode> sub[2]; + /** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair. + * nullptr otherwise. */ + const std::pair<CScript, int>* leaf = nullptr; + /** Whether or not this node has been explored (is known to be a leaf, or known to have children). */ + bool explored = false; + /** Whether or not this node is an inner node (unknown until explored = true). */ + bool inner; + /** Whether or not we have produced output for this subtree. */ + bool done = false; + }; + + // Build tree from the provided branches. + TreeNode root; + root.hash = spenddata.merkle_root; + for (const auto& [key, control_blocks] : spenddata.scripts) { + const auto& [script, leaf_ver] = key; + for (const auto& control : control_blocks) { + // Skip script records with nonsensical leaf version. + if (leaf_ver < 0 || leaf_ver >= 0x100 || leaf_ver & 1) continue; + // Skip script records with invalid control block sizes. + if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || + ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) continue; + // Skip script records that don't match the control block. + if ((control[0] & TAPROOT_LEAF_MASK) != leaf_ver) continue; + // Skip script records that don't match the provided Merkle root. + const uint256 leaf_hash = ComputeTapleafHash(leaf_ver, script); + const uint256 merkle_root = ComputeTaprootMerkleRoot(control, leaf_hash); + if (merkle_root != spenddata.merkle_root) continue; + + TreeNode* node = &root; + size_t levels = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; + for (size_t depth = 0; depth < levels; ++depth) { + // Can't descend into a node which we already know is a leaf. + if (node->explored && !node->inner) return std::nullopt; + + // Extract partner hash from Merkle branch in control block. + uint256 hash; + std::copy(control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - 1 - depth) * TAPROOT_CONTROL_NODE_SIZE, + control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - depth) * TAPROOT_CONTROL_NODE_SIZE, + hash.begin()); + + if (node->sub[0]) { + // Descend into the existing left or right branch. + bool desc = false; + for (int i = 0; i < 2; ++i) { + if (node->sub[i]->hash == hash || (node->sub[i]->hash.IsNull() && node->sub[1-i]->hash != hash)) { + node->sub[i]->hash = hash; + node = &*node->sub[1-i]; + desc = true; + break; + } + } + if (!desc) return std::nullopt; // This probably requires a hash collision to hit. + } else { + // We're in an unexplored node. Create subtrees and descend. + node->explored = true; + node->inner = true; + node->sub[0] = std::make_unique<TreeNode>(); + node->sub[1] = std::make_unique<TreeNode>(); + node->sub[1]->hash = hash; + node = &*node->sub[0]; + } + } + // Cannot turn a known inner node into a leaf. + if (node->sub[0]) return std::nullopt; + node->explored = true; + node->inner = false; + node->leaf = &key; + node->hash = leaf_hash; + } + } + + // Recursive processing to turn the tree into flattened output. Use an explicit stack here to avoid + // overflowing the call stack (the tree may be 128 levels deep). + std::vector<TreeNode*> stack{&root}; + while (!stack.empty()) { + TreeNode& node = *stack.back(); + if (!node.explored) { + // Unexplored node, which means the tree is incomplete. + return std::nullopt; + } else if (!node.inner) { + // Leaf node; produce output. + ret.emplace_back(stack.size() - 1, node.leaf->first, node.leaf->second); + node.done = true; + stack.pop_back(); + } else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() && + (CHashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) { + // Whenever there are nodes with two identical subtrees under it, we run into a problem: + // the control blocks for the leaves underneath those will be identical as well, and thus + // they will all be matched to the same path in the tree. The result is that at the location + // where the duplicate occurred, the left child will contain a normal tree that can be explored + // and processed, but the right one will remain unexplored. + // + // This situation can be detected, by encountering an inner node with unexplored right subtree + // with known hash, and H_TapBranch(hash, hash) is equal to the parent node (this node)'s hash. + // + // To deal with this, simply process the left tree a second time (set its done flag to false; + // noting that the done flag of its children have already been set to false after processing + // those). To avoid ending up in an infinite loop, set the done flag of the right (unexplored) + // subtree to true. + node.sub[0]->done = false; + node.sub[1]->done = true; + } else if (node.sub[0]->done && node.sub[1]->done) { + // An internal node which we're finished with. + node.sub[0]->done = false; + node.sub[1]->done = false; + node.done = true; + stack.pop_back(); + } else if (!node.sub[0]->done) { + // An internal node whose left branch hasn't been processed yet. Do so first. + stack.push_back(&*node.sub[0]); + } else if (!node.sub[1]->done) { + // An internal node whose right branch hasn't been processed yet. Do so first. + stack.push_back(&*node.sub[1]); + } + } + + return ret; +} diff --git a/src/script/standard.h b/src/script/standard.h index d7ea5cef27..ac4e2f3276 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -11,6 +11,7 @@ #include <uint256.h> #include <util/hash_type.h> +#include <map> #include <string> #include <variant> @@ -209,15 +210,50 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey); /** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); +struct ShortestVectorFirstComparator +{ + bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const + { + if (a.size() < b.size()) return true; + if (a.size() > b.size()) return false; + return a < b; + } +}; + +struct TaprootSpendData +{ + /** The BIP341 internal key. */ + XOnlyPubKey internal_key; + /** The Merkle root of the script tree (0 if no scripts). */ + uint256 merkle_root; + /** Map from (script, leaf_version) to (sets of) control blocks. + * The control blocks are sorted by size, so that the signing logic can + * easily prefer the cheapest one. */ + std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts; + /** Merge other TaprootSpendData (for the same scriptPubKey) into this. */ + void Merge(TaprootSpendData other); +}; + /** Utility class to construct Taproot outputs from internal key and script tree. */ class TaprootBuilder { private: + /** Information about a tracked leaf in the Merkle tree. */ + struct LeafInfo + { + CScript script; //!< The script. + int leaf_version; //!< The leaf version for that script. + std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf. + }; + /** Information associated with a node in the Merkle tree. */ struct NodeInfo { /** Merkle hash of this node. */ uint256 hash; + /** Tracked leaves underneath this node (either from the node itself, or its children). + * The merkle_branch field for each is the partners to get to *this* node. */ + std::vector<LeafInfo> leaves; }; /** Whether the builder is in a valid state so far. */ bool m_valid = true; @@ -260,7 +296,8 @@ private: std::vector<std::optional<NodeInfo>> m_branch; XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing. - XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */ + XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. + bool m_parity; //!< The tweak parity, computed when finalizing. /** Combine information about a parent Merkle tree node from its child nodes. */ static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b); @@ -269,8 +306,9 @@ private: public: /** Add a new script at a certain depth in the tree. Add() operations must be called - * in depth-first traversal order of binary tree. */ - TaprootBuilder& Add(int depth, const CScript& script, int leaf_version); + * in depth-first traversal order of binary tree. If track is true, it will be included in + * the GetSpendData() output. */ + TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true); /** Like Add(), but for a Merkle node with a given hash to the tree. */ TaprootBuilder& AddOmitted(int depth, const uint256& hash); /** Finalize the construction. Can only be called when IsComplete() is true. @@ -285,6 +323,16 @@ public: WitnessV1Taproot GetOutput(); /** Check if a list of depths is legal (will lead to IsComplete()). */ static bool ValidDepths(const std::vector<int>& depths); + /** Compute spending data (after Finalize()). */ + TaprootSpendData GetSpendData() const; }; +/** Given a TaprootSpendData and the output key, reconstruct its script tree. + * + * If the output doesn't match the spenddata, or if the data in spenddata is incomplete, + * std::nullopt is returned. Otherwise, a vector of (depth, script, leaf_ver) tuples is + * returned, corresponding to a depth-first traversal of the script tree. + */ +std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output); + #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 49b40924e0..f2eed956cd 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <addrman.h> +#include <i2p.h> #include <test/data/asmap.raw.h> #include <test/util/setup_common.h> #include <util/asmap.h> @@ -74,9 +75,9 @@ public: // Simulates connection failure so that we can test eviction of offline nodes void SimConnFail(const CService& addr) { - LOCK(cs); int64_t nLastSuccess = 1; - Good_(addr, true, nLastSuccess); // Set last good connection in the deep past. + // Set last good connection in the deep past. + Good(addr, true, nLastSuccess); bool count_failure = false; int64_t nLastTry = GetAdjustedTime()-61; @@ -783,6 +784,46 @@ BOOST_AUTO_TEST_CASE(addrman_serialization) BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second); } +BOOST_AUTO_TEST_CASE(remove_invalid) +{ + // Confirm that invalid addresses are ignored in unserialization. + + CAddrManTest addrman; + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + const CAddress new1{ResolveService("5.5.5.5"), NODE_NONE}; + const CAddress new2{ResolveService("6.6.6.6"), NODE_NONE}; + const CAddress tried1{ResolveService("7.7.7.7"), NODE_NONE}; + const CAddress tried2{ResolveService("8.8.8.8"), NODE_NONE}; + + addrman.Add({new1, tried1, new2, tried2}, CNetAddr{}); + addrman.Good(tried1); + addrman.Good(tried2); + BOOST_REQUIRE_EQUAL(addrman.size(), 4); + + stream << addrman; + + const std::string str{stream.str()}; + size_t pos; + + const char new2_raw[]{6, 6, 6, 6}; + const uint8_t new2_raw_replacement[]{0, 0, 0, 0}; // 0.0.0.0 is !IsValid() + pos = str.find(new2_raw, 0, sizeof(new2_raw)); + BOOST_REQUIRE(pos != std::string::npos); + BOOST_REQUIRE(pos + sizeof(new2_raw_replacement) <= stream.size()); + memcpy(stream.data() + pos, new2_raw_replacement, sizeof(new2_raw_replacement)); + + const char tried2_raw[]{8, 8, 8, 8}; + const uint8_t tried2_raw_replacement[]{255, 255, 255, 255}; // 255.255.255.255 is !IsValid() + pos = str.find(tried2_raw, 0, sizeof(tried2_raw)); + BOOST_REQUIRE(pos != std::string::npos); + BOOST_REQUIRE(pos + sizeof(tried2_raw_replacement) <= stream.size()); + memcpy(stream.data() + pos, tried2_raw_replacement, sizeof(tried2_raw_replacement)); + + addrman.Clear(); + stream >> addrman; + BOOST_CHECK_EQUAL(addrman.size(), 2); +} BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { @@ -926,5 +967,121 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } +BOOST_AUTO_TEST_CASE(reset_i2p_ports) +{ + CAddrManTest addrman1; + CAddrManTest addrman2; + const uint32_t good_time{static_cast<uint32_t>(GetAdjustedTime())}; + constexpr uint16_t port = 8333; + + // Has its port changed, will be re-positioned within the same bucket in vvNew. + const CAddress i2p_new1{ + ResolveService("72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p", port), + NODE_NONE, + good_time}; + + // Has its port changed, will not be re-positioned in vvNew because ports 0 and 10075 result in + // the same bucket position. + const CAddress i2p_new2{ + ResolveService("gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p", 10075), + NODE_NONE, + good_time}; + + // Remains unchanged, port is already as it should be. + const CAddress i2p_new3{ + ResolveService("c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p", + I2P_SAM31_PORT), + NODE_NONE, + good_time}; + + // Has its port changed, re-positioning in vvNew will cause i2p_new3 to be evicted. + const CAddress i2p_new4{ + ResolveService("c4cbbkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p", port), + NODE_NONE, + good_time}; + + // Remains unchanged. + const CAddress ipv4_new{ResolveService("1.2.3.4", port), NODE_NONE, good_time}; + + // Has its port changed, will be re-positioned in vvTried. + const CAddress i2p_tried1{ + ResolveService("h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p", port), + NODE_NONE, + good_time}; + + // Has its port changed, will not be re-positioned in vvTried because ports 0 and 10537 + // result in the same position (bucket, i). + const CAddress i2p_tried2{ + ResolveService("pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p", 10537), + NODE_NONE, + good_time}; + + // Remains unchanged, port is already as it should be. + const CAddress i2p_tried3{ + ResolveService("hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p", + I2P_SAM31_PORT), + NODE_NONE, + good_time}; + + // Has its port changed, re-positioning in vvTried will cause i2p_tried3 to be moved to vvNew. + const CAddress i2p_tried4{ + ResolveService("hna37nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p", port), + NODE_NONE, + good_time}; + + // Remains unchanged. + const CAddress ipv4_tried{ResolveService("2.3.4.5", port), NODE_NONE, good_time}; + + const CNetAddr source; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + addrman1.Add(i2p_new1, source); + addrman1.Add(i2p_new2, source); + addrman1.Add(i2p_new3, source); + addrman1.Add(i2p_new4, source); + addrman1.Add(ipv4_new, source); + + addrman1.Add(i2p_tried1, source); + addrman1.Good(i2p_tried1); + addrman1.Add(i2p_tried2, source); + addrman1.Good(i2p_tried2); + addrman1.Add(i2p_tried3, source); + addrman1.Good(i2p_tried3); + addrman1.Add(i2p_tried4, source); + addrman1.Good(i2p_tried4); + addrman1.Add(ipv4_tried, source); + addrman1.Good(ipv4_tried); + + stream << addrman1; + stream >> addrman2; + + const size_t max_addresses{0}; + const size_t max_pct{0}; + + auto addresses = addrman2.GetAddr(max_addresses, max_pct, NET_I2P); + BOOST_REQUIRE_EQUAL(addresses.size(), 7UL); + std::sort(addresses.begin(), addresses.end()); // Just some deterministic order. + BOOST_CHECK_EQUAL(addresses[0].ToStringIP(), i2p_new4.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[0].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[1].ToStringIP(), i2p_new2.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[1].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[2].ToStringIP(), i2p_tried4.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[2].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[3].ToStringIP(), i2p_tried3.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[3].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[4].ToStringIP(), i2p_tried1.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[4].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[5].ToStringIP(), i2p_tried2.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[5].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[6].ToStringIP(), i2p_new1.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[6].GetPort(), I2P_SAM31_PORT); + + addresses = addrman2.GetAddr(max_addresses, max_pct, NET_IPV4); + BOOST_REQUIRE_EQUAL(addresses.size(), 2UL); + std::sort(addresses.begin(), addresses.end()); // Just some deterministic order. + BOOST_CHECK_EQUAL(addresses[0].ToStringIPPort(), ipv4_new.ToStringIPPort()); + BOOST_CHECK_EQUAL(addresses[1].ToStringIPPort(), ipv4_tried.ToStringIPPort()); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 2f532ef598..2eb653e9ec 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -62,7 +62,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const CScript& scriptPubKey) { const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(::ChainstateActive(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; @@ -117,9 +117,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) std::vector<BlockFilter> filters; std::vector<uint256> filter_hashes; - for (const CBlockIndex* block_index = ::ChainActive().Genesis(); + for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis(); block_index != nullptr; - block_index = ::ChainActive().Next(block_index)) { + block_index = m_node.chainman->ActiveChain().Next(block_index)) { BOOST_CHECK(!filter_index.LookupFilter(block_index, filter)); BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header)); BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); @@ -131,7 +131,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // BlockUntilSyncedToCurrentChain should return false before index is started. BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(filter_index.Start(::ChainstateActive())); + BOOST_REQUIRE(filter_index.Start(m_node.chainman->ActiveChainstate())); // Allow filter index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; @@ -145,9 +145,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); const CBlockIndex* block_index; - for (block_index = ::ChainActive().Genesis(); + for (block_index = m_node.chainman->ActiveChain().Genesis(); block_index != nullptr; - block_index = ::ChainActive().Next(block_index)) { + block_index = m_node.chainman->ActiveChain().Next(block_index)) { CheckFilterLookups(filter_index, block_index, last_header); } } @@ -156,7 +156,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* tip; { LOCK(cs_main); - tip = ::ChainActive().Tip(); + tip = m_node.chainman->ActiveChain().Tip(); } CKey coinbase_key_A, coinbase_key_B; coinbase_key_A.MakeNewKey(true); @@ -178,7 +178,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -196,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -210,7 +210,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -231,14 +231,14 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(chainA[i]->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainA[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainA_last_header); { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(chainB[i]->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainB[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainB_last_header); @@ -250,7 +250,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); - tip = ::ChainActive().Tip(); + tip = m_node.chainman->ActiveChain().Tip(); } BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters)); BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes)); diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 106fcd2a33..597d7a7340 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -22,7 +22,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = ChainActive().Tip(); + block_index = m_node.chainman->ActiveChain().Tip(); } // CoinStatsIndex should not be found before it is started. @@ -32,7 +32,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // is started. BOOST_CHECK(!coin_stats_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(coin_stats_index.Start(::ChainstateActive())); + BOOST_REQUIRE(coin_stats_index.Start(m_node.chainman->ActiveChainstate())); // Allow the CoinStatsIndex to catch up with the block index that is syncing // in a background thread. @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* genesis_block_index; { LOCK(cs_main); - genesis_block_index = ChainActive().Genesis(); + genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index, coin_stats)); @@ -64,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* new_block_index; { LOCK(cs_main); - new_block_index = ChainActive().Tip(); + new_block_index = m_node.chainman->ActiveChain().Tip(); } coin_stats_index.LookUpStats(new_block_index, new_coin_stats); diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index a56ce51acb..5668ead1fb 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -14,36 +14,19 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <serialize.h> +#include <test/util/net.h> +#include <test/util/setup_common.h> #include <txorphanage.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> #include <validation.h> -#include <test/util/setup_common.h> - #include <array> #include <stdint.h> #include <boost/test/unit_test.hpp> -struct CConnmanTest : public CConnman { - using CConnman::CConnman; - void AddNode(CNode& node) - { - LOCK(cs_vNodes); - vNodes.push_back(&node); - } - void ClearNodes() - { - LOCK(cs_vNodes); - for (CNode* node : vNodes) { - delete node; - } - vNodes.clear(); - } -}; - static CService ip(uint32_t i) { struct in_addr s; @@ -83,8 +66,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) // This test requires that we have a chain with non-zero work. { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip() != nullptr); - BOOST_CHECK(::ChainActive().Tip()->nChainWork > 0); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip() != nullptr); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->nChainWork > 0); } // Test starts here @@ -120,7 +103,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) peerLogic->FinalizeNode(dummyNode1); } -static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &peerLogic, CConnmanTest* connman) +static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman) { CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false)); @@ -130,13 +113,13 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &pee peerLogic.InitializeNode(&node); node.fSuccessfullyConnected = true; - connman->AddNode(node); + connman.AddTestNode(node); } BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { const CChainParams& chainparams = Params(); - auto connman = std::make_unique<CConnmanTest>(0x1337, 0x1337, *m_node.addrman); + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); @@ -150,8 +133,8 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) std::vector<CNode *> vNodes; // Mock some outbound peers - for (int i=0; i<max_outbound_full_relay; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); + for (int i = 0; i < max_outbound_full_relay; ++i) { + AddRandomOutboundPeer(vNodes, *peerLogic, *connman); } peerLogic->CheckForStaleTipAndEvictPeers(); @@ -176,7 +159,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // If we add one more peer, something should get marked for eviction // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). - AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); + AddRandomOutboundPeer(vNodes, *peerLogic, *connman); peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay; ++i) { @@ -202,14 +185,14 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) peerLogic->FinalizeNode(*node); } - connman->ClearNodes(); + connman->ClearTestNodes(); } BOOST_AUTO_TEST_CASE(peer_discouragement) { const CChainParams& chainparams = Params(); - auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - auto connman = std::make_unique<CConnmanTest>(0x1337, 0x1337, *m_node.addrman); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); @@ -233,7 +216,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[0]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[0]); nodes[0]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[0]); + connman->AddTestNode(*nodes[0]); peerLogic->Misbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); // Should be discouraged { LOCK(nodes[0]->cs_sendProcessing); @@ -249,7 +232,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[1]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[1]); nodes[1]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[1]); + connman->AddTestNode(*nodes[1]); peerLogic->Misbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1, /* message */ ""); { LOCK(nodes[1]->cs_sendProcessing); @@ -280,7 +263,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[2]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[2]); nodes[2]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[2]); + connman->AddTestNode(*nodes[2]); peerLogic->Misbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); { LOCK(nodes[2]->cs_sendProcessing); @@ -296,13 +279,13 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) for (CNode* node : nodes) { peerLogic->FinalizeNode(*node); } - connman->ClearNodes(); + connman->ClearTestNodes(); } BOOST_AUTO_TEST_CASE(DoS_bantime) { const CChainParams& chainparams = Params(); - auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 36e2dac3ff..8553f80a17 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -124,14 +124,10 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& // Check that private can produce the normalized descriptors std::string norm1; - BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, false)); + BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1)); BOOST_CHECK(EqualDescriptor(norm1, norm_pub)); - BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, false)); + BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1)); BOOST_CHECK(EqualDescriptor(norm1, norm_pub)); - BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, true)); - BOOST_CHECK(EqualDescriptor(norm1, norm_prv)); - BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, true)); - BOOST_CHECK(EqualDescriptor(norm1, norm_prv)); // Check whether IsRange on both returns the expected result BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0); diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 98ae32a8d0..db0b461873 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -57,15 +57,6 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) (void)addr_man.SelectTriedCollision(); }, [&] { - (void)addr_man.Select(fuzzed_data_provider.ConsumeBool()); - }, - [&] { - (void)addr_man.GetAddr( - /* max_addresses */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), - /* max_pct */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), - /* network */ std::nullopt); - }, - [&] { const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider); const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); if (opt_address && opt_net_addr) { @@ -109,12 +100,15 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) if (opt_service) { addr_man.SetServices(*opt_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); } - }, - [&] { - (void)addr_man.Check(); }); } - (void)addr_man.size(); + const CAddrMan& const_addr_man{addr_man}; + (void)/*const_*/addr_man.GetAddr( + /* max_addresses */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), + /* max_pct */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), + /* network */ std::nullopt); + (void)/*const_*/addr_man.Select(fuzzed_data_provider.ConsumeBool()); + (void)const_addr_man.size(); CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION); - data_stream << addr_man; + data_stream << const_addr_man; } diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index 759a70a857..182aabc79b 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -9,8 +9,10 @@ #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> #include <test/util/setup_common.h> +#include <util/readwritefile.h> #include <util/system.h> +#include <cassert> #include <cstdint> #include <limits> #include <string> @@ -30,6 +32,13 @@ void initialize_banman() static const auto testing_setup = MakeNoLogFileContext<>(); } +static bool operator==(const CBanEntry& lhs, const CBanEntry& rhs) +{ + return lhs.nVersion == rhs.nVersion && + lhs.nCreateTime == rhs.nCreateTime && + lhs.nBanUntil == rhs.nBanUntil; +} + FUZZ_TARGET_INIT(banman, initialize_banman) { // The complexity is O(N^2), where N is the input size, because each call @@ -38,10 +47,23 @@ FUZZ_TARGET_INIT(banman, initialize_banman) int limit_max_ops{300}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); - const fs::path banlist_file = gArgs.GetDataDirNet() / "fuzzed_banlist.dat"; - fs::remove(banlist_file); + fs::path banlist_file = gArgs.GetDataDirNet() / "fuzzed_banlist"; + + const bool start_with_corrupted_banlist{fuzzed_data_provider.ConsumeBool()}; + bool force_read_and_write_to_err{false}; + if (start_with_corrupted_banlist) { + const std::string sfx{fuzzed_data_provider.ConsumeBool() ? ".dat" : ".json"}; + assert(WriteBinaryFile(banlist_file.string() + sfx, + fuzzed_data_provider.ConsumeRandomLengthString())); + } else { + force_read_and_write_to_err = fuzzed_data_provider.ConsumeBool(); + if (force_read_and_write_to_err) { + banlist_file = fs::path{"path"} / "to" / "inaccessible" / "fuzzed_banlist"; + } + } + { - BanMan ban_man{banlist_file, nullptr, ConsumeBanTimeOffset(fuzzed_data_provider)}; + BanMan ban_man{banlist_file, /* client_interface */ nullptr, /* default_ban_time */ ConsumeBanTimeOffset(fuzzed_data_provider)}; while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, @@ -79,6 +101,17 @@ FUZZ_TARGET_INIT(banman, initialize_banman) ban_man.Discourage(ConsumeNetAddr(fuzzed_data_provider)); }); } + if (!force_read_and_write_to_err) { + ban_man.DumpBanlist(); + SetMockTime(ConsumeTime(fuzzed_data_provider)); + banmap_t banmap; + ban_man.GetBanned(banmap); + BanMan ban_man_read{banlist_file, /* client_interface */ nullptr, /* default_ban_time */ 0}; + banmap_t banmap_read; + ban_man_read.GetBanned(banmap_read); + assert(banmap == banmap_read); + } } - fs::remove(banlist_file); + fs::remove(banlist_file.string() + ".dat"); + fs::remove(banlist_file.string() + ".json"); } diff --git a/src/test/fuzz/base_encode_decode.cpp b/src/test/fuzz/base_encode_decode.cpp index 4470e13a61..2b4f15115b 100644 --- a/src/test/fuzz/base_encode_decode.cpp +++ b/src/test/fuzz/base_encode_decode.cpp @@ -14,7 +14,12 @@ #include <string> #include <vector> -FUZZ_TARGET(base_encode_decode) +void initialize_base_encode_decode() +{ + static const ECCVerifyHandle verify_handle; +} + +FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode) { const std::string random_encoded_string(buffer.begin(), buffer.end()); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index b509ee0b26..f452696689 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -27,6 +27,7 @@ #include <vector> namespace { +const TestingSetup* g_setup; const Coin EMPTY_COIN{}; bool operator==(const Coin& a, const Coin& b) @@ -39,6 +40,7 @@ bool operator==(const Coin& a, const Coin& b) void initialize_coins_view() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(coins_view, initialize_coins_view) @@ -181,8 +183,8 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) } { - const CCoinsViewCursor* coins_view_cursor = backend_coins_view.Cursor(); - assert(coins_view_cursor == nullptr); + std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor(); + assert(!coins_view_cursor); (void)backend_coins_view.EstimateSize(); (void)backend_coins_view.GetBestBlock(); (void)backend_coins_view.GetHeadBlocks(); @@ -268,7 +270,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; bool expected_code_path = false; try { - (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats); + (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_setup->m_node.chainman->m_blockman)), stats); } catch (const std::logic_error&) { expected_code_path = true; } diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp index eeeac18968..f83747e424 100644 --- a/src/test/fuzz/crypto.cpp +++ b/src/test/fuzz/crypto.cpp @@ -19,6 +19,10 @@ FUZZ_TARGET(crypto) { + // Hashing is expensive with sanitizers enabled, so limit the number of + // calls + int limit_max_ops{30}; + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; std::vector<uint8_t> data = ConsumeRandomLengthByteVector(fuzzed_data_provider); if (data.empty()) { @@ -36,7 +40,7 @@ FUZZ_TARGET(crypto) SHA3_256 sha3; CSipHasher sip_hasher{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()}; - while (fuzzed_data_provider.ConsumeBool()) { + while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, [&] { diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 1290c78712..721e4360d0 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -53,9 +53,9 @@ struct invalid_fuzzing_input_exception : public std::exception { }; template <typename T> -CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION) +CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK) { - CDataStream ds(SER_NETWORK, version); + CDataStream ds(ser_type, version); ds << obj; return ds; } @@ -69,9 +69,9 @@ T Deserialize(CDataStream ds) } template <typename T> -void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt) +void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt, const int ser_type = SER_NETWORK) { - CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CDataStream ds(buffer, ser_type, INIT_PROTO_VERSION); if (protocol_version) { ds.SetVersion(*protocol_version); } else { @@ -92,9 +92,9 @@ void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optio } template <typename T> -void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION) +void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK) { - assert(Deserialize<T>(Serialize(obj, version)) == obj); + assert(Deserialize<T>(Serialize(obj, version, ser_type)) == obj); } } // namespace @@ -136,8 +136,7 @@ FUZZ_TARGET_DESERIALIZE(partial_merkle_tree_deserialize, { FUZZ_TARGET_DESERIALIZE(pub_key_deserialize, { CPubKey pub_key; DeserializeFromFuzzingInput(buffer, pub_key); - // TODO: The following equivalence should hold for CPubKey? Fix. - // AssertEqualAfterSerializeDeserialize(pub_key); + AssertEqualAfterSerializeDeserialize(pub_key); }) FUZZ_TARGET_DESERIALIZE(script_deserialize, { CScript script; @@ -251,9 +250,37 @@ FUZZ_TARGET_DESERIALIZE(messageheader_deserialize, { DeserializeFromFuzzingInput(buffer, mh); (void)mh.IsCommandValid(); }) -FUZZ_TARGET_DESERIALIZE(address_deserialize, { +FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_notime, { CAddress a; - DeserializeFromFuzzingInput(buffer, a); + DeserializeFromFuzzingInput(buffer, a, INIT_PROTO_VERSION); + // A CAddress without nTime (as is expected under INIT_PROTO_VERSION) will roundtrip + // in all 5 formats (with/without nTime, v1/v2, network/disk) + AssertEqualAfterSerializeDeserialize(a, INIT_PROTO_VERSION); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); +}) +FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_withtime, { + CAddress a; + DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION); + // A CAddress in V1 mode will roundtrip in all 4 formats that have nTime. + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); +}) +FUZZ_TARGET_DESERIALIZE(address_deserialize_v2, { + CAddress a; + DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION | ADDRV2_FORMAT); + // A CAddress in V2 mode will roundtrip in both V2 formats, and also in the V1 formats + // with time if it's V1 compatible. + if (a.IsAddrV1Compatible()) { + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + } + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); }) FUZZ_TARGET_DESERIALIZE(inv_deserialize, { CInv i; diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index e9fa343896..e28e2feb0a 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -16,8 +16,6 @@ #include <pow.h> #include <protocol.h> #include <pubkey.h> -#include <rpc/util.h> -#include <script/signingprovider.h> #include <script/standard.h> #include <serialize.h> #include <streams.h> @@ -158,20 +156,6 @@ FUZZ_TARGET_INIT(integer, initialize_integer) const CKeyID key_id{u160}; const CScriptID script_id{u160}; - // CTxDestination = CNoDestination ∪ PKHash ∪ ScriptHash ∪ WitnessV0ScriptHash ∪ WitnessV0KeyHash ∪ WitnessUnknown - const PKHash pk_hash{u160}; - const ScriptHash script_hash{u160}; - const WitnessV0KeyHash witness_v0_key_hash{u160}; - const WitnessV0ScriptHash witness_v0_script_hash{u256}; - const std::vector<CTxDestination> destinations{pk_hash, script_hash, witness_v0_key_hash, witness_v0_script_hash}; - const SigningProvider store; - for (const CTxDestination& destination : destinations) { - (void)DescribeAddress(destination); - (void)EncodeDestination(destination); - (void)GetKeyForDestination(store, destination); - (void)GetScriptForDestination(destination); - (void)IsValidDestination(destination); - } { CDataStream stream(SER_NETWORK, INIT_PROTO_VERSION); diff --git a/src/test/fuzz/key_io.cpp b/src/test/fuzz/key_io.cpp index 665ca01fa1..f58bf8b316 100644 --- a/src/test/fuzz/key_io.cpp +++ b/src/test/fuzz/key_io.cpp @@ -4,9 +4,6 @@ #include <chainparams.h> #include <key_io.h> -#include <rpc/util.h> -#include <script/signingprovider.h> -#include <script/standard.h> #include <test/fuzz/fuzz.h> #include <cassert> @@ -39,12 +36,4 @@ FUZZ_TARGET_INIT(key_io, initialize_key_io) if (ext_pub_key.pubkey.size() == CPubKey::COMPRESSED_SIZE) { assert(ext_pub_key == DecodeExtPubKey(EncodeExtPubKey(ext_pub_key))); } - - const CTxDestination tx_destination = DecodeDestination(random_string); - (void)DescribeAddress(tx_destination); - (void)GetKeyForDestination(/* store */ {}, tx_destination); - (void)GetScriptForDestination(tx_destination); - (void)IsValidDestination(tx_destination); - - (void)IsValidDestinationString(random_string); } diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp index dbd0c76d42..bfa977520b 100644 --- a/src/test/fuzz/load_external_block_file.cpp +++ b/src/test/fuzz/load_external_block_file.cpp @@ -13,9 +13,14 @@ #include <cstdint> #include <vector> +namespace { +const TestingSetup* g_setup; +} // namespace + void initialize_load_external_block_file() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) @@ -27,5 +32,5 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) return; } FlatFilePos flat_file_pos; - ::ChainstateActive().LoadExternalBlockFile(Params(), fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr); + g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr); } diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp index f9d8129ca9..6cb81901cb 100644 --- a/src/test/fuzz/netaddress.cpp +++ b/src/test/fuzz/netaddress.cpp @@ -54,7 +54,7 @@ FUZZ_TARGET(netaddress) (void)net_addr.IsRFC3927(); (void)net_addr.IsRFC3964(); if (net_addr.IsRFC4193()) { - assert(net_addr.GetNetwork() == Network::NET_ONION || net_addr.GetNetwork() == Network::NET_INTERNAL || net_addr.GetNetwork() == Network::NET_UNROUTABLE); + assert(net_addr.GetNetwork() == Network::NET_INTERNAL || net_addr.GetNetwork() == Network::NET_UNROUTABLE); } (void)net_addr.IsRFC4380(); (void)net_addr.IsRFC4843(); diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp index 70ffc6bf37..a3f71426fa 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -31,7 +31,7 @@ FUZZ_TARGET(node_eviction) /* nKeyedNetGroup */ fuzzed_data_provider.ConsumeIntegral<uint64_t>(), /* prefer_evict */ fuzzed_data_provider.ConsumeBool(), /* m_is_local */ fuzzed_data_provider.ConsumeBool(), - /* m_is_onion */ fuzzed_data_provider.ConsumeBool(), + /* m_network */ fuzzed_data_provider.PickValueInArray(ALL_NETWORKS), }); } // Make a copy since eviction_candidates may be in some valid but otherwise diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 7b99193ad0..c4e4d4c785 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -58,7 +58,19 @@ void initialize_process_message() static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); g_setup = testing_setup.get(); + + // Temporary debug for https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=35027 + { + LOCK(::cs_main); + assert(CheckDiskSpace(gArgs.GetDataDirNet())); + assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize())); + } for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { + { + LOCK(::cs_main); + assert(CheckDiskSpace(gArgs.GetDataDirNet())); + assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize())); + } MineBlock(g_setup->m_node, CScript() << OP_TRUE); } SyncWithValidationInterfaceQueue(); diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index b87bcf2ef5..950ee45d1d 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -6,8 +6,10 @@ #include <compressor.h> #include <core_io.h> #include <core_memusage.h> +#include <key_io.h> #include <policy/policy.h> #include <pubkey.h> +#include <rpc/util.h> #include <script/descriptor.h> #include <script/interpreter.h> #include <script/script.h> @@ -184,26 +186,26 @@ FUZZ_TARGET_INIT(script, initialize_script) } { - WitnessUnknown witness_unknown_1{}; - witness_unknown_1.version = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); - const std::vector<uint8_t> witness_unknown_program_1 = fuzzed_data_provider.ConsumeBytes<uint8_t>(40); - witness_unknown_1.length = witness_unknown_program_1.size(); - std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown_1.program); - - WitnessUnknown witness_unknown_2{}; - witness_unknown_2.version = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); - const std::vector<uint8_t> witness_unknown_program_2 = fuzzed_data_provider.ConsumeBytes<uint8_t>(40); - witness_unknown_2.length = witness_unknown_program_2.size(); - std::copy(witness_unknown_program_2.begin(), witness_unknown_program_2.end(), witness_unknown_2.program); - - (void)(witness_unknown_1 == witness_unknown_2); - (void)(witness_unknown_1 < witness_unknown_2); - } + const CTxDestination tx_destination_1{ + fuzzed_data_provider.ConsumeBool() ? + DecodeDestination(fuzzed_data_provider.ConsumeRandomLengthString()) : + ConsumeTxDestination(fuzzed_data_provider)}; + const CTxDestination tx_destination_2{ConsumeTxDestination(fuzzed_data_provider)}; + const std::string encoded_dest{EncodeDestination(tx_destination_1)}; + const UniValue json_dest{DescribeAddress(tx_destination_1)}; + Assert(tx_destination_1 == DecodeDestination(encoded_dest)); + (void)GetKeyForDestination(/* store */ {}, tx_destination_1); + const CScript dest{GetScriptForDestination(tx_destination_1)}; + const bool valid{IsValidDestination(tx_destination_1)}; + Assert(dest.empty() != valid); + + Assert(valid == IsValidDestinationString(encoded_dest)); - { - const CTxDestination tx_destination_1 = ConsumeTxDestination(fuzzed_data_provider); - const CTxDestination tx_destination_2 = ConsumeTxDestination(fuzzed_data_provider); - (void)(tx_destination_1 == tx_destination_2); (void)(tx_destination_1 < tx_destination_2); + if (tx_destination_1 == tx_destination_2) { + Assert(encoded_dest == EncodeDestination(tx_destination_2)); + Assert(json_dest.write() == DescribeAddress(tx_destination_2).write()); + Assert(dest == GetScriptForDestination(tx_destination_2)); + } } } diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index bcf0b0ce72..ece3214ed5 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <pubkey.h> #include <test/fuzz/util.h> #include <test/util/script.h> #include <util/rbf.h> @@ -304,3 +305,41 @@ uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept }) : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); } + +CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + CTxDestination tx_destination; + const size_t call_size{CallOneOf( + fuzzed_data_provider, + [&] { + tx_destination = CNoDestination{}; + }, + [&] { + tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + tx_destination = ScriptHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + tx_destination = WitnessV0ScriptHash{ConsumeUInt256(fuzzed_data_provider)}; + }, + [&] { + tx_destination = WitnessV0KeyHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}}; + }, + [&] { + WitnessUnknown witness_unknown{}; + witness_unknown.version = fuzzed_data_provider.ConsumeIntegralInRange(2, 16); + std::vector<uint8_t> witness_unknown_program_1{fuzzed_data_provider.ConsumeBytes<uint8_t>(40)}; + if (witness_unknown_program_1.size() < 2) { + witness_unknown_program_1 = {0, 0}; + } + witness_unknown.length = witness_unknown_program_1.size(); + std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program); + tx_destination = witness_unknown; + })}; + Assert(call_size == std::variant_size_v<CTxDestination>); + return tx_destination; +} diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 023dcdb3e5..9f09395a9a 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -37,7 +37,7 @@ #include <vector> template <typename... Callables> -void CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) +size_t CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) { constexpr size_t call_size{sizeof...(callables)}; static_assert(call_size >= 1); @@ -45,6 +45,7 @@ void CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) size_t i{0}; ((i++ == call_index ? callables() : void()), ...); + return call_size; } template <typename Collection> @@ -178,36 +179,7 @@ template <typename WeakEnumType, size_t size> return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; } -[[nodiscard]] inline CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - CTxDestination tx_destination; - CallOneOf( - fuzzed_data_provider, - [&] { - tx_destination = CNoDestination{}; - }, - [&] { - tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; - }, - [&] { - tx_destination = ScriptHash{ConsumeUInt160(fuzzed_data_provider)}; - }, - [&] { - tx_destination = WitnessV0ScriptHash{ConsumeUInt256(fuzzed_data_provider)}; - }, - [&] { - tx_destination = WitnessV0KeyHash{ConsumeUInt160(fuzzed_data_provider)}; - }, - [&] { - WitnessUnknown witness_unknown{}; - witness_unknown.version = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); - const std::vector<uint8_t> witness_unknown_program_1 = fuzzed_data_provider.ConsumeBytes<uint8_t>(40); - witness_unknown.length = witness_unknown_program_1.size(); - std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program); - tx_destination = witness_unknown; - }); - return tx_destination; -} +[[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept; template <typename T> [[nodiscard]] bool MultiplicationOverflow(const T i, const T j) noexcept diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index e1a21b6c53..c2aaf486c5 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -14,9 +14,14 @@ #include <cstdint> #include <vector> +namespace { +const TestingSetup* g_setup; +} // namespace + void initialize_validation_load_mempool() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) @@ -29,6 +34,6 @@ FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) auto fuzzed_fopen = [&](const fs::path&, const char*) { return fuzzed_file_provider.open(); }; - (void)LoadMempool(pool, ::ChainstateActive(), fuzzed_fopen); + (void)LoadMempool(pool, g_setup->m_node.chainman->ActiveChainstate(), fuzzed_fopen); (void)DumpMempool(pool, fuzzed_fopen, true); } diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp index 73463b071e..44779f7d7c 100644 --- a/src/test/interfaces_tests.cpp +++ b/src/test/interfaces_tests.cpp @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(findCommonAncestor) auto* orig_tip = active.Tip(); for (int i = 0; i < 10; ++i) { BlockValidationState state; - ChainstateActive().InvalidateBlock(state, Params(), active.Tip()); + m_node.chainman->ActiveChainstate().InvalidateBlock(state, active.Tip()); } BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10); coinbaseKey.MakeNewKey(true); diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index cb66d5164e..b915982d98 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -300,6 +300,48 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors) auto sig = ParseHex(test.first[2]); BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second); } + + static const std::vector<std::array<std::string, 5>> SIGN_VECTORS = { + {{"0000000000000000000000000000000000000000000000000000000000000003", "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}}, + {{"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "0000000000000000000000000000000000000000000000000000000000000001", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}}, + {{"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}}, + {{"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}}, + }; + + for (const auto& [sec_hex, pub_hex, aux_hex, msg_hex, sig_hex] : SIGN_VECTORS) { + auto sec = ParseHex(sec_hex); + auto pub = ParseHex(pub_hex); + uint256 aux256(ParseHex(aux_hex)); + uint256 msg256(ParseHex(msg_hex)); + auto sig = ParseHex(sig_hex); + unsigned char sig64[64]; + + // Run the untweaked test vectors above, comparing with exact expected signature. + CKey key; + key.Set(sec.begin(), sec.end(), true); + XOnlyPubKey pubkey(key.GetPubKey()); + BOOST_CHECK(std::equal(pubkey.begin(), pubkey.end(), pub.begin(), pub.end())); + bool ok = key.SignSchnorr(msg256, sig64, nullptr, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(std::vector<unsigned char>(sig64, sig64 + 64) == sig); + // Verify those signatures for good measure. + BOOST_CHECK(pubkey.VerifySchnorr(msg256, sig64)); + + // Do 10 iterations where we sign with a random Merkle root to tweak, + // and compare against the resulting tweaked keys, with random aux. + // In iteration i=0 we tweak with empty Merkle tree. + for (int i = 0; i < 10; ++i) { + uint256 merkle_root; + if (i) merkle_root = InsecureRand256(); + auto tweaked = pubkey.CreateTapTweak(i ? &merkle_root : nullptr); + BOOST_CHECK(tweaked); + XOnlyPubKey tweaked_key = tweaked->first; + aux256 = InsecureRand256(); + bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(tweaked_key.VerifySchnorr(msg256, sig64)); + } + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index e54948a840..e20c5e4e8f 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -45,7 +45,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler(::ChainstateActive(), *m_node.mempool, params, options); + return BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, params, options); } constexpr static struct { @@ -82,11 +82,11 @@ constexpr static struct { {2, 0xbbbeb305}, {2, 0xfe1c810a}, }; -static CBlockIndex CreateBlockIndex(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static CBlockIndex CreateBlockIndex(int nHeight, CBlockIndex* active_chain_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CBlockIndex index; index.nHeight = nHeight; - index.pprev = ::ChainActive().Tip(); + index.pprev = active_chain_tip; return index; } @@ -228,17 +228,17 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { LOCK(cs_main); pblock->nVersion = 1; - pblock->nTime = ::ChainActive().Tip()->GetMedianTimePast()+1; + pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.nVersion = 1; txCoinbase.vin[0].scriptSig = CScript(); txCoinbase.vin[0].scriptSig.push_back(bi.extranonce); - txCoinbase.vin[0].scriptSig.push_back(::ChainActive().Height()); + txCoinbase.vin[0].scriptSig.push_back(m_node.chainman->ActiveChain().Height()); txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) txCoinbase.vout[0].scriptPubKey = CScript(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); if (txFirst.size() == 0) - baseheight = ::ChainActive().Height(); + baseheight = m_node.chainman->ActiveChain().Height(); if (txFirst.size() < 4) txFirst.push_back(pblock->vtx[0]); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -364,29 +364,29 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->clear(); // subsidy changing - int nHeight = ::ChainActive().Height(); + int nHeight = m_node.chainman->ActiveChain().Height(); // Create an actual 209999-long block chain (without valid blocks). - while (::ChainActive().Tip()->nHeight < 209999) { - CBlockIndex* prev = ::ChainActive().Tip(); + while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - ::ChainActive().SetTip(next); + m_node.chainman->ActiveChain().SetTip(next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); // Extend to a 210000-long block chain. - while (::ChainActive().Tip()->nHeight < 210000) { - CBlockIndex* prev = ::ChainActive().Tip(); + while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - ::ChainActive().SetTip(next); + m_node.chainman->ActiveChain().SetTip(next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); @@ -409,16 +409,16 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->clear(); // Delete the dummy blocks again. - while (::ChainActive().Tip()->nHeight > nHeight) { - CBlockIndex* del = ::ChainActive().Tip(); - ::ChainActive().SetTip(del->pprev); - ::ChainstateActive().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); + while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { + CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); + m_node.chainman->ActiveChain().SetTip(del->pprev); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); delete del->phashBlock; delete del; } // non-final txs in mempool - SetMockTime(::ChainActive().Tip()->GetMedianTimePast()+1); + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1); int flags = LOCKTIME_VERIFY_SEQUENCE|LOCKTIME_MEDIAN_TIME_PAST; // height map std::vector<int> prevheights; @@ -430,7 +430,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.hash = txFirst[0]->GetHash(); // only 1 transaction tx.vin[0].prevout.n = 0; tx.vin[0].scriptSig = CScript() << OP_1; - tx.vin[0].nSequence = ::ChainActive().Tip()->nHeight + 1; // txFirst[0] is the 2nd block + tx.vin[0].nSequence = m_node.chainman->ActiveChain().Tip()->nHeight + 1; // txFirst[0] is the 2nd block prevheights[0] = baseheight + 1; tx.vout.resize(1); tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; @@ -438,53 +438,62 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = 0; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), 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 + + { + CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 2, active_chain_tip))); // Sequence locks pass on 2nd block + } // relative time locked tx.vin[0].prevout.hash = txFirst[1]->GetHash(); - 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 + tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 1))); // Sequence locks pass 512 seconds later + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + + { + CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 1, active_chain_tip))); // Sequence locks pass 512 seconds later + } + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP // absolute height locked tx.vin[0].prevout.hash = txFirst[2]->GetHash(); tx.vin[0].nSequence = CTxIn::SEQUENCE_FINAL - 1; prevheights[0] = baseheight + 3; - tx.nLockTime = ::ChainActive().Tip()->nHeight + 1; + tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), 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 + BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block // absolute time locked tx.vin[0].prevout.hash = txFirst[3]->GetHash(); - tx.nLockTime = ::ChainActive().Tip()->GetMedianTimePast(); + tx.nLockTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast(); prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), 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 + BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later // mempool-dependent transactions (not added) tx.vin[0].prevout.hash = hash; - prevheights[0] = ::ChainActive().Tip()->nHeight + 1; + prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass tx.vin[0].nSequence = 1; BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail @@ -502,14 +511,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3U); // However if we advance height by 1 and time by 512, all of them should be mined for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - ::ChainActive().Tip()->nHeight++; - SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1); + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + m_node.chainman->ActiveChain().Tip()->nHeight++; + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); - ::ChainActive().Tip()->nHeight--; + m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); m_node.mempool->clear(); diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 31d391bf7d..5eb280b498 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -2,7 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <netaddress.h> #include <net.h> +#include <test/util/net.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> @@ -15,33 +17,6 @@ BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup) -namespace { -constexpr int NODE_EVICTION_TEST_ROUNDS{10}; -constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200}; -} // namespace - -std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context) -{ - std::vector<NodeEvictionCandidate> candidates; - for (int id = 0; id < n_candidates; ++id) { - candidates.push_back({ - /* id */ id, - /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)), - /* m_min_ping_time */ std::chrono::microseconds{random_context.randrange(100)}, - /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)), - /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)), - /* fRelevantServices */ random_context.randbool(), - /* fRelayTxes */ random_context.randbool(), - /* fBloomFilter */ random_context.randbool(), - /* nKeyedNetGroup */ random_context.randrange(100), - /* prefer_evict */ random_context.randbool(), - /* m_is_local */ random_context.randbool(), - /* m_is_onion */ random_context.randbool(), - }); - } - return candidates; -} - // Create `num_peers` random nodes, apply setup function `candidate_setup_fn`, // call ProtectEvictionCandidatesByRatio() to apply protection logic, and then // return true if all of `protected_peer_ids` and none of `unprotected_peer_ids` @@ -94,7 +69,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = c.m_is_local = false; + c.m_is_local = false; + c.m_network = NET_IPV4; }, /* protected_peer_ids */ {0, 1, 2, 3, 4, 5}, /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11}, @@ -104,129 +80,359 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( num_peers, [num_peers](NodeEvictionCandidate& c) { c.nTimeConnected = num_peers - c.id; - c.m_is_onion = c.m_is_local = false; + c.m_is_local = false; + c.m_network = NET_IPV6; }, /* protected_peer_ids */ {6, 7, 8, 9, 10, 11}, /* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5}, random_context)); - // Test protection of onion and localhost peers... + // Test protection of onion, localhost, and I2P peers... // Expect 1/4 onion peers to be protected from eviction, - // independently of other characteristics. + // if no localhost or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { - c.m_is_onion = (c.id == 3 || c.id == 8 || c.id == 9); + c.m_is_local = false; + c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4; }, /* protected_peer_ids */ {3, 8, 9}, /* unprotected_peer_ids */ {}, random_context)); - // Expect 1/4 onion peers and 1/4 of the others to be protected - // from eviction, sorted by longest uptime (lowest nTimeConnected). + // Expect 1/4 onion peers and 1/4 of the other peers to be protected, + // sorted by longest uptime (lowest nTimeConnected), if no localhost or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; c.m_is_local = false; - c.m_is_onion = (c.id == 3 || c.id > 7); + c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6; }, /* protected_peer_ids */ {0, 1, 2, 3, 8, 9}, /* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11}, random_context)); // Expect 1/4 localhost peers to be protected from eviction, - // if no onion peers. + // if no onion or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { - c.m_is_onion = false; c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + c.m_network = NET_IPV4; }, /* protected_peer_ids */ {1, 9, 11}, /* unprotected_peer_ids */ {}, random_context)); // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest nTimeConnected), if no onion peers. + // sorted by longest uptime (lowest nTimeConnected), if no onion or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = false; c.m_is_local = (c.id > 6); + c.m_network = NET_IPV6; }, /* protected_peer_ids */ {0, 1, 2, 7, 8, 9}, /* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11}, random_context)); - // Combined test: expect 1/4 onion and 2 localhost peers to be protected - // from eviction, sorted by longest uptime. + // Expect 1/4 I2P peers to be protected from eviction, + // if no onion or localhost peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_local = false; + c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4; + }, + /* protected_peer_ids */ {2, 7, 10}, + /* unprotected_peer_ids */ {}, + random_context)); + + // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, + // sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 0 || c.id == 5 || c.id == 10); - c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + c.m_is_local = false; + c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6; }, - /* protected_peer_ids */ {0, 1, 2, 5, 9, 10}, - /* unprotected_peer_ids */ {3, 4, 6, 7, 8, 11}, + /* protected_peer_ids */ {0, 1, 2, 4, 9, 10}, + /* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11}, random_context)); - // Combined test: expect having only 1 onion to allow allocating the - // remaining 2 of the 1/4 to localhost peers, sorted by longest uptime. + // Tests with 2 networks... + + // Combined test: expect having 1 localhost and 1 onion peer out of 4 to + // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime; + // stable sort breaks tie with array order of localhost first. BOOST_CHECK(IsProtected( - num_peers + 4, [](NodeEvictionCandidate& c) { + 4, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 15); - c.m_is_local = (c.id > 6 && c.id < 11); + c.m_is_local = (c.id == 4); + c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4; }, - /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15}, - /* unprotected_peer_ids */ {4, 5, 6, 10, 11, 12, 13, 14}, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2}, + random_context)); + + // Combined test: expect having 1 localhost and 1 onion peer out of 7 to + // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by + // uptime; stable sort breaks tie with array order of localhost first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 localhost and 1 onion peer out of 8 to + // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted + // by uptime; stable sort breaks tie with array order of localhost first. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 5, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 7}, random_context)); - // Combined test: expect 2 onions (< 1/4) to allow allocating the minimum 2 - // localhost peers, sorted by longest uptime. + // Combined test: expect having 3 localhost and 3 onion peers out of 12 to + // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest + // uptime; stable sort breaks ties with the array order of localhost first. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 7 || c.id == 9); - c.m_is_local = (c.id == 6 || c.id == 11); + c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11); + c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; }, - /* protected_peer_ids */ {0, 1, 6, 7, 9, 11}, - /* unprotected_peer_ids */ {2, 3, 4, 5, 8, 10}, + /* protected_peer_ids */ {0, 1, 2, 6, 7, 9}, + /* unprotected_peer_ids */ {3, 4, 5, 8, 10, 11}, random_context)); - // Combined test: when > 1/4, expect max 1/4 onion and 2 localhost peers - // to be protected from eviction, sorted by longest uptime. + // Combined test: expect having 4 localhost and 1 onion peer out of 12 to + // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id > 3 && c.id < 8); - c.m_is_local = (c.id > 7); + c.m_is_local = (c.id > 4 && c.id < 9); + c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; }, - /* protected_peer_ids */ {0, 4, 5, 6, 8, 9}, - /* unprotected_peer_ids */ {1, 2, 3, 7, 10, 11}, + /* protected_peer_ids */ {0, 1, 2, 5, 6, 10}, + /* unprotected_peer_ids */ {3, 4, 7, 8, 9, 11}, random_context)); - // Combined test: idem > 1/4 with only 8 peers: expect 2 onion and 2 - // localhost peers (1/4 + 2) to be protected, sorted by longest uptime. + // Combined test: expect having 4 localhost and 2 onion peers out of 16 to + // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime. BOOST_CHECK(IsProtected( - 8, [](NodeEvictionCandidate& c) { + 16, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id > 1 && c.id < 5); - c.m_is_local = (c.id > 4); + c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12); + c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 6, 8, 9, 10}, + /* unprotected_peer_ids */ {4, 5, 7, 11, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 5 localhost and 1 onion peer out of 16 to + // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4 + // others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id > 10); + c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 10, 11, 12, 13}, + /* unprotected_peer_ids */ {4, 5, 6, 7, 8, 9, 14, 15}, + random_context)); + + // Combined test: expect having 1 localhost and 4 onion peers out of 16 to + // protect 1 localhost and 3 onions (recovering the unused localhost slot), + // plus 4 others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 15); + c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15}, + /* unprotected_peer_ids */ {5, 6, 10, 11, 12, 13, 14}, + random_context)); + + // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect + // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3 + // others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = false; + if (c.id == 8 || c.id == 10) { + c.m_network = NET_ONION; + } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) { + c.m_network = NET_I2P; + } else { + c.m_network = NET_IPV4; + } }, - /* protected_peer_ids */ {2, 3, 5, 6}, - /* unprotected_peer_ids */ {0, 1, 4, 7}, + /* protected_peer_ids */ {0, 1, 2, 6, 8, 10}, + /* unprotected_peer_ids */ {3, 4, 5, 7, 9, 11}, random_context)); - // Combined test: idem > 1/4 with only 6 peers: expect 1 onion peer and no - // localhost peers (1/4 + 0) to be protected, sorted by longest uptime. + // Tests with 3 networks... + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4 + // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted + // by longest uptime; stable sort breaks tie with array order of I2P first. BOOST_CHECK(IsProtected( - 6, [](NodeEvictionCandidate& c) { + 4, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 4 || c.id == 5); c.m_is_local = (c.id == 3); + if (c.id == 4) { + c.m_network = NET_I2P; + } else if (c.id == 2) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2}, + random_context)); + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7 + // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted + // by longest uptime; stable sort breaks tie with array order of I2P first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 4); + if (c.id == 6) { + c.m_network = NET_I2P; + } else if (c.id == 5) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8 + // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted + // by uptime; stable sort breaks tie with array order of I2P then localhost. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + if (c.id == 5) { + c.m_network = NET_I2P; + } else if (c.id == 4) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 5, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 7}, + random_context)); + + // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of + // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others + // for 8 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6 || c.id > 11); + if (c.id == 7 || c.id == 11) { + c.m_network = NET_I2P; + } else if (c.id == 9 || c.id == 10) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 6, 7, 9, 11}, + /* unprotected_peer_ids */ {4, 5, 8, 10, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of + // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total, + // sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 12); + if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2 + c.m_network = NET_I2P; + } else if (c.id == 23) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } }, - /* protected_peer_ids */ {0, 1, 4}, - /* unprotected_peer_ids */ {2, 3, 5}, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22}, + random_context)); + + // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of + // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the + // unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 15); + if (c.id == 12 || c.id == 14 || c.id == 17) { + c.m_network = NET_I2P; + } else if (c.id > 17) { // 4 protected instead of usual 2 + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23}, + random_context)); + + // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of + // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others + // for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 13); + if (c.id > 16) { + c.m_network = NET_I2P; + } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, + random_context)); + + // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of + // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted + // by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id > 15); + if (c.id > 10 && c.id < 15) { + c.m_network = NET_I2P; + } else if (c.id > 6 && c.id < 10) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, + /* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context)); } @@ -257,91 +463,89 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test) { FastRandomContext random_context{true}; - for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) { - for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) { - // Four nodes with the highest keyed netgroup values should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Eight nodes with the lowest minimum ping time should be protected - // from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [](NodeEvictionCandidate& candidate) { - candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four nodes that most recently sent us novel transactions accepted - // into our mempool should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastTXTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Up to eight non-tx-relay peers that most recently sent us novel - // blocks should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four peers that most recently sent us novel blocks should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Combination of the previous two tests. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); - - // Combination of all tests above. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected - candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected - candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected - candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); - - // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most - // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay - // peers by last novel block time, and four more peers by last novel block time. - if (number_of_nodes >= 29) { - BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } - - // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least - // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last - // novel block time. - if (number_of_nodes <= 20) { - BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } + for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) { + // Four nodes with the highest keyed netgroup values should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Eight nodes with the lowest minimum ping time should be protected + // from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [](NodeEvictionCandidate& candidate) { + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four nodes that most recently sent us novel transactions accepted + // into our mempool should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastTXTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Up to eight non-tx-relay peers that most recently sent us novel + // blocks should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four peers that most recently sent us novel blocks should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Combination of the previous two tests. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); + + // Combination of all tests above. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected + candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected + candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); + + // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most + // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay + // peers by last novel block time, and four more peers by last novel block time. + if (number_of_nodes >= 29) { + BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); + } - // Cases left to test: - // * "If any remaining peers are preferred for eviction consider only them. [...]" - // * "Identify the network group with the most connections and youngest member. [...]" + // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least + // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last + // novel block time. + if (number_of_nodes <= 20) { + BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); } + + // Cases left to test: + // * "If any remaining peers are preferred for eviction consider only them. [...]" + // * "Identify the network group with the most connections and youngest member. [...]" } } diff --git a/src/test/serfloat_tests.cpp b/src/test/serfloat_tests.cpp index 54e07b0f61..7876c0bcda 100644 --- a/src/test/serfloat_tests.cpp +++ b/src/test/serfloat_tests.cpp @@ -36,9 +36,9 @@ uint64_t TestDouble(double f) { } // namespace BOOST_AUTO_TEST_CASE(double_serfloat_tests) { - BOOST_CHECK_EQUAL(TestDouble(0.0), 0); + BOOST_CHECK_EQUAL(TestDouble(0.0), 0U); BOOST_CHECK_EQUAL(TestDouble(-0.0), 0x8000000000000000); - BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::infinity()), 0x7ff0000000000000); + BOOST_CHECK_EQUAL(TestDouble(std::numeric_limits<double>::infinity()), 0x7ff0000000000000U); BOOST_CHECK_EQUAL(TestDouble(-std::numeric_limits<double>::infinity()), 0xfff0000000000000); BOOST_CHECK_EQUAL(TestDouble(0.5), 0x3fe0000000000000ULL); BOOST_CHECK_EQUAL(TestDouble(1.0), 0x3ff0000000000000ULL); @@ -48,8 +48,8 @@ BOOST_AUTO_TEST_CASE(double_serfloat_tests) { // Roundtrip test on IEC559-compatible systems if (std::numeric_limits<double>::is_iec559) { - BOOST_CHECK_EQUAL(sizeof(double), 8); - BOOST_CHECK_EQUAL(sizeof(uint64_t), 8); + BOOST_CHECK_EQUAL(sizeof(double), 8U); + BOOST_CHECK_EQUAL(sizeof(uint64_t), 8U); // Test extreme values TestDouble(std::numeric_limits<double>::min()); TestDouble(-std::numeric_limits<double>::min()); diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 7af2b79f37..acd0151e1a 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -119,7 +119,7 @@ BOOST_AUTO_TEST_CASE(streams_vector_reader_rvalue) uint32_t varint = 0; // Deserialize into r-value reader >> VARINT(varint); - BOOST_CHECK_EQUAL(varint, 54321); + BOOST_CHECK_EQUAL(varint, 54321U); BOOST_CHECK(reader.empty()); } diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index 940145b84f..e97eab2c00 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -7,6 +7,11 @@ #include <univalue.h> #ifdef ENABLE_EXTERNAL_SIGNER +#if defined(WIN32) && !defined(__kernel_entry) +// A workaround for boost 1.71 incompatibility with mingw-w64 compiler. +// For details see https://github.com/bitcoin/bitcoin/pull/22348. +#define __kernel_entry +#endif #include <boost/process.hpp> #endif // ENABLE_EXTERNAL_SIGNER diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index d47c54fd6e..3ce7ecb5f2 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -28,7 +28,7 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) // BlockUntilSyncedToCurrentChain should return false before txindex is started. BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(txindex.Start(::ChainstateActive())); + BOOST_REQUIRE(txindex.Start(m_node.chainman->ActiveChainstate())); // Allow tx index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 95ad85d0f8..ade9e210f2 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -37,7 +37,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) LOCK(cs_main); unsigned int initialPoolSize = m_node.mempool->size(); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(coinbaseTx), + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, MakeTransactionRef(coinbaseTx), true /* bypass_limits */); BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID); diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 3244b58082..23195c0a26 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -31,7 +31,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) const auto ToMemPool = [this](const CMutableTransaction& tx) { LOCK(cs_main); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(tx), + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, MakeTransactionRef(tx), true /* bypass_limits */); return result.m_result_type == MempoolAcceptResult::ResultType::VALID; }; @@ -63,7 +63,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } // Test 2: ... and should be rejected if spend1 is in the memory pool @@ -71,7 +71,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } m_node.mempool->clear(); @@ -80,7 +80,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } m_node.mempool->clear(); @@ -91,7 +91,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(oneSpend, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash()); } // spends[1] should have been removed from the mempool when the // block with spends[0] is accepted: @@ -109,7 +109,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) // should fail. // Capture this interaction with the upgraded_nop argument: set it when evaluating // any script flag that is implemented as an upgraded NOP code. -static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache, CCoinsViewCache& active_coins_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { PrecomputedTransactionData txdata; // If we add many more flags, this loop can get too expensive, but we can @@ -126,7 +126,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail // WITNESS requires P2SH test_flags |= SCRIPT_VERIFY_P2SH; } - bool ret = CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, nullptr); + bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, nullptr); // CheckInputScripts should succeed iff test_flags doesn't intersect with // failing_flags bool expected_return_value = !(test_flags & failing_flags); @@ -136,13 +136,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail if (ret && add_to_cache) { // Check that we get a cache hit if the tx was valid std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK(scriptchecks.empty()); } else { // Check that we get script executions to check, if the transaction // was invalid, or we didn't add to cache. std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size()); } } @@ -205,20 +205,20 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) TxValidationState state; PrecomputedTransactionData ptd_spend_tx; - BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); + BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); // If we call again asking for scriptchecks (as happens in // ConnectBlock), we should add a script check object for this -- we're // not caching invalidity (if that changes, delete this test case). std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), 1U); // Test that CheckInputScripts returns true iff DERSIG-enforcing flags are // not present. Don't add these checks to the cache, so that we can // test later that block validation works fine in the absence of cached // successes. - ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false); + ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false, m_node.chainman->ActiveChainstate().CoinsTip()); } // And if we produce a block with this tx, it should be valid (DERSIG not @@ -227,8 +227,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey); LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() == block.GetHash()); - BOOST_CHECK(::ChainstateActive().CoinsTip().GetBestBlock() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChainstate().CoinsTip().GetBestBlock() == block.GetHash()); // Test P2SH: construct a transaction that is valid without P2SH, and // then test validity with P2SH. @@ -244,7 +244,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) std::vector<unsigned char> vchSig2(p2pk_scriptPubKey.begin(), p2pk_scriptPubKey.end()); invalid_under_p2sh_tx.vin[0].scriptSig << vchSig2; - ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true, m_node.chainman->ActiveChainstate().CoinsTip()); } // Test CHECKLOCKTIMEVERIFY @@ -267,13 +267,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Make it valid, and check again invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; TxValidationState state; PrecomputedTransactionData txdata; - BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } // TEST CHECKSEQUENCEVERIFY @@ -295,13 +295,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Make it valid, and check again invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; TxValidationState state; PrecomputedTransactionData txdata; - BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } // TODO: add tests for remaining script flags @@ -324,11 +324,11 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. - ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Remove the witness, and check that it is now invalid. valid_with_witness_tx.vin[0].scriptWitness.SetNull(); - ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true, m_node.chainman->ActiveChainstate().CoinsTip()); } { @@ -353,7 +353,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) } // This should be valid under all script flags - ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true); + ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Check that if the second input is invalid, but the first input is // valid, the transaction is not cached. @@ -363,12 +363,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) TxValidationState state; PrecomputedTransactionData txdata; // This transaction is now invalid under segwit, because of the second input. - BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); + BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); std::vector<CScriptCheck> scriptchecks; // Make sure this transaction was not cached (ie because the first // input was valid) - BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); // Should get 2 script checks back -- caching is on a whole-transaction basis. BOOST_CHECK_EQUAL(scriptchecks.size(), 2U); } diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 1204873828..f6a11bc02e 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -74,12 +74,12 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) { auto block = std::make_shared<CBlock>( - BlockAssembler{::ChainstateActive(), *Assert(node.mempool), Params()} + BlockAssembler{Assert(node.chainman)->ActiveChainstate(), *Assert(node.mempool), Params()} .CreateNewBlock(coinbase_scriptPubKey) ->block); LOCK(cs_main); - block->nTime = ::ChainActive().Tip()->GetMedianTimePast() + 1; + block->nTime = Assert(node.chainman)->ActiveChain().Tip()->GetMedianTimePast() + 1; block->hashMerkleRoot = BlockMerkleRoot(*block); return block; diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp index 847a490e03..28d7967078 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -6,6 +6,9 @@ #include <chainparams.h> #include <net.h> +#include <span.h> + +#include <vector> void ConnmanTestMsg::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const { @@ -37,3 +40,25 @@ bool ConnmanTestMsg::ReceiveMsgFrom(CNode& node, CSerializedNetMsg& ser_msg) con NodeReceiveMsgBytes(node, ser_msg.data, complete); return complete; } + +std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candidates, FastRandomContext& random_context) +{ + std::vector<NodeEvictionCandidate> candidates; + for (int id = 0; id < n_candidates; ++id) { + candidates.push_back({ + /* id */ id, + /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)), + /* m_min_ping_time */ std::chrono::microseconds{random_context.randrange(100)}, + /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)), + /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)), + /* fRelevantServices */ random_context.randbool(), + /* fRelayTxes */ random_context.randbool(), + /* fBloomFilter */ random_context.randbool(), + /* nKeyedNetGroup */ random_context.randrange(100), + /* prefer_evict */ random_context.randbool(), + /* m_is_local */ random_context.randbool(), + /* m_network */ ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())], + }); + } + return candidates; +} diff --git a/src/test/util/net.h b/src/test/util/net.h index 71685d437a..939ec322ed 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -6,9 +6,11 @@ #define BITCOIN_TEST_UTIL_NET_H #include <compat.h> +#include <netaddress.h> #include <net.h> #include <util/sock.h> +#include <array> #include <cassert> #include <cstring> #include <string> @@ -67,6 +69,16 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{ ConnectionType::ADDR_FETCH, }; +constexpr auto ALL_NETWORKS = std::array{ + Network::NET_UNROUTABLE, + Network::NET_IPV4, + Network::NET_IPV6, + Network::NET_ONION, + Network::NET_I2P, + Network::NET_CJDNS, + Network::NET_INTERNAL, +}; + /** * A mocked Sock alternative that returns a statically contained data upon read and succeeds * and ignores all writes. The data to be returned is given to the constructor and when it is @@ -129,4 +141,6 @@ private: mutable size_t m_consumed; }; +std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candidates, FastRandomContext& random_context); + #endif // BITCOIN_TEST_UTIL_NET_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 863c3ab565..d9d236be1d 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -76,6 +76,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()}, m_args{} { + m_node.args = &gArgs; const std::vector<const char*> arguments = Cat( { "dummy", @@ -94,7 +95,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve gArgs.ForceSetArg("-datadir", m_path_root.string()); gArgs.ClearPathCache(); { - SetupServerArgs(m_node); + SetupServerArgs(*m_node.args); std::string error; const bool success{m_node.args->ParseParameters(arguments.size(), arguments.data(), error)}; assert(success); @@ -145,7 +146,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1); - m_node.chainman = &::g_chainman; + m_node.chainman = std::make_unique<ChainstateManager>(); // Start script-checking threads. Set g_parallel_script_checks to true so they are used. constexpr int script_check_threads = 2; @@ -167,7 +168,7 @@ ChainTestingSetup::~ChainTestingSetup() m_node.mempool.reset(); m_node.scheduler.reset(); m_node.chainman->Reset(); - m_node.chainman = nullptr; + m_node.chainman.reset(); pblocktree.reset(); } @@ -179,23 +180,23 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const // instead of unit tests, but for now we need these here. RegisterAllCoreRPCCommands(tableRPC); - m_node.chainman->InitializeChainstate(*m_node.mempool); - ::ChainstateActive().InitCoinsDB( + m_node.chainman->InitializeChainstate(m_node.mempool.get()); + m_node.chainman->ActiveChainstate().InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); - assert(!::ChainstateActive().CanFlushToDisk()); - ::ChainstateActive().InitCoinsCache(1 << 23); - assert(::ChainstateActive().CanFlushToDisk()); - if (!::ChainstateActive().LoadGenesisBlock(chainparams)) { + assert(!m_node.chainman->ActiveChainstate().CanFlushToDisk()); + m_node.chainman->ActiveChainstate().InitCoinsCache(1 << 23); + assert(m_node.chainman->ActiveChainstate().CanFlushToDisk()); + if (!m_node.chainman->ActiveChainstate().LoadGenesisBlock()) { throw std::runtime_error("LoadGenesisBlock failed."); } BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, chainparams)) { + if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } m_node.addrman = std::make_unique<CAddrMan>(); - m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); // Deterministic randomness for tests. m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, m_node.banman.get(), *m_node.scheduler, *m_node.chainman, @@ -240,7 +241,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa { const CChainParams& chainparams = Params(); CTxMemPool empty_pool; - CBlock block = BlockAssembler(::ChainstateActive(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler(m_node.chainman->ActiveChainstate(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { @@ -299,7 +300,7 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio // If submit=true, add transaction to the mempool. if (submit) { LOCK(cs_main); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool.get(), MakeTransactionRef(mempool_txn), /* bypass_limits */ false); + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool.get(), MakeTransactionRef(mempool_txn), /* bypass_limits */ false); assert(result.m_result_type == MempoolAcceptResult::ResultType::VALID); } diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 552be0a2da..e0bc10d660 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -84,8 +84,8 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock) { - LOCK(cs_main); // For g_chainman.m_blockman.LookupBlockIndex - GenerateCoinbaseCommitment(*pblock, g_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); + LOCK(cs_main); // For m_node.chainman->m_blockman.LookupBlockIndex + GenerateCoinbaseCommitment(*pblock, m_node.chainman->m_blockman.LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) const CBlockIndex* initial_tip = nullptr; { LOCK(cs_main); - initial_tip = ::ChainActive().Tip(); + initial_tip = m_node.chainman->ActiveChain().Tip(); } auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash()); RegisterSharedValidationInterface(sub); @@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) UnregisterSharedValidationInterface(sub); LOCK(cs_main); - BOOST_CHECK_EQUAL(sub->m_expected_tip, ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } /** @@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // Run the test multiple times for (int test_runs = 3; test_runs > 0; --test_runs) { - BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); // Later on split from here const uint256 split_hash{last_mined->hashPrevBlock}; @@ -273,7 +273,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { LOCK(cs_main); for (const auto& tx : txs) { - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, tx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, tx, false /* bypass_limits */); BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID); } } @@ -306,7 +306,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) } LOCK(cs_main); // We are done with the reorg, so the tip must have changed - assert(tip_init != ::ChainActive().Tip()->GetBlockHash()); + assert(tip_init != m_node.chainman->ActiveChain().Tip()->GetBlockHash()); }}; // Submit the reorg in this thread to invalidate and remove the txs from the tx pool @@ -314,7 +314,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) ProcessBlock(b); } // Check that the reorg was eventually successful - BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); // We can join the other thread, which returns when the reorg was successful rpc_thread.join(); diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 92d8cf2e7d..2893b412fb 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) return outp; }; - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 0b912acb08..0bd378631b 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -31,13 +31,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) CTxMemPool& mempool = *m_node.mempool; std::vector<CChainState*> chainstates; - const CChainParams& chainparams = Params(); BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -67,7 +66,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // const uint256 snapshot_blockhash = GetRandHash(); CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate( - mempool, snapshot_blockhash)); + &mempool, snapshot_blockhash)); chainstates.push_back(&c2); BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash); @@ -76,9 +75,9 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23)); // Unlike c1, which doesn't have any blocks. Gets us different tip, height. - c2.LoadGenesisBlock(chainparams); + c2.LoadGenesisBlock(); BlockValidationState _; - BOOST_CHECK(c2.ActivateBestChain(_, chainparams, nullptr)); + BOOST_CHECK(c2.ActivateBestChain(_, nullptr)); BOOST_CHECK(manager.IsSnapshotActive()); BOOST_CHECK(!manager.IsSnapshotValidated()); @@ -130,7 +129,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -138,7 +137,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) { LOCK(::cs_main); c1.InitCoinsCache(1 << 23); - BOOST_REQUIRE(c1.LoadGenesisBlock(Params())); + BOOST_REQUIRE(c1.LoadGenesisBlock()); c1.CoinsTip().SetBestBlock(InsecureRand256()); manager.MaybeRebalanceCaches(); } @@ -148,7 +147,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a snapshot-based chainstate. // - CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool, GetRandHash())); + CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -156,7 +155,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) { LOCK(::cs_main); c2.InitCoinsCache(1 << 23); - BOOST_REQUIRE(c2.LoadGenesisBlock(Params())); + BOOST_REQUIRE(c2.LoadGenesisBlock()); c2.CoinsTip().SetBestBlock(InsecureRand256()); manager.MaybeRebalanceCaches(); } diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index a3b344d2c9..22aafcaa6c 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -20,10 +20,9 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) { CTxMemPool mempool; BlockManager blockman{}; - CChainState chainstate{mempool, blockman}; + CChainState chainstate{&mempool, blockman}; chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false); WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10)); - CTxMemPool tx_pool{}; constexpr bool is_64_bit = sizeof(void*) == 8; @@ -57,7 +56,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // Without any coins in the cache, we shouldn't need to flush. BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), CoinsCacheSizeState::OK); // If the initial memory allocations of cacheCoins don't match these common @@ -72,7 +71,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) } BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), CoinsCacheSizeState::CRITICAL); BOOST_TEST_MESSAGE("Exiting cache flush tests early due to unsupported arch"); @@ -93,7 +92,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) print_view_mem_usage(view); BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE); BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), CoinsCacheSizeState::OK); } @@ -101,26 +100,26 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) for (int i{0}; i < 4; ++i) { add_coin(view); print_view_mem_usage(view); - if (chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0) == + if (chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0) == CoinsCacheSizeState::CRITICAL) { break; } } BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), CoinsCacheSizeState::CRITICAL); // Passing non-zero max mempool usage should allow us more headroom. BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), CoinsCacheSizeState::OK); for (int i{0}; i < 3; ++i) { add_coin(view); print_view_mem_usage(view); BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), CoinsCacheSizeState::OK); } @@ -136,7 +135,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) BOOST_CHECK(usage_percentage >= 0.9); BOOST_CHECK(usage_percentage < 1); BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 1 << 10), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, 1 << 10), CoinsCacheSizeState::LARGE); } @@ -144,7 +143,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) for (int i{0}; i < 1000; ++i) { add_coin(view); BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool), + chainstate.GetCoinsCacheSizeState(), CoinsCacheSizeState::OK); } @@ -152,7 +151,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // preallocated memory that doesn't get reclaimed even after flush. BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, 0), CoinsCacheSizeState::CRITICAL); view.SetBestBlock(InsecureRand256()); @@ -160,7 +159,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) print_view_mem_usage(view); BOOST_CHECK_EQUAL( - chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 0), + chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, 0), CoinsCacheSizeState::CRITICAL); } diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 304cd8feb0..690031cdc1 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -5,6 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <consensus/params.h> +#include <deploymentstatus.h> #include <test/util/setup_common.h> #include <validation.h> #include <versionbits.h> @@ -258,8 +259,8 @@ BOOST_AUTO_TEST_CASE(versionbits_test) /** Check that ComputeBlockVersion will set the appropriate bit correctly */ static void check_computeblockversion(const Consensus::Params& params, Consensus::DeploymentPos dep) { - // This implicitly uses versionbitscache, so clear it every time - versionbitscache.Clear(); + // This implicitly uses g_versionbitscache, so clear it every time + g_versionbitscache.Clear(); int64_t bit = params.vDeployments[dep].bit; int64_t nStartTime = params.vDeployments[dep].nStartTime; @@ -267,7 +268,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus int min_activation_height = params.vDeployments[dep].min_activation_height; // should not be any signalling for first block - BOOST_CHECK_EQUAL(ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); // always/never active deployments shouldn't need to be tested further if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE || @@ -287,7 +288,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Check min_activation_height is on a retarget boundary BOOST_REQUIRE_EQUAL(min_activation_height % params.nMinerConfirmationWindow, 0U); - const uint32_t bitmask{VersionBitsMask(params, dep)}; + const uint32_t bitmask{g_versionbitscache.Mask(params, dep)}; BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit); // In the first chain, test that the bit is set by CBV until it has failed. @@ -306,9 +307,9 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // earlier time, so will transition from DEFINED to STARTED at the // end of the first period by mining blocks at nTime == 0 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then we'll keep mining at nStartTime... } else { // use a time 1s earlier than start time to check we stay DEFINED @@ -316,28 +317,28 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Start generating blocks before nStartTime lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); // Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet. for (uint32_t i = 1; i < params.nMinerConfirmationWindow - 4; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Now mine 5 more blocks at the start time -- MTP should not have passed yet, so // CBV should still not yet set the bit. nTime = nStartTime; for (uint32_t i = params.nMinerConfirmationWindow - 4; i <= params.nMinerConfirmationWindow; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Next we will advance to the next period and transition to STARTED, } lastBlock = firstChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); // so ComputeBlockVersion should now set the bit, - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // and should also be using the VERSIONBITS_TOP_BITS. - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); // Check that ComputeBlockVersion will set the bit until nTimeout nTime += 600; @@ -346,8 +347,8 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // These blocks are all before nTimeout is reached. while (nTime < nTimeout && blocksToMine > 0) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); blocksToMine--; nTime += 600; nHeight += 1; @@ -361,7 +362,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // finish the last period before we start timing out while (nHeight % params.nMinerConfirmationWindow != 0) { lastBlock = firstChain.Mine(nHeight+1, nTime - 1, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } @@ -369,12 +370,12 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // the bit until the period transition. for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } // The next block should trigger no longer setting the bit. lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // On a new chain: @@ -385,30 +386,30 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Mine one period worth of blocks, and check that the bit will be on for the // next period. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Mine another period worth of blocks, signaling the new bit. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip(); // After one period of setting the bit on each block, it should have locked in. // We keep setting the bit for one more period though, until activation. - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1<<bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Now check that we keep mining the block until the end of this period, and // then stop at the beginning of the next period. lastBlock = secondChain.Mine((params.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); if (lastBlock->nHeight + 1 < min_activation_height) { // check signalling continues while min_activation_height is not reached lastBlock = secondChain.Mine(min_activation_height - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then reach min_activation_height, which was already REQUIRE'd to start a new period lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); } // Check that we don't signal after activation - BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, params) & (1<<bit), 0); + BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) @@ -425,7 +426,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) // not take precedence over STARTED/LOCKED_IN. So all softforks on // the same bit might overlap, even when non-overlapping start-end // times are picked. - const uint32_t dep_mask{VersionBitsMask(chainParams->GetConsensus(), dep)}; + const uint32_t dep_mask{g_versionbitscache.Mask(chainParams->GetConsensus(), dep)}; BOOST_CHECK(!(chain_all_vbits & dep_mask)); chain_all_vbits |= dep_mask; check_computeblockversion(chainParams->GetConsensus(), dep); diff --git a/src/txdb.cpp b/src/txdb.cpp index 762f71feb1..4b76bee5ab 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -168,9 +168,34 @@ bool CBlockTreeDB::ReadLastBlockFile(int &nFile) { return Read(DB_LAST_BLOCK, nFile); } -CCoinsViewCursor *CCoinsViewDB::Cursor() const +/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ +class CCoinsViewDBCursor: public CCoinsViewCursor { - CCoinsViewDBCursor *i = new CCoinsViewDBCursor(const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock()); +public: + // Prefer using CCoinsViewDB::Cursor() since we want to perform some + // cache warmup on instantiation. + CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn): + CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} + ~CCoinsViewDBCursor() {} + + bool GetKey(COutPoint &key) const override; + bool GetValue(Coin &coin) const override; + unsigned int GetValueSize() const override; + + bool Valid() const override; + void Next() override; + +private: + std::unique_ptr<CDBIterator> pcursor; + std::pair<char, COutPoint> keyTmp; + + friend class CCoinsViewDB; +}; + +std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const +{ + auto i = std::make_unique<CCoinsViewDBCursor>( + const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock()); /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ diff --git a/src/txdb.h b/src/txdb.h index 0cf7e2f1b8..845d80788f 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -60,7 +60,7 @@ public: uint256 GetBestBlock() const override; std::vector<uint256> GetHeadBlocks() const override; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor *Cursor() const override; + std::unique_ptr<CCoinsViewCursor> Cursor() const override; //! Attempt to update from an older database format. Returns whether an error occurred. bool Upgrade(); @@ -70,28 +70,6 @@ public: void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; -/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ -class CCoinsViewDBCursor: public CCoinsViewCursor -{ -public: - ~CCoinsViewDBCursor() {} - - bool GetKey(COutPoint &key) const override; - bool GetValue(Coin &coin) const override; - unsigned int GetValueSize() const override; - - bool Valid() const override; - void Next() override; - -private: - CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn): - CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} - std::unique_ptr<CDBIterator> pcursor; - std::pair<char, COutPoint> keyTmp; - - friend class CCoinsViewDB; -}; - /** Access to the block database (blocks/index/) */ class CBlockTreeDB : public CDBWrapper { diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 7c73a2d78d..c5a4bbf1b0 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -513,7 +513,6 @@ void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags) for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { const CTransaction& tx = it->GetTx(); LockPoints lp = it->GetLockPoints(); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp); CCoinsViewMemPool view_mempool(&active_chainstate.CoinsTip(), *this); if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags) @@ -638,10 +637,8 @@ void CTxMemPool::check(CChainState& active_chainstate) const uint64_t innerUsage = 0; CCoinsViewCache& active_coins_tip = active_chainstate.CoinsTip(); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(active_coins_tip)); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&active_coins_tip)); const int64_t spendheight = active_chainstate.m_chain.Height() + 1; - assert(g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate) == spendheight); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT std::list<const CTxMemPoolEntry*> waitingOnDependants; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { diff --git a/src/util/system.cpp b/src/util/system.cpp index 13ccf7463e..258ba2f235 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -6,6 +6,11 @@ #include <util/system.h> #ifdef ENABLE_EXTERNAL_SIGNER +#if defined(WIN32) && !defined(__kernel_entry) +// A workaround for boost 1.71 incompatibility with mingw-w64 compiler. +// For details see https://github.com/bitcoin/bitcoin/pull/22348. +#define __kernel_entry +#endif #include <boost/process.hpp> #endif // ENABLE_EXTERNAL_SIGNER @@ -620,14 +625,14 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV m_settings.forced_settings[SettingName(strArg)] = strValue; } -void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat) +void ArgsManager::AddCommand(const std::string& cmd, const std::string& help) { Assert(cmd.find('=') == std::string::npos); Assert(cmd.at(0) != '-'); LOCK(cs_args); m_accept_any_command = false; // latch to false - std::map<std::string, Arg>& arg_map = m_available_args[cat]; + std::map<std::string, Arg>& arg_map = m_available_args[OptionsCategory::COMMANDS]; auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND}); Assert(ret.second); // Fail on duplicate commands } @@ -1243,9 +1248,9 @@ void runCommand(const std::string& strCommand) } #endif -#ifdef ENABLE_EXTERNAL_SIGNER UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) { +#ifdef ENABLE_EXTERNAL_SIGNER namespace bp = boost::process; UniValue result_json; @@ -1277,8 +1282,10 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); return result_json; -} +#else + throw std::runtime_error("Compiled without external signing support (required for external signing)."); #endif // ENABLE_EXTERNAL_SIGNER +} void SetupEnvironment() { diff --git a/src/util/system.h b/src/util/system.h index c4317c62d0..3547bad585 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -102,7 +102,6 @@ std::string ShellEscape(const std::string& arg); #if HAVE_SYSTEM void runCommand(const std::string& strCommand); #endif -#ifdef ENABLE_EXTERNAL_SIGNER /** * Execute a command which returns JSON, and parse the result. * @@ -111,7 +110,6 @@ void runCommand(const std::string& strCommand); * @return parsed JSON */ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); -#endif // ENABLE_EXTERNAL_SIGNER /** * Most paths passed as configuration arguments are treated as relative to @@ -376,7 +374,7 @@ public: /** * Add subcommand */ - void AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat); + void AddCommand(const std::string& cmd, const std::string& help); /** * Add many hidden arguments diff --git a/src/validation.cpp b/src/validation.cpp index d5c4be6811..26333d7026 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -15,6 +15,7 @@ #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <cuckoocache.h> +#include <deploymentstatus.h> #include <flatfile.h> #include <hash.h> #include <index/blockfilterindex.h> @@ -101,21 +102,6 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn return false; } -ChainstateManager g_chainman; - -CChainState& ChainstateActive() -{ - LOCK(::cs_main); - assert(g_chainman.m_active_chainstate); - return *g_chainman.m_active_chainstate; -} - -CChain& ChainActive() -{ - LOCK(::cs_main); - return ::ChainstateActive().m_chain; -} - /** * Mutex to guard access to validation specific variables, such as reading * or changing the chainstate. @@ -161,7 +147,6 @@ void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false); CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) const { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.BlockIndex()) == std::addressof(m_block_index)); BlockMap::const_iterator it = m_block_index.find(hash); return it == m_block_index.end() ? nullptr : it->second; } @@ -170,7 +155,6 @@ CBlockIndex* BlockManager::FindForkInGlobalIndex(const CChain& chain, const CBlo { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); // Find the latest block common to locator and chain - we expect that // locator.vHave is sorted descending by height. for (const uint256& hash : locator.vHave) { @@ -198,7 +182,6 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i { AssertLockHeld(cs_main); assert(active_chain_tip); // TODO: Make active_chain_tip a reference - assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*active_chain_tip)); // By convention a negative value for flags indicates that the // current network-enforced consensus rules should be used. In @@ -237,7 +220,6 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) if (lp->maxInputBlock) { // Check whether ::ChainActive() is an extension of the block at which the LockPoints // calculation was valid. If not LockPoints are no longer valid - assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); if (!active_chain.Contains(lp->maxInputBlock)) { return false; } @@ -331,7 +313,6 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, siz std::vector<COutPoint> vNoSpendsRemaining; pool.TrimToSize(limit, &vNoSpendsRemaining); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_cache)); for (const COutPoint& removed : vNoSpendsRemaining) coins_cache.Uncache(removed); } @@ -339,7 +320,6 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, siz static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (active_chainstate.IsInitialBlockDownload()) return false; if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime<std::chrono::seconds>() - MAX_FEE_ESTIMATION_TIP_AGE)) @@ -349,24 +329,14 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_ return true; } -/* Make mempool consistent after a reorg, by re-adding or recursively erasing - * disconnected block transactions from the mempool, and also removing any - * other transactions from the mempool that are no longer valid given the new - * tip/height. - * - * Note: we assume that disconnectpool only contains transactions that are NOT - * confirmed in the current chain nor already in the mempool (otherwise, - * in-mempool descendants of such transactions would be removed). - * - * Passing fAddToMempool=false will skip trying to add the transactions back, - * and instead just erase from the mempool as needed. - */ - -static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& mempool, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, mempool.cs) +void CChainState::MaybeUpdateMempoolForReorg( + DisconnectedBlockTransactions& disconnectpool, + bool fAddToMempool) { + if (!m_mempool) return; + AssertLockHeld(cs_main); - AssertLockHeld(mempool.cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + AssertLockHeld(m_mempool->cs); std::vector<uint256> vHashUpdate; // disconnectpool's insertion_order index sorts the entries from // oldest to newest, but the oldest entry will be the last tx from the @@ -378,11 +348,13 @@ static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& me while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) { // ignore validation errors in resurrected transactions if (!fAddToMempool || (*it)->IsCoinBase() || - AcceptToMemoryPool(active_chainstate, mempool, *it, true /* bypass_limits */).m_result_type != MempoolAcceptResult::ResultType::VALID) { + AcceptToMemoryPool( + *this, *m_mempool, *it, true /* bypass_limits */).m_result_type != + MempoolAcceptResult::ResultType::VALID) { // If the transaction doesn't make it in to the mempool, remove any // transactions that depend on it (which would now be orphans). - mempool.removeRecursive(**it, MemPoolRemovalReason::REORG); - } else if (mempool.exists((*it)->GetHash())) { + m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG); + } else if (m_mempool->exists((*it)->GetHash())) { vHashUpdate.push_back((*it)->GetHash()); } ++it; @@ -393,12 +365,16 @@ static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& me // previously-confirmed transactions back to the mempool. // UpdateTransactionsFromBlock finds descendants of any transactions in // the disconnectpool that were added back and cleans up the mempool state. - mempool.UpdateTransactionsFromBlock(vHashUpdate); + m_mempool->UpdateTransactionsFromBlock(vHashUpdate); // We also need to remove any now-immature transactions - mempool.removeForReorg(active_chainstate, STANDARD_LOCKTIME_VERIFY_FLAGS); + m_mempool->removeForReorg(*this, STANDARD_LOCKTIME_VERIFY_FLAGS); // Re-limit mempool size, in case we added any transactions - LimitMempoolSize(mempool, active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); + LimitMempoolSize( + *m_mempool, + this->CoinsTip(), + gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, + std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); } /** @@ -433,7 +409,6 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS assert(txFrom->vout.size() > txin.prevout.n); assert(txFrom->vout[txin.prevout.n] == coin.out); } else { - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_tip)); const Coin& coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout); assert(!coinFromUTXOSet.IsSpent()); assert(coinFromUTXOSet.out == coin.out); @@ -454,7 +429,6 @@ public: m_limit_ancestor_size(gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000), m_limit_descendants(gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)), m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) { - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); } // We put the arguments we're handed into a struct, so we can pass them @@ -607,13 +581,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); if (!CheckFinalTx(m_active_chainstate.m_chain.Tip(), tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); - // is it already in the memory pool? - if (m_pool.exists(hash)) { + if (m_pool.exists(GenTxid(true, tx.GetWitnessHash()))) { + // Exact transaction already exists in the mempool. return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-in-mempool"); + } else if (m_pool.exists(GenTxid(false, tx.GetHash()))) { + // Transaction with the same non-witness data but different witness (same txid, different + // wtxid) already exists in the mempool. + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-same-nonwitness-data-in-mempool"); } // Check for conflicts with in-memory transactions @@ -663,7 +640,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) LockPoints lp; m_view.SetBackend(m_viewmempool); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); // do all inputs exist? for (const CTxIn& txin : tx.vin) { @@ -701,20 +677,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // be mined yet. // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's // backend was removed, it no longer pulls coins from the mempool. - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); if (!CheckSequenceLocks(m_active_chainstate.m_chain.Tip(), m_view, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_active_chainstate.m_blockman)); if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_blockman.GetSpendHeight(m_view), ws.m_base_fees)) { return false; // state filled in by CheckTxInputs } // Check for non-standard pay-to-script-hash in inputs - const auto& params = args.m_chainparams.GetConsensus(); - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); - auto taproot_state = VersionBitsState(m_active_chainstate.m_chain.Tip(), params, Consensus::DEPLOYMENT_TAPROOT, versionbitscache); - if (fRequireStandard && !AreInputsStandard(tx, m_view, taproot_state == ThresholdState::ACTIVE)) { + const bool taproot_active = DeploymentActiveAfter(m_active_chainstate.m_chain.Tip(), args.m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_TAPROOT); + if (fRequireStandard && !AreInputsStandard(tx, m_view, taproot_active)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); } @@ -739,7 +711,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), fSpendsCoinbase, nSigOpsCost, lp)); unsigned int nSize = entry->GetTxSize(); @@ -992,9 +963,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, P // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus()); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata, m_active_chainstate.CoinsTip())) { return error("%s: BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s", __func__, hash.ToString(), state.ToString()); @@ -1035,7 +1004,6 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // - it's not being re-added during a reorg which bypasses typical mempool fee limits // - the node is not behind // - the transaction is not dependent on any other transactions in the mempool - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx); // Store transaction in memory @@ -1043,7 +1011,6 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // trim mempool and check if tx was trimmed if (!bypass_limits) { - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); if (!m_pool.exists(hash)) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); @@ -1146,7 +1113,6 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, test_accept, /* m_allow_bip125_replacement */ true }; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { // Remove coins that were not present in the coins cache before calling @@ -1159,14 +1125,13 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp } // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits BlockValidationState state_dummy; - active_chainstate.FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC); + active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); return result; } MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx, bool bypass_limits, bool test_accept) { - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); return AcceptToMemoryPoolWithTime(Params(), pool, active_chainstate, tx, GetTime(), bypass_limits, test_accept); } @@ -1182,7 +1147,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx const CChainParams& chainparams = Params(); MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache, test_accept, /* m_allow_bip125_replacement */ false }; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args); // Uncache coins pertaining to transactions that were not submitted to the mempool. @@ -1245,8 +1209,9 @@ void CoinsViews::InitCache() m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview); } -CChainState::CChainState(CTxMemPool& mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash) +CChainState::CChainState(CTxMemPool* mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash) : m_mempool(mempool), + m_params(::Params()), m_blockman(blockman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -1321,7 +1286,6 @@ static void AlertNotify(const std::string& strMessage) void CChainState::CheckForkWarningConditions() { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); // Before we get past initial download, we cannot reliably alert about forks // (we assume we don't get stuck on a fork before finishing our initial sync) @@ -1340,7 +1304,6 @@ void CChainState::CheckForkWarningConditions() // Called both upon regular invalid block discovery *and* InvalidateBlock void CChainState::InvalidChainFound(CBlockIndex* pindexNew) { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork) pindexBestInvalid = pindexNew; if (pindexBestHeader != nullptr && pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) { @@ -1401,7 +1364,6 @@ bool CScriptCheck::operator()() { int BlockManager::GetSpendHeight(const CCoinsViewCache& inputs) { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); CBlockIndex* pindexPrev = LookupBlockIndex(inputs.GetBestBlock()); return pindexPrev->nHeight + 1; } @@ -1645,23 +1607,6 @@ void StopScriptCheckWorkerThreads() scriptcheckqueue.StopWorkerThreads(); } -VersionBitsCache versionbitscache GUARDED_BY(cs_main); - -int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) -{ - LOCK(cs_main); - int32_t nVersion = VERSIONBITS_TOP_BITS; - - for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { - ThresholdState state = VersionBitsState(pindexPrev, params, static_cast<Consensus::DeploymentPos>(i), versionbitscache); - if (state == ThresholdState::LOCKED_IN || state == ThresholdState::STARTED) { - nVersion |= VersionBitsMask(params, static_cast<Consensus::DeploymentPos>(i)); - } - } - - return nVersion; -} - /** * Threshold condition checker that triggers when unknown versionbits are seen on the network. */ @@ -1683,24 +1628,14 @@ public: return pindex->nHeight >= params.MinBIP9WarningHeight && ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && ((pindex->nVersion >> bit) & 1) != 0 && - ((ComputeBlockVersion(pindex->pprev, params) >> bit) & 1) == 0; + ((g_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> bit) & 1) == 0; } }; static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_main); -// 0.13.0 was shipped with a segwit deployment defined for testnet, but not for -// mainnet. We no longer need to support disabling the segwit deployment -// except for testing purposes, due to limitations of the functional test -// environment. See test/functional/p2p-segwit.py. -static bool IsScriptWitnessEnabled(const Consensus::Params& params) +static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams) { - return params.SegwitHeight != std::numeric_limits<int>::max(); -} - -static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - AssertLockHeld(cs_main); - unsigned int flags = SCRIPT_VERIFY_NONE; // BIP16 didn't become active until Apr 1 2012 (on mainnet, and @@ -1717,32 +1652,32 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens // Enforce WITNESS rules whenever P2SH is in effect (and the segwit // deployment is defined). - if (flags & SCRIPT_VERIFY_P2SH && IsScriptWitnessEnabled(consensusparams)) { + if (flags & SCRIPT_VERIFY_P2SH && DeploymentEnabled(consensusparams, Consensus::DEPLOYMENT_SEGWIT)) { flags |= SCRIPT_VERIFY_WITNESS; } - // Start enforcing the DERSIG (BIP66) rule - if (pindex->nHeight >= consensusparams.BIP66Height) { + // Enforce the DERSIG (BIP66) rule + if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_DERSIG)) { flags |= SCRIPT_VERIFY_DERSIG; } - // Start enforcing CHECKLOCKTIMEVERIFY (BIP65) rule - if (pindex->nHeight >= consensusparams.BIP65Height) { + // Enforce CHECKLOCKTIMEVERIFY (BIP65) + if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CLTV)) { flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; } - // Start enforcing BIP112 (CHECKSEQUENCEVERIFY) - if (pindex->nHeight >= consensusparams.CSVHeight) { + // Enforce CHECKSEQUENCEVERIFY (BIP112) + if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CSV)) { flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; } - // Start enforcing Taproot using versionbits logic. - if (VersionBitsState(pindex->pprev, consensusparams, Consensus::DEPLOYMENT_TAPROOT, versionbitscache) == ThresholdState::ACTIVE) { + // Enforce Taproot (BIP340-BIP342) + if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_TAPROOT)) { flags |= SCRIPT_VERIFY_TAPROOT; } - // Start enforcing BIP147 NULLDUMMY (activated simultaneously with segwit) - if (IsWitnessEnabled(pindex->pprev, consensusparams)) { + // Enforce BIP147 NULLDUMMY (activated simultaneously with segwit) + if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_SEGWIT)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } @@ -1764,7 +1699,7 @@ static int64_t nBlocksTotal = 0; * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, - CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck) + CCoinsViewCache& view, bool fJustCheck) { AssertLockHeld(cs_main); assert(pindex); @@ -1784,7 +1719,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // is enforced in ContextualCheckBlockHeader(); we wouldn't want to // re-enforce that rule here (at least until we make it impossible for // GetAdjustedTime() to go backward). - if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) { + if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) { if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware @@ -1802,7 +1737,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Special case for the genesis block, skipping connection of its transactions // (its coinbase is unspendable) - if (block.GetHash() == chainparams.GetConsensus().hashGenesisBlock) { + if (block.GetHash() == m_params.GetConsensus().hashGenesisBlock) { if (!fJustCheck) view.SetBestBlock(pindex->GetBlockHash()); return true; @@ -1834,7 +1769,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // artificially set the default assumed verified block further back. // The test against nMinimumChainWork prevents the skipping when denied access to any chain at // least as good as the expected chain. - fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, chainparams.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); + fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); } } } @@ -1914,9 +1849,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // be reset before it reaches block 1,983,702 and starts doing unnecessary // BIP30 checking again. assert(pindex->pprev); - CBlockIndex *pindexBIP34height = pindex->pprev->GetAncestor(chainparams.GetConsensus().BIP34Height); + CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(m_params.GetConsensus().BIP34Height); //Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond. - fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == chainparams.GetConsensus().BIP34Hash)); + fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == m_params.GetConsensus().BIP34Hash)); // TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a // consensus change that ensures coinbases at those heights can not @@ -1932,14 +1867,14 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, } } - // Start enforcing BIP68 (sequence locks) + // Enforce BIP68 (sequence locks) int nLockTimeFlags = 0; - if (pindex->nHeight >= chainparams.GetConsensus().CSVHeight) { + if (DeploymentActiveAt(*pindex, m_params.GetConsensus(), Consensus::DEPLOYMENT_CSV)) { nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; } // Get the script flags for this block - unsigned int flags = GetBlockScriptFlags(pindex, chainparams.GetConsensus()); + unsigned int flags = GetBlockScriptFlags(pindex, m_params.GetConsensus()); int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1; LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal); @@ -2029,7 +1964,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); - CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); + CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, m_params.GetConsensus()); if (block.vtx[0]->GetValueOut() > blockReward) { LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount"); @@ -2045,8 +1980,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, if (fJustCheck) return true; - if (!WriteUndoDataForBlock(blockundo, state, pindex, chainparams)) + if (!WriteUndoDataForBlock(blockundo, state, pindex, m_params)) { return false; + } if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) { pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); @@ -2066,20 +2002,18 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, return true; } -CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(const CTxMemPool* tx_pool) +CoinsCacheSizeState CChainState::GetCoinsCacheSizeState() { return this->GetCoinsCacheSizeState( - tx_pool, m_coinstip_cache_size_bytes, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); } CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( - const CTxMemPool* tx_pool, size_t max_coins_cache_size_bytes, size_t max_mempool_size_bytes) { - const int64_t nMempoolUsage = tx_pool ? tx_pool->DynamicMemoryUsage() : 0; + const int64_t nMempoolUsage = m_mempool ? m_mempool->DynamicMemoryUsage() : 0; int64_t cacheSize = CoinsTip().DynamicMemoryUsage(); int64_t nTotalSpace = max_coins_cache_size_bytes + std::max<int64_t>(max_mempool_size_bytes - nMempoolUsage, 0); @@ -2099,7 +2033,6 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( } bool CChainState::FlushStateToDisk( - const CChainParams& chainparams, BlockValidationState &state, FlushStateMode mode, int nManualPruneHeight) @@ -2119,7 +2052,7 @@ bool CChainState::FlushStateToDisk( bool fFlushForPrune = false; bool fDoFullFlush = false; - CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(&m_mempool); + CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(); LOCK(cs_LastBlockFile); if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) && !fReindex) { // make sure we don't prune above the blockfilterindexes bestblocks @@ -2136,7 +2069,7 @@ bool CChainState::FlushStateToDisk( } else { LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH); - m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload()); + m_blockman.FindFilesToPrune(setFilesToPrune, m_params.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload()); fCheckForPruning = false; } if (!setFilesToPrune.empty()) { @@ -2236,20 +2169,19 @@ bool CChainState::FlushStateToDisk( return true; } -void CChainState::ForceFlushStateToDisk() { +void CChainState::ForceFlushStateToDisk() +{ BlockValidationState state; - const CChainParams& chainparams = Params(); - if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS)) { + if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); } } -void CChainState::PruneAndFlush() { +void CChainState::PruneAndFlush() +{ BlockValidationState state; fCheckForPruning = true; - const CChainParams& chainparams = Params(); - - if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::NONE)) { + if (!this->FlushStateToDisk(state, FlushStateMode::NONE)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); } } @@ -2271,12 +2203,12 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn) res += warn; } -/** Check warning conditions and do some notifications on new chain tip set. */ -static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams, CChainState& active_chainstate) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +void CChainState::UpdateTip(const CBlockIndex* pindexNew) { // New best block - mempool.AddTransactionsUpdated(1); + if (m_mempool) { + m_mempool->AddTransactionsUpdated(1); + } { LOCK(g_best_block_mutex); @@ -2285,14 +2217,13 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } bilingual_str warning_messages; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); - if (!active_chainstate.IsInitialBlockDownload()) { + if (!this->IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); - ThresholdState state = checker.GetStateFor(pindex, chainParams.GetConsensus(), warningcache[bit]); + ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache[bit]); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { - const bilingual_str warning = strprintf(_("Warning: unknown new rules activated (versionbit %i)"), bit); + const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); if (state == ThresholdState::ACTIVE) { DoWarning(warning); } else { @@ -2301,37 +2232,37 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } } } - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), active_chainstate.CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), active_chainstate.CoinsTip().GetCacheSize(), + GuessVerificationProgress(m_params.TxData(), pindexNew), this->CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), this->CoinsTip().GetCacheSize(), !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : ""); } /** Disconnect m_chain's tip. * After calling, the mempool will be in an inconsistent state, with * transactions from disconnected blocks being added to disconnectpool. You - * should make the mempool consistent again by calling UpdateMempoolForReorg. + * should make the mempool consistent again by calling MaybeUpdateMempoolForReorg. * with cs_main held. * * If disconnectpool is nullptr, then no disconnected transactions are added to * disconnectpool (note that the caller is responsible for mempool consistency * in any case). */ -bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) +bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) { AssertLockHeld(cs_main); - AssertLockHeld(m_mempool.cs); + if (m_mempool) AssertLockHeld(m_mempool->cs); CBlockIndex *pindexDelete = m_chain.Tip(); assert(pindexDelete); // Read block from disk. std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); CBlock& block = *pblock; - if (!ReadBlockFromDisk(block, pindexDelete, chainparams.GetConsensus())) + if (!ReadBlockFromDisk(block, pindexDelete, m_params.GetConsensus())) { return error("DisconnectTip(): Failed to read block"); + } // Apply the block atomically to the chain state. int64_t nStart = GetTimeMicros(); { @@ -2344,10 +2275,11 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& } LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI); // Write the chain state to disk, if necessary. - if (!FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED)) + if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; + } - if (disconnectpool) { + if (disconnectpool && m_mempool) { // Save transactions to re-add to mempool at end of reorg for (auto it = block.vtx.rbegin(); it != block.vtx.rend(); ++it) { disconnectpool->addTransaction(*it); @@ -2355,14 +2287,14 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) { // Drop the earliest entry, and remove its children from the mempool. auto it = disconnectpool->queuedTx.get<insertion_order>().begin(); - m_mempool.removeRecursive(**it, MemPoolRemovalReason::REORG); + m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG); disconnectpool->removeEntry(it); } } m_chain.SetTip(pindexDelete->pprev); - UpdateTip(m_mempool, pindexDelete->pprev, chainparams, *this); + UpdateTip(pindexDelete->pprev); // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: GetMainSignals().BlockDisconnected(pblock, pindexDelete); @@ -2421,10 +2353,10 @@ public: * * The block is added to connectTrace if connection succeeds. */ -bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool) +bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) { AssertLockHeld(cs_main); - AssertLockHeld(m_mempool.cs); + if (m_mempool) AssertLockHeld(m_mempool->cs); assert(pindexNew->pprev == m_chain.Tip()); // Read block from disk. @@ -2432,8 +2364,9 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch std::shared_ptr<const CBlock> pthisBlock; if (!pblock) { std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>(); - if (!ReadBlockFromDisk(*pblockNew, pindexNew, chainparams.GetConsensus())) + if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_params.GetConsensus())) { return AbortNode(state, "Failed to read block"); + } pthisBlock = pblockNew; } else { pthisBlock = pblock; @@ -2445,7 +2378,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO); { CCoinsViewCache view(&CoinsTip()); - bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams); + bool rv = ConnectBlock(blockConnecting, state, pindexNew, view); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { if (state.IsInvalid()) @@ -2461,16 +2394,19 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, nTimeFlush * MILLI / nBlocksTotal); // Write the chain state to disk, if necessary. - if (!FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED)) + if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; + } int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4; LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal); // Remove conflicting transactions from the mempool.; - m_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight); - disconnectpool.removeForBlock(blockConnecting.vtx); + if (m_mempool) { + m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight); + disconnectpool.removeForBlock(blockConnecting.vtx); + } // Update m_chain & related variables. m_chain.SetTip(pindexNew); - UpdateTip(m_mempool, pindexNew, chainparams, *this); + UpdateTip(pindexNew); int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal); @@ -2557,11 +2493,10 @@ void CChainState::PruneBlockIndexCandidates() { * * @returns true unless a system error occurred */ -bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) +bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { AssertLockHeld(cs_main); - AssertLockHeld(m_mempool.cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + if (m_mempool) AssertLockHeld(m_mempool->cs); const CBlockIndex* pindexOldTip = m_chain.Tip(); const CBlockIndex* pindexFork = m_chain.FindFork(pindexMostWork); @@ -2570,10 +2505,10 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai bool fBlocksDisconnected = false; DisconnectedBlockTransactions disconnectpool; while (m_chain.Tip() && m_chain.Tip() != pindexFork) { - if (!DisconnectTip(state, chainparams, &disconnectpool)) { + if (!DisconnectTip(state, &disconnectpool)) { // This is likely a fatal error, but keep the mempool consistent, // just in case. Only remove from the mempool in this case. - UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); + MaybeUpdateMempoolForReorg(disconnectpool, false); // If we're unable to disconnect a block during normal operation, // then that is a failure of our local system -- we should abort @@ -2603,7 +2538,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai // Connect new blocks. for (CBlockIndex* pindexConnect : reverse_iterate(vpindexToConnect)) { - if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { + if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (state.IsInvalid()) { // The block violates a consensus rule. if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { @@ -2617,7 +2552,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai // A system error occurred (disk space, database error, ...). // Make the mempool consistent with the current tip, just in case // any observers try to use it before shutdown. - UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); + MaybeUpdateMempoolForReorg(disconnectpool, false); return false; } } else { @@ -2634,9 +2569,9 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai if (fBlocksDisconnected) { // If any blocks were disconnected, disconnectpool may be non empty. Add // any disconnected transactions back to the mempool. - UpdateMempoolForReorg(*this, m_mempool, disconnectpool, true); + MaybeUpdateMempoolForReorg(disconnectpool, true); } - m_mempool.check(*this); + if (m_mempool) m_mempool->check(*this); CheckForkWarningConditions(); @@ -2661,7 +2596,6 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { if (pindexHeader != pindexHeaderOld) { fNotify = true; - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); fInitialBlockDownload = chainstate.IsInitialBlockDownload(); pindexHeaderOld = pindexHeader; } @@ -2681,7 +2615,8 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { } } -bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { +bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock) +{ // Note that while we're often called here from ProcessNewBlock, this is // far from a guarantee. Things in the P2P/RPC will often end up calling // us in the middle of ProcessNewBlock - do not assume pblock is set @@ -2708,7 +2643,8 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar { LOCK(cs_main); - LOCK(m_mempool.cs); // Lock transaction pool for at least as long as it takes for connectTrace to be consumed + // Lock transaction pool for at least as long as it takes for connectTrace to be consumed + LOCK(MempoolMutex()); CBlockIndex* starting_tip = m_chain.Tip(); bool blocks_connected = false; do { @@ -2727,7 +2663,7 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar bool fInvalidFound = false; std::shared_ptr<const CBlock> nullBlockPtr; - if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) { + if (!ActivateBestChainStep(state, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) { // A system error occurred return false; } @@ -2769,17 +2705,17 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar // that the best block hash is non-null. if (ShutdownRequested()) break; } while (pindexNewTip != pindexMostWork); - CheckBlockIndex(chainparams.GetConsensus()); + CheckBlockIndex(); // Write changes periodically to disk, after relay. - if (!FlushStateToDisk(chainparams, state, FlushStateMode::PERIODIC)) { + if (!FlushStateToDisk(state, FlushStateMode::PERIODIC)) { return false; } return true; } -bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) +bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) { { LOCK(cs_main); @@ -2805,10 +2741,10 @@ bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& } } - return ActivateBestChain(state, params, std::shared_ptr<const CBlock>()); + return ActivateBestChain(state, std::shared_ptr<const CBlock>()); } -bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) +bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) { // Genesis block can't be invalidated assert(pindex); @@ -2858,7 +2794,9 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam LimitValidationInterfaceQueue(); LOCK(cs_main); - LOCK(m_mempool.cs); // Lock for as long as disconnectpool is in scope to make sure UpdateMempoolForReorg is called after DisconnectTip without unlocking in between + // Lock for as long as disconnectpool is in scope to make sure MaybeUpdateMempoolForReorg is + // called after DisconnectTip without unlocking in between + LOCK(MempoolMutex()); if (!m_chain.Contains(pindex)) break; pindex_was_in_chain = true; CBlockIndex *invalid_walk_tip = m_chain.Tip(); @@ -2866,14 +2804,13 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam // ActivateBestChain considers blocks already in m_chain // unconditionally valid already, so force disconnect away from it. DisconnectedBlockTransactions disconnectpool; - bool ret = DisconnectTip(state, chainparams, &disconnectpool); + bool ret = DisconnectTip(state, &disconnectpool); // DisconnectTip will add transactions to disconnectpool. // Adjust the mempool to be consistent with the new tip, adding // transactions back to the mempool if disconnecting was successful, // and we're not doing a very deep invalidation (in which case // keeping the mempool up to date is probably futile anyway). - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); - UpdateMempoolForReorg(*this, m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); + MaybeUpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); if (!ret) return false; assert(invalid_walk_tip->pprev == m_chain.Tip()); @@ -2909,7 +2846,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam to_mark_failed = invalid_walk_tip; } - CheckBlockIndex(chainparams.GetConsensus()); + CheckBlockIndex(); { LOCK(cs_main); @@ -3020,7 +2957,7 @@ CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) } /** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */ -void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) +void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) { pindexNew->nTx = block.vtx.size(); pindexNew->nChainTx = 0; @@ -3028,7 +2965,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi pindexNew->nDataPos = pos.nPos; pindexNew->nUndoPos = 0; pindexNew->nStatus |= BLOCK_HAVE_DATA; - if (IsWitnessEnabled(pindexNew->pprev, consensusParams)) { + if (DeploymentActiveAt(*pindexNew, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { pindexNew->nStatus |= BLOCK_OPT_WITNESS; } pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); @@ -3149,17 +3086,11 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu return true; } -bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params) -{ - int height = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; - return (height >= params.SegwitHeight); -} - void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) { int commitpos = GetWitnessCommitmentIndex(block); static const std::vector<unsigned char> nonce(32, 0x00); - if (commitpos != NO_WITNESS_COMMITMENT && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) { + if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) { CMutableTransaction tx(*block.vtx[0]); tx.vin[0].scriptWitness.stack.resize(1); tx.vin[0].scriptWitness.stack[0] = nonce; @@ -3172,7 +3103,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc std::vector<unsigned char> commitment; int commitpos = GetWitnessCommitmentIndex(block); std::vector<unsigned char> ret(32, 0x00); - if (consensusParams.SegwitHeight != std::numeric_limits<int>::max()) { + if (DeploymentEnabled(consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { if (commitpos == NO_WITNESS_COMMITMENT) { uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr); CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot); @@ -3203,7 +3134,6 @@ CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { const uint256& hash = i.second; - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { return pindex; @@ -3236,7 +3166,6 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). - assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); @@ -3252,13 +3181,13 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); - // Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded: - // check for version 2, 3 and 4 upgrades - if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || - (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || - (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) + // Reject blocks with outdated version + if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) || + (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_DERSIG)) || + (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CLTV))) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); + } return true; } @@ -3273,9 +3202,9 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat { const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; - // Start enforcing BIP113 (Median Time Past). + // Enforce BIP113 (Median Time Past). int nLockTimeFlags = 0; - if (nHeight >= consensusParams.CSVHeight) { + if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV)) { assert(pindexPrev != nullptr); nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; } @@ -3292,7 +3221,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat } // Enforce rule that the coinbase starts with serialized block height - if (nHeight >= consensusParams.BIP34Height) + if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) { CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || @@ -3310,7 +3239,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are // multiple, the last one is used. bool fHaveWitness = false; - if (nHeight >= consensusParams.SegwitHeight) { + if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { int commitpos = GetWitnessCommitmentIndex(block); if (commitpos != NO_WITNESS_COMMITMENT) { bool malleated = false; @@ -3440,7 +3369,6 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS // Exposed wrapper for AcceptBlockHeader bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) { - assert(std::addressof(::ChainstateActive()) == std::addressof(ActiveChainstate())); AssertLockNotHeld(cs_main); { LOCK(cs_main); @@ -3448,7 +3376,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast bool accepted = m_blockman.AcceptBlockHeader( header, state, chainparams, &pindex); - ActiveChainstate().CheckBlockIndex(chainparams.GetConsensus()); + ActiveChainstate().CheckBlockIndex(); if (!accepted) { return false; @@ -3467,7 +3395,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) +bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) { const CBlock& block = *pblock; @@ -3477,8 +3405,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - bool accepted_header = m_blockman.AcceptBlockHeader(block, state, chainparams, &pindex); - CheckBlockIndex(chainparams.GetConsensus()); + bool accepted_header = m_blockman.AcceptBlockHeader(block, state, m_params, &pindex); + CheckBlockIndex(); if (!accepted_header) return false; @@ -3515,8 +3443,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block if (pindex->nChainWork < nMinimumChainWork) return true; } - if (!CheckBlock(block, state, chainparams.GetConsensus()) || - !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { + if (!CheckBlock(block, state, m_params.GetConsensus()) || + !ContextualCheckBlock(block, state, m_params.GetConsensus(), pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(pindex); @@ -3531,21 +3459,20 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // Write block to history file if (fNewBlock) *fNewBlock = true; - assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { - FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, chainparams, dbp); + FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, m_params, dbp); if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; } - ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); + ReceivedBlockTransactions(block, pindex, blockPos); } catch (const std::runtime_error& e) { return AbortNode(state, std::string("System error: ") + e.what()); } - FlushStateToDisk(chainparams, state, FlushStateMode::NONE); + FlushStateToDisk(state, FlushStateMode::NONE); - CheckBlockIndex(chainparams.GetConsensus()); + CheckBlockIndex(); return true; } @@ -3553,7 +3480,6 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) { AssertLockNotHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(ActiveChainstate())); { CBlockIndex *pindex = nullptr; @@ -3572,7 +3498,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s bool ret = CheckBlock(*block, state, chainparams.GetConsensus()); if (ret) { // Store to disk - ret = ActiveChainstate().AcceptBlock(block, state, chainparams, &pindex, force_processing, nullptr, new_block); + ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block); } if (!ret) { GetMainSignals().BlockChecked(*block, state); @@ -3583,8 +3509,9 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s NotifyHeaderTip(ActiveChainstate()); BlockValidationState state; // Only used to report errors, not invalidity - ignore it - if (!ActiveChainstate().ActivateBestChain(state, chainparams, block)) + if (!ActiveChainstate().ActivateBestChain(state, block)) { return error("%s: ActivateBestChain failed (%s)", __func__, state.ToString()); + } return true; } @@ -3598,7 +3525,6 @@ bool TestBlockValidity(BlockValidationState& state, bool fCheckMerkleRoot) { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip()); CCoinsViewCache viewNew(&chainstate.CoinsTip()); uint256 block_hash(block.GetHash()); @@ -3608,15 +3534,15 @@ bool TestBlockValidity(BlockValidationState& state, indexDummy.phashBlock = &block_hash; // NOTE: CheckBlockHeader is called by CheckBlock - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainstate.m_blockman)); if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString()); - if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, chainparams, true)) + if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) { return false; + } assert(state.IsValid()); return true; @@ -3687,10 +3613,8 @@ void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nM void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight) { BlockValidationState state; - const CChainParams& chainparams = Params(); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (!active_chainstate.FlushStateToDisk( - chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) { + state, FlushStateMode::NONE, nManualPruneHeight)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); } } @@ -3840,11 +3764,10 @@ void BlockManager::Unload() { m_block_index.clear(); } -bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) +bool CChainState::LoadBlockIndexDB() { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!m_blockman.LoadBlockIndex( - chainparams.GetConsensus(), *pblocktree, + m_params.GetConsensus(), *pblocktree, setBlockIndexCandidates)) { return false; } @@ -3898,14 +3821,14 @@ bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) void CChainState::LoadMempool(const ArgsManager& args) { + if (!m_mempool) return; if (args.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); - ::LoadMempool(m_mempool, *this); + ::LoadMempool(*m_mempool, *this); } - m_mempool.SetIsLoaded(!ShutdownRequested()); + m_mempool->SetIsLoaded(!ShutdownRequested()); } -bool CChainState::LoadChainTip(const CChainParams& chainparams) +bool CChainState::LoadChainTip() { AssertLockHeld(cs_main); const CCoinsViewCache& coins_cache = CoinsTip(); @@ -3926,10 +3849,10 @@ bool CChainState::LoadChainTip(const CChainParams& chainparams) tip = m_chain.Tip(); LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n", - tip->GetBlockHash().ToString(), - m_chain.Height(), - FormatISO8601DateTime(tip->GetBlockTime()), - GuessVerificationProgress(chainparams.TxData(), tip)); + tip->GetBlockHash().ToString(), + m_chain.Height(), + FormatISO8601DateTime(tip->GetBlockTime()), + GuessVerificationProgress(m_params.TxData(), tip)); return true; } @@ -3951,7 +3874,6 @@ bool CVerifyDB::VerifyDB( { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); if (chainstate.m_chain.Tip() == nullptr || chainstate.m_chain.Tip()->pprev == nullptr) return true; @@ -4041,8 +3963,9 @@ bool CVerifyDB::VerifyDB( CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); - if (!chainstate.ConnectBlock(block, state, pindex, coins, chainparams)) + if (!chainstate.ConnectBlock(block, state, pindex, coins)) { return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + } if (ShutdownRequested()) return true; } } @@ -4054,11 +3977,11 @@ bool CVerifyDB::VerifyDB( } /** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ -bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) +bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) { // TODO: merge with ConnectBlock CBlock block; - if (!ReadBlockFromDisk(block, pindex, params.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindex, m_params.GetConsensus())) { return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } @@ -4074,7 +3997,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i return true; } -bool CChainState::ReplayBlocks(const CChainParams& params) +bool CChainState::ReplayBlocks() { LOCK(cs_main); @@ -4110,7 +4033,7 @@ bool CChainState::ReplayBlocks(const CChainParams& params) while (pindexOld != pindexFork) { if (pindexOld->nHeight > 0) { // Never disconnect the genesis block. CBlock block; - if (!ReadBlockFromDisk(block, pindexOld, params.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindexOld, m_params.GetConsensus())) { return error("RollbackBlock(): ReadBlockFromDisk() failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString()); } LogPrintf("Rolling back %s (%i)\n", pindexOld->GetBlockHash().ToString(), pindexOld->nHeight); @@ -4132,7 +4055,7 @@ bool CChainState::ReplayBlocks(const CChainParams& params) const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight); LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight); uiInterface.ShowProgress(_("Replaying blocks…").translated, (int) ((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)) , false); - if (!RollforwardBlock(pindex, cache, params)) return false; + if (!RollforwardBlock(pindex, cache)) return false; } cache.SetBestBlock(pindexNew->GetBlockHash()); @@ -4141,15 +4064,14 @@ bool CChainState::ReplayBlocks(const CChainParams& params) return true; } -bool CChainState::NeedsRedownload(const CChainParams& params) const +bool CChainState::NeedsRedownload() const { AssertLockHeld(cs_main); - // At and above params.SegwitHeight, segwit consensus rules must be validated + // At and above m_params.SegwitHeight, segwit consensus rules must be validated CBlockIndex* block{m_chain.Tip()}; - const int segwit_height{params.GetConsensus().SegwitHeight}; - while (block != nullptr && block->nHeight >= segwit_height) { + while (block != nullptr && DeploymentActiveAt(*block, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { if (!(block->nStatus & BLOCK_OPT_WITNESS)) { // block is insufficiently validated for a segwit client return true; @@ -4179,20 +4101,20 @@ void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman) nLastBlockFile = 0; setDirtyBlockIndex.clear(); setDirtyFileInfo.clear(); - versionbitscache.Clear(); + g_versionbitscache.Clear(); for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { warningcache[b].clear(); } fHavePruned = false; } -bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams) +bool ChainstateManager::LoadBlockIndex() { AssertLockHeld(cs_main); // Load block index from databases bool needs_init = fReindex; if (!fReindex) { - bool ret = ActiveChainstate().LoadBlockIndexDB(chainparams); + bool ret = ActiveChainstate().LoadBlockIndexDB(); if (!ret) return false; needs_init = m_blockman.m_block_index.empty(); } @@ -4209,7 +4131,7 @@ bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams) return true; } -bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) +bool CChainState::LoadGenesisBlock() { LOCK(cs_main); @@ -4217,17 +4139,16 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) // m_blockman.m_block_index. Note that we can't use m_chain here, since it is // set based on the coins db, not the block index db, which is the only // thing loaded at this point. - if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) + if (m_blockman.m_block_index.count(m_params.GenesisBlock().GetHash())) return true; - assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { - const CBlock& block = chainparams.GenesisBlock(); - FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr); + const CBlock& block = m_params.GenesisBlock(); + FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, m_params, nullptr); if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); - ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); + ReceivedBlockTransactions(block, pindex, blockPos); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); } @@ -4235,7 +4156,7 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) return true; } -void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp) +void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) { // Map of disk positions for blocks with unknown parent (only used for reindex) static std::multimap<uint256, FlatFilePos> mapBlocksUnknownParent; @@ -4256,11 +4177,12 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f try { // locate a header unsigned char buf[CMessageHeader::MESSAGE_START_SIZE]; - blkdat.FindByte(chainparams.MessageStart()[0]); + blkdat.FindByte(m_params.MessageStart()[0]); nRewind = blkdat.GetPos()+1; blkdat >> buf; - if (memcmp(buf, chainparams.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) + if (memcmp(buf, m_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) { continue; + } // read size blkdat >> nSize; if (nSize < 80 || nSize > MAX_BLOCK_SERIALIZED_SIZE) @@ -4284,8 +4206,7 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f { LOCK(cs_main); // detect out of order blocks, and store them for later - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_blockman)); - if (hash != chainparams.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { + if (hash != m_params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), block.hashPrevBlock.ToString()); if (dbp) @@ -4294,32 +4215,28 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f } // process in case the block isn't known yet - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_blockman)); CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash); if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); - if (AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) { + if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) { nLoaded++; } if (state.IsError()) { break; } - } else if (hash != chainparams.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { - LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight); + } else if (hash != m_params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { + LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight); } } // Activate the genesis block so normal node progress can continue - if (hash == chainparams.GetConsensus().hashGenesisBlock) { + if (hash == m_params.GetConsensus().hashGenesisBlock) { BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); - if (!ActivateBestChain(state, chainparams, nullptr)) { + if (!ActivateBestChain(state, nullptr)) { break; } } - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); NotifyHeaderTip(*this); // Recursively process earlier encountered successors of this block @@ -4332,22 +4249,18 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f while (range.first != range.second) { std::multimap<uint256, FlatFilePos>::iterator it = range.first; std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>(); - if (ReadBlockFromDisk(*pblockrecursive, it->second, chainparams.GetConsensus())) - { + if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) { LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(), head.ToString()); LOCK(cs_main); BlockValidationState dummy; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); - if (AcceptBlock(pblockrecursive, dummy, chainparams, nullptr, true, &it->second, nullptr)) - { + if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) { nLoaded++; queue.push_back(pblockrecursive->GetHash()); } } range.first++; mapBlocksUnknownParent.erase(it); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); NotifyHeaderTip(*this); } } @@ -4361,7 +4274,7 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart); } -void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) +void CChainState::CheckBlockIndex() { if (!fCheckBlockIndex) { return; @@ -4415,7 +4328,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) // Begin: actual consistency checks. if (pindex->pprev == nullptr) { // Genesis block checks. - assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match. + assert(pindex->GetBlockHash() == m_params.GetConsensus().hashGenesisBlock); // Genesis block's hash must match. assert(pindex == m_chain.Genesis()); // The current active chain's genesis block must be this block. } if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) @@ -4571,16 +4484,14 @@ bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) this->ToString(), coinstip_size * (1.0 / 1024 / 1024)); BlockValidationState state; - const CChainParams& chainparams = Params(); - bool ret; if (coinstip_size > old_coinstip_size) { // Likely no need to flush if cache sizes have grown. - ret = FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED); + ret = FlushStateToDisk(state, FlushStateMode::IF_NEEDED); } else { // Otherwise, flush state to disk and deallocate the in-memory coins map. - ret = FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS); + ret = FlushStateToDisk(state, FlushStateMode::ALWAYS); CoinsTip().ReallocateCache(); } return ret; @@ -4628,7 +4539,6 @@ bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mocka } if (nTime > nNow - nExpiryTimeout) { LOCK(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (AcceptToMemoryPoolWithTime(chainparams, pool, active_chainstate, tx, nTime, false /* bypass_limits */, false /* test_accept */).m_result_type == MempoolAcceptResult::ResultType::VALID) { ++count; @@ -4779,7 +4689,8 @@ std::vector<CChainState*> ChainstateManager::GetAll() return out; } -CChainState& ChainstateManager::InitializeChainstate(CTxMemPool& mempool, const std::optional<uint256>& snapshot_blockhash) +CChainState& ChainstateManager::InitializeChainstate( + CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash) { bool is_snapshot = snapshot_blockhash.has_value(); std::unique_ptr<CChainState>& to_modify = @@ -4858,7 +4769,7 @@ bool ChainstateManager::ActivateSnapshot( } auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>( - this->ActiveChainstate().m_mempool, m_blockman, base_blockhash)); + /* mempool */ nullptr, m_blockman, base_blockhash)); { LOCK(::cs_main); @@ -4881,7 +4792,7 @@ bool ChainstateManager::ActivateSnapshot( LOCK(::cs_main); assert(!m_snapshot_chainstate); m_snapshot_chainstate.swap(snapshot_chainstate); - const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip(::Params()); + const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip(); assert(chaintip_loaded); m_active_chainstate = m_snapshot_chainstate.get(); @@ -4944,6 +4855,14 @@ bool ChainstateManager::PopulateAndValidateSnapshot( coins_count - coins_left); return false; } + if (coin.nHeight > base_height || + outpoint.n >= std::numeric_limits<decltype(outpoint.n)>::max() // Avoid integer wrap-around in coinstats.cpp:ApplyHash + ) { + LogPrintf("[snapshot] bad snapshot data after deserializing %d coins\n", + coins_count - coins_left); + return false; + } + coins_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin)); --coins_left; @@ -4966,7 +4885,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( } const auto snapshot_cache_state = WITH_LOCK(::cs_main, - return snapshot_chainstate.GetCoinsCacheSizeState(&snapshot_chainstate.m_mempool)); + return snapshot_chainstate.GetCoinsCacheSizeState()); if (snapshot_cache_state >= CoinsCacheSizeState::CRITICAL) { @@ -5055,7 +4974,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() // won't ask to rewind the entire assumed-valid chain on startup. - if (index->pprev && ::IsWitnessEnabled(index->pprev, ::Params().GetConsensus())) { + if (index->pprev && DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { index->nStatus |= BLOCK_OPT_WITNESS; } } diff --git a/src/validation.h b/src/validation.h index 0c396c92ee..9a2be3ad97 100644 --- a/src/validation.h +++ b/src/validation.h @@ -24,7 +24,6 @@ #include <sync.h> #include <txmempool.h> // For CTxMemPool::cs #include <txdb.h> -#include <versionbits.h> #include <serialize.h> #include <util/check.h> #include <util/hasher.h> @@ -345,10 +344,6 @@ bool TestBlockValidity(BlockValidationState& state, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Check whether witness commitments are required for a block, and whether to enforce NULLDUMMY (BIP 147) rules. - * Note that transaction witness validation rules are always enforced when P2SH is enforced. */ -bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params); - /** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams); @@ -592,8 +587,11 @@ protected: */ mutable std::atomic<bool> m_cached_finished_ibd{false}; - //! mempool that is kept in sync with the chain - CTxMemPool& m_mempool; + //! Optional mempool that is kept in sync with the chain. + //! Only the active chainstate has a mempool. + CTxMemPool* m_mempool; + + const CChainParams& m_params; //! Manages the UTXO set, which is a reflection of the contents of `m_chain`. std::unique_ptr<CoinsViews> m_coins_views; @@ -603,7 +601,10 @@ public: //! CChainState instances. BlockManager& m_blockman; - explicit CChainState(CTxMemPool& mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash = std::nullopt); + explicit CChainState( + CTxMemPool* mempool, + BlockManager& blockman, + std::optional<uint256> from_snapshot_blockhash = std::nullopt); /** * Initialize the CoinsViews UTXO set database management data structures. The in-memory @@ -680,7 +681,7 @@ public: EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Import blocks from an external file */ - void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp = nullptr); + void LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp = nullptr); /** * Update the on-disk chain state. @@ -694,8 +695,7 @@ public: * @returns true unless a system error occurred */ bool FlushStateToDisk( - const CChainParams& chainparams, - BlockValidationState &state, + BlockValidationState& state, FlushStateMode mode, int nManualPruneHeight = 0); @@ -723,37 +723,36 @@ public: */ bool ActivateBestChain( BlockValidationState& state, - const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock = nullptr) LOCKS_EXCLUDED(cs_main); - bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view); bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, - CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Apply the effects of a block disconnection on the UTXO set. - bool DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs); + bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); // Manual block validity manipulation: /** Mark a block as precious and reorganize. * * May not be called in a validationinterface callback. */ - bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); /** Mark a block as invalid. */ - bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); /** Remove invalidity status from a block and its descendants. */ void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Replay blocks that aren't fully applied to the database. */ - bool ReplayBlocks(const CChainParams& params); + bool ReplayBlocks(); /** Whether the chain state needs to be redownloaded due to lack of witness data */ - [[nodiscard]] bool NeedsRedownload(const CChainParams& params) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + [[nodiscard]] bool NeedsRedownload() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ - bool LoadGenesisBlock(const CChainParams& chainparams); + bool LoadGenesisBlock(); void PruneBlockIndexCandidates(); @@ -767,41 +766,66 @@ public: * * By default this only executes fully when using the Regtest chain; see: fCheckBlockIndex. */ - void CheckBlockIndex(const Consensus::Params& consensusParams); + void CheckBlockIndex(); /** Load the persisted mempool from disk */ void LoadMempool(const ArgsManager& args); /** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */ - bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main); //! Dictates whether we need to flush the cache to disk or not. //! //! @return the state of the size of the coins cache. - CoinsCacheSizeState GetCoinsCacheSizeState(const CTxMemPool* tx_pool) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + CoinsCacheSizeState GetCoinsCacheSizeState() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); CoinsCacheSizeState GetCoinsCacheSizeState( - const CTxMemPool* tx_pool, size_t max_coins_cache_size_bytes, size_t max_mempool_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); private: - bool ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs); - bool ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs); + bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); + bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); - void InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); - void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) EXCLUSIVE_LOCKS_REQUIRED(cs_main); void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main); void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + //! Indirection necessary to make lock annotations work with an optional mempool. + RecursiveMutex* MempoolMutex() const LOCK_RETURNED(m_mempool->cs) + { + return m_mempool ? &m_mempool->cs : nullptr; + } + + /** + * Make mempool consistent after a reorg, by re-adding or recursively erasing + * disconnected block transactions from the mempool, and also removing any + * other transactions from the mempool that are no longer valid given the new + * tip/height. + * + * Note: we assume that disconnectpool only contains transactions that are NOT + * confirmed in the current chain nor already in the mempool (otherwise, + * in-mempool descendants of such transactions would be removed). + * + * Passing fAddToMempool=false will skip trying to add the transactions back, + * and instead just erase from the mempool as needed. + */ + void MaybeUpdateMempoolForReorg( + DisconnectedBlockTransactions& disconnectpool, + bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); + + /** Check warning conditions and do some notifications on new chain tip set. */ + void UpdateTip(const CBlockIndex* pindexNew) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); friend ChainstateManager; }; @@ -891,10 +915,6 @@ private: CAutoFile& coins_file, const SnapshotMetadata& metadata); - // For access to m_active_chainstate. - friend CChainState& ChainstateActive(); - friend CChain& ChainActive(); - public: std::thread m_load_block; //! A single BlockManager instance is shared across each constructed @@ -916,7 +936,9 @@ public: // constructor //! @param[in] snapshot_blockhash If given, signify that this chainstate //! is based on a snapshot. - CChainState& InitializeChainstate(CTxMemPool& mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt) + CChainState& InitializeChainstate( + CTxMemPool* mempool, + const std::optional<uint256>& snapshot_blockhash = std::nullopt) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! Get all chainstates currently being used. @@ -1006,7 +1028,7 @@ public: bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); //! Load the block tree and coins database from disk, initializing state if we're running with -reindex - bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main); //! Unload block index and chain data before shutdown. void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); @@ -1017,27 +1039,17 @@ public: //! Check to see if caches are out of balance and if so, call //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); -}; - -/** DEPRECATED! Please use node.chainman instead. May only be used in validation.cpp internally */ -extern ChainstateManager g_chainman GUARDED_BY(::cs_main); - -/** Please prefer the identical ChainstateManager::ActiveChainstate */ -CChainState& ChainstateActive(); -/** Please prefer the identical ChainstateManager::ActiveChain */ -CChain& ChainActive(); + ~ChainstateManager() { + LOCK(::cs_main); + UnloadBlockIndex(/* mempool */ nullptr, *this); + Reset(); + } +}; /** Global variable that points to the active block tree (protected by cs_main) */ extern std::unique_ptr<CBlockTreeDB> pblocktree; -extern VersionBitsCache versionbitscache; - -/** - * Determine what nVersion a new block should use. - */ -int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params); - using FopenFn = std::function<FILE*(const fs::path&, const char*)>; /** Dump the mempool to disk. */ diff --git a/src/versionbits.cpp b/src/versionbits.cpp index df2ec4e056..94c3c9559f 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -190,29 +190,48 @@ public: } // namespace -ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) +ThresholdState VersionBitsCache::State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { - return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]); + LOCK(m_mutex); + return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]); } -BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) +BIP9Stats VersionBitsCache::Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params); } -int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) +int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) { - return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]); + LOCK(m_mutex); + return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, m_caches[pos]); } -uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos) +uint32_t VersionBitsCache::Mask(const Consensus::Params& params, Consensus::DeploymentPos pos) { return VersionBitsConditionChecker(pos).Mask(params); } +int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) +{ + LOCK(m_mutex); + int32_t nVersion = VERSIONBITS_TOP_BITS; + + for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { + Consensus::DeploymentPos pos = static_cast<Consensus::DeploymentPos>(i); + ThresholdState state = VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, m_caches[pos]); + if (state == ThresholdState::LOCKED_IN || state == ThresholdState::STARTED) { + nVersion |= Mask(params, pos); + } + } + + return nVersion; +} + void VersionBitsCache::Clear() { + LOCK(m_mutex); for (unsigned int d = 0; d < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; d++) { - caches[d].clear(); + m_caches[d].clear(); } } diff --git a/src/versionbits.h b/src/versionbits.h index 634a848ef5..0b2f4a0258 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -6,6 +6,8 @@ #define BITCOIN_VERSIONBITS_H #include <chain.h> +#include <sync.h> + #include <map> /** What block version to use for new blocks (pre versionbits) */ @@ -71,21 +73,31 @@ public: int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; }; -/** BIP 9 allows multiple softforks to be deployed in parallel. We cache per-period state for every one of them - * keyed by the bit position used to signal support. */ -struct VersionBitsCache +/** BIP 9 allows multiple softforks to be deployed in parallel. We cache + * per-period state for every one of them. */ +class VersionBitsCache { - ThresholdConditionCache caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; +private: + Mutex m_mutex; + ThresholdConditionCache m_caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] GUARDED_BY(m_mutex); + +public: + /** Get the numerical statistics for a given deployment for the signalling period that includes the block after pindexPrev. */ + static BIP9Stats Statistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + + static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); + + /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ + ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + + /** Get the block height at which the BIP9 deployment switched into the state for the block after pindexPrev. */ + int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + + /** Determine what nVersion a new block should use + */ + int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params); void Clear(); }; -/** Get the BIP9 state for a given deployment at the current tip. */ -ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); -/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */ -BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); -/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ -int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); -uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos); - #endif // BITCOIN_VERSIONBITS_H diff --git a/src/versionbitsinfo.cpp b/src/versionbitsinfo.cpp deleted file mode 100644 index fa41bad46d..0000000000 --- a/src/versionbitsinfo.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2016-2020 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include <versionbitsinfo.h> - -#include <consensus/params.h> - -const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = { - { - /*.name =*/ "testdummy", - /*.gbt_force =*/ true, - }, - { - /*.name =*/ "taproot", - /*.gbt_force =*/ true, - }, -}; diff --git a/src/versionbitsinfo.h b/src/versionbitsinfo.h deleted file mode 100644 index a7822bc747..0000000000 --- a/src/versionbitsinfo.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2016-2018 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_VERSIONBITSINFO_H -#define BITCOIN_VERSIONBITSINFO_H - -struct VBDeploymentInfo { - /** Deployment name */ - const char *name; - /** Whether GBT clients can safely ignore this rule in simplified usage */ - bool gbt_force; -}; - -extern const struct VBDeploymentInfo VersionBitsDeploymentInfo[]; - -#endif // BITCOIN_VERSIONBITSINFO_H diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp index fe2c810afa..efef1ec754 100644 --- a/src/wallet/external_signer_scriptpubkeyman.cpp +++ b/src/wallet/external_signer_scriptpubkeyman.cpp @@ -13,8 +13,6 @@ #include <utility> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor> desc) { LOCK(cs_desc_man); @@ -62,10 +60,10 @@ bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, c } // If sign is true, transaction must previously have been filled -TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (!sign) { - return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed); + return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed); } // Already complete if every input is now signed @@ -84,5 +82,3 @@ TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransact FinalizePSBT(psbt); // This won't work in a multisig setup return TransactionError::OK; } - -#endif diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h index 1786958912..61df3d0015 100644 --- a/src/wallet/external_signer_scriptpubkeyman.h +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H #define BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H -#ifdef ENABLE_EXTERNAL_SIGNER #include <wallet/scriptpubkeyman.h> #include <memory> @@ -16,8 +15,8 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor) : DescriptorScriptPubKeyMan(storage, descriptor) {} - ExternalSignerScriptPubKeyMan(WalletStorage& storage, bool internal) - : DescriptorScriptPubKeyMan(storage, internal) + ExternalSignerScriptPubKeyMan(WalletStorage& storage) + : DescriptorScriptPubKeyMan(storage) {} /** Provide a descriptor at setup time @@ -29,8 +28,6 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; }; -#endif - #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index ee92316b89..e33adf94c9 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -349,9 +349,9 @@ public: TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, + size_t* n_signed, PartiallySignedTransaction& psbtx, - bool& complete, - size_t* n_signed) override + bool& complete) override { return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed); } @@ -475,13 +475,13 @@ public: std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override { return MakeHandler(m_wallet->NotifyAddressBookChanged.connect( - [fn](CWallet*, const CTxDestination& address, const std::string& label, bool is_mine, - const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); })); + [fn](const CTxDestination& address, const std::string& label, bool is_mine, + const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); })); } std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override { return MakeHandler(m_wallet->NotifyTransactionChanged.connect( - [fn](CWallet*, const uint256& txid, ChangeType status) { fn(txid, status); })); + [fn](const uint256& txid, ChangeType status) { fn(txid, status); })); } std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 726b13beac..ea97b339cf 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -286,6 +286,9 @@ RPCHelpMan importaddress() if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); } + if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); + } pwallet->MarkDirty(); @@ -962,6 +965,9 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } + if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); + } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { @@ -1086,6 +1092,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } + if (parsed_desc->GetOutputType() == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets"); + } have_solving_data = parsed_desc->IsSolvable(); const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; @@ -1530,6 +1539,18 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } } + // Taproot descriptors cannot be imported if Taproot is not yet active. + // Check if this is a Taproot descriptor + CTxDestination dest; + ExtractDestination(scripts[0], dest); + if (std::holds_alternative<WitnessV1Taproot>(dest)) { + // Check if Taproot is active + if (!wallet.chain().isTaprootActive()) { + // Taproot is not active, raise an error + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import tr() descriptor when Taproot is not active"); + } + } + // If private keys are enabled, check some things. if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { @@ -1545,9 +1566,8 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c // Check if the wallet already contains the descriptor auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); if (existing_spk_manager) { - LOCK(existing_spk_manager->cs_desc_man); - if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) { - throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end)); + if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, error); } } @@ -1564,16 +1584,16 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } else { wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); } + } else { + if (w_desc.descriptor->GetOutputType()) { + wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); + } } result.pushKV("success", UniValue(true)); } catch (const UniValue& e) { result.pushKV("success", UniValue(false)); result.pushKV("error", e); - } catch (...) { - result.pushKV("success", UniValue(false)); - - result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); } if (warnings.size()) result.pushKV("warnings", warnings); return result; @@ -1766,8 +1786,6 @@ RPCHelpMan listdescriptors() throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); } - EnsureWalletIsUnlocked(*wallet); - LOCK(wallet->cs_wallet); UniValue descriptors(UniValue::VARR); @@ -1781,7 +1799,7 @@ RPCHelpMan listdescriptors() LOCK(desc_spk_man->cs_desc_man); const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); std::string descriptor; - if (!desc_spk_man->GetDescriptorString(descriptor, false)) { + if (!desc_spk_man->GetDescriptorString(descriptor)) { throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string."); } spk.pushKV("desc", descriptor); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 534c974178..f1d5117415 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -269,6 +269,9 @@ static RPCHelpMan getnewaddress() if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } + if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); + } } CTxDestination dest; @@ -313,6 +316,9 @@ static RPCHelpMan getrawchangeaddress() if (!ParseOutputType(request.params[0].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); } + if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); + } } CTxDestination dest; @@ -1004,6 +1010,9 @@ static RPCHelpMan addmultisigaddress() if (!ParseOutputType(request.params[3].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); } + if (output_type == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets"); + } } // Construct using pay-to-script-hash: @@ -3320,7 +3329,8 @@ RPCHelpMan signrawtransactionwithwallet() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -3542,7 +3552,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) } else { PartiallySignedTransaction psbtx(mtx); bool complete = false; - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false /* sign */, true /* bip32derivs */); CHECK_NONFATAL(err == TransactionError::OK); CHECK_NONFATAL(!complete); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -3847,17 +3857,22 @@ RPCHelpMan getaddressinfo() isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = provider && IsSolvable(*provider, scriptPubKey); - ret.pushKV("solvable", solvable); - - if (solvable) { - ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); + if (provider) { + auto inferred = InferDescriptor(scriptPubKey, *provider); + bool solvable = inferred->IsSolvable() || IsSolvable(*provider, scriptPubKey); + ret.pushKV("solvable", solvable); + if (solvable) { + ret.pushKV("desc", inferred->ToString()); + } + } else { + ret.pushKV("solvable", false); } + DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey)); if (desc_spk_man) { std::string desc_str; - if (desc_spk_man->GetDescriptorString(desc_str, false)) { + if (desc_spk_man->GetDescriptorString(desc_str)) { ret.pushKV("parent_desc", desc_str); } } @@ -4175,8 +4190,8 @@ static RPCHelpMan send() // First fill transaction with our data without signing, // so external signers are not asked sign more than once. bool complete; - pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true); - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); + pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } @@ -4291,7 +4306,8 @@ static RPCHelpMan walletprocesspsbt() { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating"}, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -4315,6 +4331,11 @@ static RPCHelpMan walletprocesspsbt() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; + const CWallet& wallet{*pwallet}; + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + wallet.BlockUntilSyncedToCurrentChain(); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); // Unserialize the transaction @@ -4331,7 +4352,7 @@ static RPCHelpMan walletprocesspsbt() bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); bool complete = true; - const TransactionError err = pwallet->FillPSBT(psbtx, complete, nHashType, sign, bip32derivs); + const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs)}; if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } @@ -4429,6 +4450,11 @@ static RPCHelpMan walletcreatefundedpsbt() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; + CWallet& wallet{*pwallet}; + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + wallet.BlockUntilSyncedToCurrentChain(); + RPCTypeCheck(request.params, { UniValue::VARR, UniValueType(), // ARR or OBJ, checked later @@ -4440,7 +4466,7 @@ static RPCHelpMan walletcreatefundedpsbt() CAmount fee; int change_position; - bool rbf = pwallet->m_signal_rbf; + bool rbf{wallet.m_signal_rbf}; const UniValue &replaceable_arg = request.params[3]["replaceable"]; if (!replaceable_arg.isNull()) { RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL); @@ -4451,7 +4477,7 @@ static RPCHelpMan walletcreatefundedpsbt() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_add_inputs = rawTx.vin.size() == 0; - FundTransaction(*pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); + FundTransaction(wallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); @@ -4459,7 +4485,7 @@ static RPCHelpMan walletcreatefundedpsbt() // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool complete = true; - const TransactionError err = pwallet->FillPSBT(psbtx, complete, 1, false, bip32derivs); + const TransactionError err{wallet.FillPSBT(psbtx, complete, 1, false, bip32derivs)}; if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 2eb9ca5c6d..73433214f1 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -13,7 +13,6 @@ #include <util/system.h> #include <util/time.h> #include <util/translation.h> -#include <external_signer.h> #include <wallet/scriptpubkeyman.h> #include <optional> @@ -23,6 +22,12 @@ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { + if (LEGACY_OUTPUT_TYPES.count(type) == 0) { + error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated; + return false; + } + assert(type != OutputType::BECH32M); + LOCK(cs_KeyStore); error.clear(); @@ -290,14 +295,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) +bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { + if (LEGACY_OUTPUT_TYPES.count(type) == 0) { + error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated; + return false; + } + assert(type != OutputType::BECH32M); + LOCK(cs_KeyStore); if (!CanGetAddresses(internal)) { + error = _("Error: Keypool ran out, please call keypoolrefill first").translated; return false; } if (!ReserveKeyFromKeyPool(index, keypool, internal)) { + error = _("Error: Keypool ran out, please call keypoolrefill first").translated; return false; } address = GetDestinationForKey(keypool.vchPubKey, type); @@ -597,7 +610,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con return SigningResult::SIGNING_FAILED; } -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -626,7 +639,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } SignatureData sigdata; input.FillSignatureData(sigdata); - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -1295,6 +1308,7 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type) { + assert(type != OutputType::BECH32M); // Remove from key pool WalletBatch batch(m_storage.GetDatabase()); batch.ErasePool(nIndex); @@ -1328,6 +1342,7 @@ void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, co bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal) { + assert(type != OutputType::BECH32M); if (!CanGetAddresses(internal)) { return false; } @@ -1396,6 +1411,7 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type) { + assert(type != OutputType::BECH32M); if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { CTxDestination witdest = WitnessV0KeyHash(key.GetID()); CScript witprog = GetScriptForDestination(witdest); @@ -1597,12 +1613,10 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const return set_address; } -void LegacyScriptPubKeyMan::SetInternal(bool internal) {} - bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { // Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later - if (!CanGetAddresses(m_internal)) { + if (!CanGetAddresses()) { error = "No addresses available"; return false; } @@ -1707,10 +1721,9 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle return true; } -bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) +bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { LOCK(cs_desc_man); - std::string error; bool result = GetNewDestination(type, address, error); index = m_wallet_descriptor.next_index - 1; return result; @@ -1790,34 +1803,10 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size) } m_map_pubkeys[pubkey] = i; } - // Write the cache - for (const auto& parent_xpub_pair : temp_cache.GetCachedParentExtPubKeys()) { - CExtPubKey xpub; - if (m_wallet_descriptor.cache.GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) { - if (xpub != parent_xpub_pair.second) { - throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub"); - } - continue; - } - if (!batch.WriteDescriptorParentCache(parent_xpub_pair.second, id, parent_xpub_pair.first)) { - throw std::runtime_error(std::string(__func__) + ": writing cache item failed"); - } - m_wallet_descriptor.cache.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second); - } - for (const auto& derived_xpub_map_pair : temp_cache.GetCachedDerivedExtPubKeys()) { - for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) { - CExtPubKey xpub; - if (m_wallet_descriptor.cache.GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) { - if (xpub != derived_xpub_pair.second) { - throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub"); - } - continue; - } - if (!batch.WriteDescriptorDerivedCache(derived_xpub_pair.second, id, derived_xpub_map_pair.first, derived_xpub_pair.first)) { - throw std::runtime_error(std::string(__func__) + ": writing cache item failed"); - } - m_wallet_descriptor.cache.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second); - } + // Merge and write the cache + DescriptorCache new_items = m_wallet_descriptor.cache.MergeAndDiff(temp_cache); + if (!batch.WriteDescriptorCacheItems(id, new_items)) { + throw std::runtime_error(std::string(__func__) + ": writing cache items failed"); } m_max_cached_index++; } @@ -1860,6 +1849,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const AssertLockHeld(cs_desc_man); assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + // Check if provided key already exists + if (m_map_keys.find(pubkey.GetID()) != m_map_keys.end() || + m_map_crypted_keys.find(pubkey.GetID()) != m_map_crypted_keys.end()) { + return true; + } + if (m_storage.HasEncryptionKeys()) { if (m_storage.IsLocked()) { return false; @@ -1879,8 +1874,14 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const } } -bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type) +bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal) { + if (addr_type == OutputType::BECH32M) { + // Don't allow setting up taproot descriptors yet + // TODO: Allow setting up taproot descriptors + return false; + } + LOCK(cs_desc_man); assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); @@ -1910,6 +1911,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix = "wpkh(" + xpub + "/84'"; break; } + case OutputType::BECH32M: assert(false); // TODO: Setup taproot descriptor } // no default case, so the compiler can warn about missing cases assert(!desc_prefix.empty()); @@ -1920,7 +1922,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix += "/0'"; } - std::string internal_path = m_internal ? "/1" : "/0"; + std::string internal_path = internal ? "/1" : "/0"; std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix; // Make the descriptor @@ -1975,13 +1977,6 @@ int64_t DescriptorScriptPubKeyMan::GetOldestKeyPoolTime() const return 0; } -size_t DescriptorScriptPubKeyMan::KeypoolCountExternalKeys() const -{ - if (m_internal) { - return 0; - } - return GetKeyPoolSize(); -} unsigned int DescriptorScriptPubKeyMan::GetKeyPoolSize() const { @@ -2083,7 +2078,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message, return SigningResult::OK; } -TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -2133,7 +2128,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } } - SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -2183,11 +2178,6 @@ uint256 DescriptorScriptPubKeyMan::GetID() const return id; } -void DescriptorScriptPubKeyMan::SetInternal(bool internal) -{ - this->m_internal = internal; -} - void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache) { LOCK(cs_desc_man); @@ -2268,15 +2258,75 @@ const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const return script_pub_keys; } -bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, bool priv) const +bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out) const { LOCK(cs_desc_man); - if (m_storage.IsLocked()) { - return false; + + FlatSigningProvider provider; + provider.keys = GetKeys(); + + return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache); +} + +void DescriptorScriptPubKeyMan::UpgradeDescriptorCache() +{ + LOCK(cs_desc_man); + if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) { + return; } + // Skip if we have the last hardened xpub cache + if (m_wallet_descriptor.cache.GetCachedLastHardenedExtPubKeys().size() > 0) { + return; + } + + // Expand the descriptor FlatSigningProvider provider; provider.keys = GetKeys(); + FlatSigningProvider out_keys; + std::vector<CScript> scripts_temp; + DescriptorCache temp_cache; + if (!m_wallet_descriptor.descriptor->Expand(0, provider, scripts_temp, out_keys, &temp_cache)){ + throw std::runtime_error("Unable to expand descriptor"); + } + + // Cache the last hardened xpubs + DescriptorCache diff = m_wallet_descriptor.cache.MergeAndDiff(temp_cache); + if (!WalletBatch(m_storage.GetDatabase()).WriteDescriptorCacheItems(GetID(), diff)) { + throw std::runtime_error(std::string(__func__) + ": writing cache items failed"); + } +} - return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, priv); +void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor) +{ + LOCK(cs_desc_man); + std::string error; + if (!CanUpdateToWalletDescriptor(descriptor, error)) { + throw std::runtime_error(std::string(__func__) + ": " + error); + } + + m_map_pubkeys.clear(); + m_map_script_pub_keys.clear(); + m_max_cached_index = -1; + m_wallet_descriptor = descriptor; +} + +bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error) +{ + LOCK(cs_desc_man); + if (!HasWalletDescriptor(descriptor)) { + error = "can only update matching descriptor"; + return false; + } + + if (descriptor.range_start > m_wallet_descriptor.range_start || + descriptor.range_end < m_wallet_descriptor.range_end) { + // Use inclusive range for error + error = strprintf("new range must include current range = [%d,%d]", + m_wallet_descriptor.range_start, + m_wallet_descriptor.range_end - 1); + return false; + } + + return true; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index b8e34fbac3..572a695662 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -181,7 +181,7 @@ public: virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; } virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; } - virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { return false; } + virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { return false; } virtual void KeepDestination(int64_t index, const OutputType& type) {} virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {} @@ -216,7 +216,6 @@ public: virtual int64_t GetOldestKeyPoolTime() const { return GetTime(); } - virtual size_t KeypoolCountExternalKeys() const { return 0; } virtual unsigned int GetKeyPoolSize() const { return 0; } virtual int64_t GetTimeFirstKey() const { return 0; } @@ -235,12 +234,10 @@ public: /** Sign a message with the given script */ virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; /** Adds script and derivation path information to a PSBT, and optionally signs it. */ - virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } + virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } virtual uint256 GetID() const { return uint256(); } - virtual void SetInternal(bool internal) {} - /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */ template<typename... Params> void WalletLogPrintf(std::string fmt, Params... parameters) const { @@ -254,6 +251,13 @@ public: boost::signals2::signal<void ()> NotifyCanGetAddressesChanged; }; +/** OutputTypes supported by the LegacyScriptPubKeyMan */ +static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES { + OutputType::LEGACY, + OutputType::P2SH_SEGWIT, + OutputType::BECH32, +}; + class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: @@ -357,7 +361,7 @@ public: bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override; void KeepDestination(int64_t index, const OutputType& type) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override; @@ -379,7 +383,7 @@ public: void RewriteDB() override; int64_t GetOldestKeyPoolTime() const override; - size_t KeypoolCountExternalKeys() const override; + size_t KeypoolCountExternalKeys() const; unsigned int GetKeyPoolSize() const override; int64_t GetTimeFirstKey() const override; @@ -394,12 +398,10 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; - void SetInternal(bool internal) override; - // Map from Key ID to key metadata. std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore); @@ -526,8 +528,6 @@ private: PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man); int32_t m_max_cached_index = -1; - bool m_internal = false; - KeyMap m_map_keys GUARDED_BY(cs_desc_man); CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man); @@ -553,9 +553,8 @@ public: : ScriptPubKeyMan(storage), m_wallet_descriptor(descriptor) {} - DescriptorScriptPubKeyMan(WalletStorage& storage, bool internal) - : ScriptPubKeyMan(storage), - m_internal(internal) + DescriptorScriptPubKeyMan(WalletStorage& storage) + : ScriptPubKeyMan(storage) {} mutable RecursiveMutex cs_desc_man; @@ -566,7 +565,7 @@ public: bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override; // Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file @@ -580,7 +579,7 @@ public: bool IsHDEnabled() const override; //! Setup descriptors based on the given CExtkey - bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type); + bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal); /** Provide a descriptor at setup time * Returns false if already setup or setup fails, true if setup is successful @@ -590,7 +589,6 @@ public: bool HavePrivateKeys() const override; int64_t GetOldestKeyPoolTime() const override; - size_t KeypoolCountExternalKeys() const override; unsigned int GetKeyPoolSize() const override; int64_t GetTimeFirstKey() const override; @@ -605,25 +603,27 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; - void SetInternal(bool internal) override; - void SetCache(const DescriptorCache& cache); bool AddKey(const CKeyID& key_id, const CKey& key); bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key); bool HasWalletDescriptor(const WalletDescriptor& desc) const; + void UpdateWalletDescriptor(WalletDescriptor& descriptor); + bool CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error); void AddDescriptorKey(const CKey& key, const CPubKey &pubkey); void WriteDescriptor(); const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); const std::vector<CScript> GetScriptPubKeys() const; - bool GetDescriptorString(std::string& out, bool priv) const; + bool GetDescriptorString(std::string& out) const; + + void UpgradeDescriptorCache(); }; #endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index c8ded4c51e..6a8df437ae 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -618,8 +618,9 @@ bool CWallet::CreateTransactionInternal( // Reserve a new key pair from key pool. If it fails, provide a dummy // destination in case we don't need change. CTxDestination dest; - if (!reservedest.GetReservedDestination(dest, true)) { - error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first."); + std::string dest_err; + if (!reservedest.GetReservedDestination(dest, true, dest_err)) { + error = strprintf(_("Transaction needs a change address, but we can't generate it. %s"), dest_err); } scriptChange = GetScriptForDestination(dest); // A valid destination implies a change script (and diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index ce7e661b67..1cefa386b7 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) // Try to sign the mutated input SignatureData sigdata; - BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK); + BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK); } BOOST_AUTO_TEST_CASE(parse_hd_keypath) diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 6a791748b4..a0070b8dd3 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -83,17 +83,17 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = ::ChainActive().Tip(); + CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = ::ChainActive().Tip(); + CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); // Verify ScanForWalletTransactions fails to read an unknown start block. { CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -112,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -138,7 +138,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -163,7 +163,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -180,10 +180,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = ::ChainActive().Tip(); + CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = ::ChainActive().Tip(); + CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); // Prune the older block file. { @@ -242,7 +242,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. - const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; + const int64_t BLOCK_TIME = m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); @@ -265,7 +265,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); AddWallet(wallet); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } JSONRPCRequest request; request.params.setArray(); @@ -286,7 +286,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); ::importwallet().HandleRequest(request); RemoveWallet(wallet, std::nullopt); @@ -313,9 +313,9 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) CWalletTx wtx(&wallet, m_coinbase_txns.back()); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 0); wtx.m_confirm = confirm; // Call GetImmatureCredit() once before adding the key to the wallet to @@ -483,16 +483,16 @@ public: wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); { LOCK2(wallet->cs_wallet, ::cs_main); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } wallet->LoadWallet(); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(*wallet); reserver.reserve(); - CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(m_node.chainman->ActiveChain().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); - BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height()); + BOOST_CHECK_EQUAL(result.last_scanned_block, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.last_scanned_height, m_node.chainman->ActiveChain().Height()); BOOST_CHECK(result.last_failed_block.IsNull()); } @@ -521,10 +521,10 @@ public: CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); - wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 1); it->second.m_confirm = confirm; return it->second; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4b6630de3c..27565aefc9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -8,6 +8,7 @@ #include <chain.h> #include <consensus/consensus.h> #include <consensus/validation.h> +#include <external_signer.h> #include <fs.h> #include <interfaces/chain.h> #include <interfaces/wallet.h> @@ -373,6 +374,19 @@ void CWallet::UpgradeKeyMetadata() SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); } +void CWallet::UpgradeDescriptorCache() +{ + if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) || IsLocked() || IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) { + return; + } + + for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) { + DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm); + desc_spkm->UpgradeDescriptorCache(); + } + SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED); +} + bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) { CCrypter crypter; @@ -389,6 +403,8 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key if (Unlock(_vMasterKey, accept_no_keys)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); + // Now that we've unlocked, upgrade the descriptor cache + UpgradeDescriptorCache(); return true; } } @@ -800,7 +816,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) success = false; } - NotifyTransactionChanged(this, originalHash, CT_UPDATED); + NotifyTransactionChanged(originalHash, CT_UPDATED); return success; } @@ -929,7 +945,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio wtx.MarkDirty(); // Notify UI of new or updated transaction - NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + NotifyTransactionChanged(hash, fInsertedNew ? CT_NEW : CT_UPDATED); #if HAVE_SYSTEM // notify an external script when a wallet transaction comes in or is updated @@ -1103,7 +1119,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); - NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED); + NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.hash == now) { @@ -1807,7 +1823,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase()); } std::map<int, std::string> input_errors; - return SignTransaction(tx, coins, SIGHASH_ALL, input_errors); + return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors); } bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const @@ -1830,6 +1846,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp if (n_signed) { *n_signed = 0; } + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { @@ -1856,7 +1873,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { int n_signed_this_spkm = 0; - TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm); + TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm); if (res != TransactionError::OK) { return res; } @@ -1907,7 +1924,13 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang int witnessversion = 0; std::vector<unsigned char> witnessprogram; if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { - return OutputType::BECH32; + if (GetScriptPubKeyMan(OutputType::BECH32M, true)) { + return OutputType::BECH32M; + } else if (GetScriptPubKeyMan(OutputType::BECH32, true)) { + return OutputType::BECH32; + } else { + return m_default_address_type; + } } } @@ -1936,7 +1959,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve for (const CTxIn& txin : tx->vin) { CWalletTx &coin = mapWallet.at(txin.prevout.hash); coin.MarkDirty(); - NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); + NotifyTransactionChanged(coin.GetHash(), CT_UPDATED); } // Get the inserted-CWalletTx from mapWallet so that the @@ -1991,7 +2014,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 for (const auto& txin : it->second.tx->vin) mapTxSpends.erase(txin.prevout); mapWallet.erase(it); - NotifyTransactionChanged(this, hash, CT_DELETED); + NotifyTransactionChanged(hash, CT_DELETED); } if (nZapSelectTxRet == DBErrors::NEED_REWRITE) @@ -2025,8 +2048,8 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add m_address_book[address].purpose = strPurpose; is_mine = IsMine(address) != ISMINE_NO; } - NotifyAddressBookChanged(this, address, strName, is_mine, - strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); + NotifyAddressBookChanged(address, strName, is_mine, + strPurpose, (fUpdated ? CT_UPDATED : CT_NEW)); if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose)) return false; return batch.WriteName(EncodeDestination(address), strName); @@ -2061,7 +2084,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address) is_mine = IsMine(address) != ISMINE_NO; } - NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED); + NotifyAddressBookChanged(address, "", is_mine, "", CT_DELETED); batch.ErasePurpose(EncodeDestination(address)); return batch.EraseName(EncodeDestination(address)); @@ -2071,9 +2094,14 @@ size_t CWallet::KeypoolCountExternalKeys() const { AssertLockHeld(cs_wallet); + auto legacy_spk_man = GetLegacyScriptPubKeyMan(); + if (legacy_spk_man) { + return legacy_spk_man->KeypoolCountExternalKeys(); + } + unsigned int count = 0; - for (auto spk_man : GetActiveScriptPubKeyMans()) { - count += spk_man->KeypoolCountExternalKeys(); + for (auto spk_man : m_external_spk_managers) { + count += spk_man.second->GetKeyPoolSize(); } return count; @@ -2110,7 +2138,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label, spk_man->TopUp(); result = spk_man->GetNewDestination(type, dest, error); } else { - error = strprintf("Error: No %s addresses available.", FormatOutputType(type)); + error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated; } if (result) { SetAddressBook(dest, label, "receive"); @@ -2125,8 +2153,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des error.clear(); ReserveDestination reservedest(this, type); - if (!reservedest.GetReservedDestination(dest, true)) { - error = _("Error: Keypool ran out, please call keypoolrefill first").translated; + if (!reservedest.GetReservedDestination(dest, true, error)) { return false; } @@ -2173,10 +2200,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal) +bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, std::string& error) { m_spk_man = pwallet->GetScriptPubKeyMan(type, internal); if (!m_spk_man) { + error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated; return false; } @@ -2186,7 +2214,7 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter m_spk_man->TopUp(); CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { + if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) { return false; } fInternal = keypool.fInternal; @@ -2215,7 +2243,6 @@ void ReserveDestination::ReturnDestination() bool CWallet::DisplayAddress(const CTxDestination& dest) { -#ifdef ENABLE_EXTERNAL_SIGNER CScript scriptPubKey = GetScriptForDestination(dest); const auto spk_man = GetScriptPubKeyMan(scriptPubKey); if (spk_man == nullptr) { @@ -2227,9 +2254,6 @@ bool CWallet::DisplayAddress(const CTxDestination& dest) } ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); return signer_spk_man->DisplayAddress(scriptPubKey, signer); -#else - return false; -#endif } void CWallet::LockCoin(const COutPoint& output) @@ -2958,7 +2982,6 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern const std::map<OutputType, ScriptPubKeyMan*>& spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers; std::map<OutputType, ScriptPubKeyMan*>::const_iterator it = spk_managers.find(type); if (it == spk_managers.end()) { - WalletLogPrintf("%s scriptPubKey Manager for output type %d does not exist\n", internal ? "Internal" : "External", static_cast<int>(type)); return nullptr; } return it->second; @@ -3035,7 +3058,7 @@ void CWallet::SetupLegacyScriptPubKeyMan() } auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this)); - for (const auto& type : OUTPUT_TYPES) { + for (const auto& type : LEGACY_OUTPUT_TYPES) { m_internal_spk_managers[type] = spk_manager.get(); m_external_spk_managers[type] = spk_manager.get(); } @@ -3063,12 +3086,8 @@ void CWallet::ConnectScriptPubKeyManNotifiers() void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc) { if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { -#ifdef ENABLE_EXTERNAL_SIGNER auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc)); m_spk_managers[id] = std::move(spk_manager); -#else - throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)"); -#endif } else { auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); m_spk_managers[id] = std::move(spk_manager); @@ -3092,7 +3111,12 @@ void CWallet::SetupDescriptorScriptPubKeyMans() for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { - auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal)); + if (t == OutputType::BECH32M) { + // Skip taproot (bech32m) for now + // TODO: Setup taproot (bech32m) descriptors by default + continue; + } + auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this)); if (IsCrypted()) { if (IsLocked()) { throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); @@ -3101,14 +3125,13 @@ void CWallet::SetupDescriptorScriptPubKeyMans() throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors"); } } - spk_manager->SetupDescriptorGeneration(master_key, t); + spk_manager->SetupDescriptorGeneration(master_key, t, internal); uint256 id = spk_manager->GetID(); m_spk_managers[id] = std::move(spk_manager); AddActiveScriptPubKeyMan(id, t, internal); } } } else { -#ifdef ENABLE_EXTERNAL_SIGNER ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); // TODO: add account parameter @@ -3128,16 +3151,13 @@ void CWallet::SetupDescriptorScriptPubKeyMans() continue; } OutputType t = *desc->GetOutputType(); - auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal)); + auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this)); spk_manager->SetupDescriptor(std::move(desc)); uint256 id = spk_manager->GetID(); m_spk_managers[id] = std::move(spk_manager); AddActiveScriptPubKeyMan(id, t, internal); } } -#else - throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)"); -#endif // ENABLE_EXTERNAL_SIGNER } } @@ -3152,12 +3172,37 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal) { + // Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets. + // Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types. + Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); + WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal)); auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers; + auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers; auto spk_man = m_spk_managers.at(id).get(); - spk_man->SetInternal(internal); spk_mans[type] = spk_man; + if (spk_mans_other[type] == spk_man) { + spk_mans_other.erase(type); + } + + NotifyCanGetAddressesChanged(); +} + +void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal) +{ + auto spk_man = GetScriptPubKeyMan(type, internal); + if (spk_man != nullptr && spk_man->GetID() == id) { + WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal)); + WalletBatch batch(GetDatabase()); + if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) { + throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed"); + } + + auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers; + spk_mans.erase(type); + } + NotifyCanGetAddressesChanged(); } @@ -3191,44 +3236,26 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat } LOCK(cs_wallet); - auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); - - // If we already have this descriptor, remove it from the maps but add the existing cache to desc - auto old_spk_man = GetDescriptorScriptPubKeyMan(desc); - if (old_spk_man) { + auto spk_man = GetDescriptorScriptPubKeyMan(desc); + if (spk_man) { WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString()); + spk_man->UpdateWalletDescriptor(desc); + } else { + auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); + spk_man = new_spk_man.get(); - { - LOCK(old_spk_man->cs_desc_man); - new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache); - } - - // Remove from maps of active spkMans - auto old_spk_man_id = old_spk_man->GetID(); - for (bool internal : {false, true}) { - for (OutputType t : OUTPUT_TYPES) { - auto active_spk_man = GetScriptPubKeyMan(t, internal); - if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) { - if (internal) { - m_internal_spk_managers.erase(t); - } else { - m_external_spk_managers.erase(t); - } - break; - } - } - } - m_spk_managers.erase(old_spk_man_id); + // Save the descriptor to memory + m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man); } // Add the private keys to the descriptor for (const auto& entry : signing_provider.keys) { const CKey& key = entry.second; - new_spk_man->AddDescriptorKey(key, key.GetPubKey()); + spk_man->AddDescriptorKey(key, key.GetPubKey()); } // Top up key pool, the manager will generate new scriptPubKeys internally - if (!new_spk_man->TopUp()) { + if (!spk_man->TopUp()) { WalletLogPrintf("Could not top up scriptPubKeys\n"); return nullptr; } @@ -3236,7 +3263,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat // Apply the label if necessary // Note: we disable labels for ranged descriptors if (!desc.descriptor->IsRange()) { - auto script_pub_keys = new_spk_man->GetScriptPubKeys(); + auto script_pub_keys = spk_man->GetScriptPubKeys(); if (script_pub_keys.empty()) { WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n"); return nullptr; @@ -3248,12 +3275,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat } } - // Save the descriptor to memory - auto ret = new_spk_man.get(); - m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man); - // Save the descriptor to DB - ret->WriteDescriptor(); + spk_man->WriteDescriptor(); - return ret; + return spk_man; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d0e26c416c..3997751f52 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -21,7 +21,6 @@ #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> -#include <external_signer.h> #include <wallet/receive.h> #include <wallet/scriptpubkeyman.h> #include <wallet/spend.h> @@ -118,6 +117,7 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA + | WALLET_FLAG_LAST_HARDENED_XPUB_CACHED | WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_DESCRIPTORS | WALLET_FLAG_EXTERNAL_SIGNER; @@ -129,6 +129,7 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{ {"avoid_reuse", WALLET_FLAG_AVOID_REUSE}, {"blank", WALLET_FLAG_BLANK_WALLET}, {"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA}, + {"last_hardened_xpub_cached", WALLET_FLAG_LAST_HARDENED_XPUB_CACHED}, {"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS}, {"descriptor_wallet", WALLET_FLAG_DESCRIPTORS}, {"external_signer", WALLET_FLAG_EXTERNAL_SIGNER} @@ -182,7 +183,7 @@ public: } //! Reserve an address - bool GetReservedDestination(CTxDestination& pubkey, bool internal); + bool GetReservedDestination(CTxDestination& pubkey, bool internal, std::string& error); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object goes out of scope @@ -477,6 +478,9 @@ public: //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Upgrade DescriptorCaches + void UpgradeDescriptorCache() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; } //! Adds a destination data tuple to the store, without saving it to disk @@ -725,19 +729,18 @@ public: /** * Address book entry changed. - * @note called with lock cs_wallet held. + * @note called without lock cs_wallet held. */ - boost::signals2::signal<void (CWallet *wallet, const CTxDestination - &address, const std::string &label, bool isMine, - const std::string &purpose, - ChangeType status)> NotifyAddressBookChanged; + boost::signals2::signal<void(const CTxDestination& address, + const std::string& label, bool isMine, + const std::string& purpose, ChangeType status)> + NotifyAddressBookChanged; /** * Wallet transaction added, removed or updated. * @note called with lock cs_wallet held. */ - boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, - ChangeType status)> NotifyTransactionChanged; + boost::signals2::signal<void(const uint256& hashTx, ChangeType status)> NotifyTransactionChanged; /** Show progress e.g. for rescan */ boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress; @@ -896,6 +899,12 @@ public: //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal); + //! Remove specified ScriptPubKeyMan from set of active SPK managers. Writes the change to the wallet file. + //! @param[in] id The unique id for the ScriptPubKeyMan + //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for + //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses + void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal); + //! Create new DescriptorScriptPubKeyMans and add them to the wallet void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 24d5351945..1e5d8dfa3a 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -52,6 +52,7 @@ const std::string TX{"tx"}; const std::string VERSION{"version"}; const std::string WALLETDESCRIPTOR{"walletdescriptor"}; const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"}; +const std::string WALLETDESCRIPTORLHCACHE{"walletdescriptorlhcache"}; const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"}; const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"}; const std::string WATCHMETA{"watchmeta"}; @@ -209,6 +210,12 @@ bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bo return WriteIC(make_pair(key, type), id); } +bool WalletBatch::EraseActiveScriptPubKeyMan(uint8_t type, bool internal) +{ + const std::string key{internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK}; + return EraseIC(make_pair(key, type)); +} + bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey) { // hash pubkey/privkey to accelerate wallet load @@ -248,6 +255,35 @@ bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint2 return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub); } +bool WalletBatch::WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index) +{ + std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE); + xpub.Encode(ser_xpub.data()); + return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORLHCACHE, desc_id), key_exp_index), ser_xpub); +} + +bool WalletBatch::WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache) +{ + for (const auto& parent_xpub_pair : cache.GetCachedParentExtPubKeys()) { + if (!WriteDescriptorParentCache(parent_xpub_pair.second, desc_id, parent_xpub_pair.first)) { + return false; + } + } + for (const auto& derived_xpub_map_pair : cache.GetCachedDerivedExtPubKeys()) { + for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) { + if (!WriteDescriptorDerivedCache(derived_xpub_pair.second, desc_id, derived_xpub_map_pair.first, derived_xpub_pair.first)) { + return false; + } + } + } + for (const auto& lh_xpub_pair : cache.GetCachedLastHardenedExtPubKeys()) { + if (!WriteDescriptorLastHardenedCache(lh_xpub_pair.second, desc_id, lh_xpub_pair.first)) { + return false; + } + } + return true; +} + class CWalletScanState { public: unsigned int nKeys{0}; @@ -602,6 +638,17 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else { wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(key_exp_index, der_index, xpub); } + } else if (strType == DBKeys::WALLETDESCRIPTORLHCACHE) { + uint256 desc_id; + uint32_t key_exp_index; + ssKey >> desc_id; + ssKey >> key_exp_index; + + std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE); + ssValue >> ser_xpub; + CExtPubKey xpub; + xpub.Decode(ser_xpub.data()); + wss.m_descriptor_caches[desc_id].CacheLastHardenedExtPubKey(key_exp_index, xpub); } else if (strType == DBKeys::WALLETDESCRIPTORKEY) { uint256 desc_id; CPubKey pubkey; @@ -843,6 +890,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) result = DBErrors::CORRUPT; } + // Upgrade all of the descriptor caches to cache the last hardened xpub + // This operation is not atomic, but if it fails, only new entries are added so it is backwards compatible + try { + pwallet->UpgradeDescriptorCache(); + } catch (...) { + result = DBErrors::CORRUPT; + } + // Set the inactive chain if (wss.m_hd_chains.size() > 0) { LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan(); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index e7b2d7d780..9b775eb481 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -246,6 +246,8 @@ public: bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor); bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index); bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index); + bool WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index); + bool WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache); /// Write destination data key,value tuple to database bool WriteDestData(const std::string &address, const std::string &key, const std::string &value); @@ -253,6 +255,7 @@ public: bool EraseDestData(const std::string &address, const std::string &key); bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal); + bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal); DBErrors LoadWallet(CWallet* pwallet); DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx); diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 0713f768c1..c75e1759bc 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -43,6 +43,9 @@ enum WalletFlags : uint64_t { // Indicates that the metadata has already been upgraded to contain key origins WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1), + // Indicates that the descriptor cache has been upgraded to cache last hardened xpubs + WALLET_FLAG_LAST_HARDENED_XPUB_CACHED = (1ULL << 2), + // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys) WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), diff --git a/test/README.md b/test/README.md index ab34ce42dc..51e61562a4 100644 --- a/test/README.md +++ b/test/README.md @@ -84,6 +84,12 @@ Run all possible tests with test/functional/test_runner.py --extended ``` +In order to run backwards compatibility tests, download the previous node binaries: + +``` +test/get_previous_releases.py -b v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 +``` + By default, up to 4 tests will be run in parallel by test_runner. To specify how many jobs to run, append `--jobs=n` diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index fab921ef19..cde0399d8b 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -29,27 +29,32 @@ from test_framework.messages import ( CTxOut, MAX_MONEY, ) -from test_framework import script as sc from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS from test_framework.script import ( CScript, + OP_0, + OP_2DIV, + OP_2MUL, + OP_AND, OP_CAT, - OP_SUBSTR, - OP_LEFT, - OP_RIGHT, + OP_CHECKSIG, + OP_DIV, OP_INVERT, - OP_AND, + OP_LEFT, + OP_LSHIFT, + OP_MOD, + OP_MUL, OP_OR, + OP_RIGHT, + OP_RSHIFT, + OP_SUBSTR, + OP_TRUE, OP_XOR, - OP_2MUL, - OP_2DIV, - OP_MUL, - OP_DIV, - OP_MOD, - OP_LSHIFT, - OP_RSHIFT ) -basic_p2sh = sc.CScript([sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL]) +from test_framework.script_util import ( + script_to_p2sh_script, +) +basic_p2sh = script_to_p2sh_script(CScript([OP_0])) class BadTxTemplate: @@ -116,7 +121,7 @@ class SizeTooSmall(BadTxTemplate): def get_tx(self): tx = CTransaction() tx.vin.append(self.valid_txin) - tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE]))) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx.calc_sha256() return tx @@ -151,6 +156,19 @@ class DuplicateInput(BadTxTemplate): return tx +class PrevoutNullInput(BadTxTemplate): + reject_reason = 'bad-txns-prevout-null' + expect_disconnect = True + + def get_tx(self): + tx = CTransaction() + tx.vin.append(self.valid_txin) + tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff))) + tx.vout.append(CTxOut(1, basic_p2sh)) + tx.calc_sha256() + return tx + + class NonexistentInput(BadTxTemplate): reject_reason = None # Added as an orphan tx. expect_disconnect = False @@ -217,7 +235,7 @@ class TooManySigops(BadTxTemplate): expect_disconnect = False def get_tx(self): - lotsa_checksigs = sc.CScript([sc.OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) + lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) return create_tx_with_script( self.spend_tx, 0, script_pub_key=lotsa_checksigs, diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py index a60a723b3e..24bb02bc90 100755 --- a/test/functional/feature_anchors.py +++ b/test/functional/feature_anchors.py @@ -2,7 +2,7 @@ # Copyright (c) 2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test Anchors functionality""" +"""Test block-relay-only anchors functionality""" import os @@ -10,6 +10,9 @@ from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +INBOUND_CONNECTIONS = 5 +BLOCK_RELAY_CONNECTIONS = 2 + def check_node_connections(*, node, num_in, num_out): info = node.getnetworkinfo() @@ -25,19 +28,26 @@ class AnchorsTest(BitcoinTestFramework): self.setup_nodes() def run_test(self): - self.log.info("Add 2 block-relay-only connections to node 0") - for i in range(2): + node_anchors_path = os.path.join( + self.nodes[0].datadir, "regtest", "anchors.dat" + ) + + self.log.info("When node starts, check if anchors.dat doesn't exist") + assert not os.path.exists(node_anchors_path) + + self.log.info(f"Add {BLOCK_RELAY_CONNECTIONS} block-relay-only connections to node") + for i in range(BLOCK_RELAY_CONNECTIONS): self.log.debug(f"block-relay-only: {i}") self.nodes[0].add_outbound_p2p_connection( P2PInterface(), p2p_idx=i, connection_type="block-relay-only" ) - self.log.info("Add 5 inbound connections to node 0") - for i in range(5): + self.log.info(f"Add {INBOUND_CONNECTIONS} inbound connections to node") + for i in range(INBOUND_CONNECTIONS): self.log.debug(f"inbound: {i}") self.nodes[0].add_p2p_connection(P2PInterface()) - self.log.info("Check node 0 connections") + self.log.info("Check node connections") check_node_connections(node=self.nodes[0], num_in=5, num_out=2) # 127.0.0.1 @@ -57,14 +67,10 @@ class AnchorsTest(BitcoinTestFramework): self.log.info("Stop node 0") self.stop_node(0) - node0_anchors_path = os.path.join( - self.nodes[0].datadir, "regtest", "anchors.dat" - ) - # It should contain only the block-relay-only addresses self.log.info("Check the addresses in anchors.dat") - with open(node0_anchors_path, "rb") as file_handler: + with open(node_anchors_path, "rb") as file_handler: anchors = file_handler.read().hex() for port in block_relay_nodes_port: @@ -74,11 +80,11 @@ class AnchorsTest(BitcoinTestFramework): ip_port = ip + port assert ip_port not in anchors - self.log.info("Start node 0") + self.log.info("Start node") self.start_node(0) self.log.info("When node starts, check if anchors.dat doesn't exist anymore") - assert not os.path.exists(node0_anchors_path) + assert not os.path.exists(node_anchors_path) if __name__ == "__main__": diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index c712f7141c..e0ba835f99 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -4,9 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Backwards compatibility functional test -Test various backwards compatibility scenarios. Download the previous node binaries: - -test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 +Test various backwards compatibility scenarios. Requires previous releases binaries, +see test/README.md. v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py. Due to a hardfork in regtest, it can't be used to sync nodes. diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py new file mode 100755 index 0000000000..6802da8d48 --- /dev/null +++ b/test/functional/feature_bind_extra.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test starting bitcoind with -bind and/or -bind=...=onion and confirm +that bind happens on the expected ports. +""" + +import sys + +from test_framework.netutil import ( + addr_to_hex, + get_bind_addrs, +) +from test_framework.test_framework import ( + BitcoinTestFramework, + SkipTest, +) +from test_framework.util import ( + PORT_MIN, + PORT_RANGE, + assert_equal, + rpc_port, +) + +class BindExtraTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + # Avoid any -bind= on the command line. Force the framework to avoid + # adding -bind=127.0.0.1. + self.bind_to_localhost_only = False + self.num_nodes = 2 + + def setup_network(self): + # Override setup_network() because we want to put the result of + # p2p_port() in self.extra_args[], before the nodes are started. + # p2p_port() is not usable in set_test_params() because PortSeed.n is + # not set at that time. + + # Due to OS-specific network stats queries, we only run on Linux. + self.log.info("Checking for Linux") + if not sys.platform.startswith('linux'): + raise SkipTest("This test can only be run on Linux.") + + loopback_ipv4 = addr_to_hex("127.0.0.1") + + # Start custom ports after p2p and rpc ports. + port = PORT_MIN + 2 * PORT_RANGE + + # Array of tuples [command line arguments, expected bind addresses]. + self.expected = [] + + # Node0, no normal -bind=... with -bind=...=onion, thus only the tor target. + self.expected.append( + [ + [f"-bind=127.0.0.1:{port}=onion"], + [(loopback_ipv4, port)] + ], + ) + port += 1 + + # Node1, both -bind=... and -bind=...=onion. + self.expected.append( + [ + [f"-bind=127.0.0.1:{port}", f"-bind=127.0.0.1:{port + 1}=onion"], + [(loopback_ipv4, port), (loopback_ipv4, port + 1)] + ], + ) + port += 2 + + self.extra_args = list(map(lambda e: e[0], self.expected)) + self.add_nodes(self.num_nodes, self.extra_args) + # Don't start the nodes, as some of them would collide trying to bind on the same port. + + def run_test(self): + for i in range(len(self.expected)): + self.log.info(f"Starting node {i} with {self.expected[i][0]}") + self.start_node(i) + pid = self.nodes[i].process.pid + binds = set(get_bind_addrs(pid)) + # Remove IPv6 addresses because on some CI environments "::1" is not configured + # on the system (so our test_ipv6_local() would return False), but it is + # possible to bind on "::". This makes it unpredictable whether to expect + # that bitcoind has bound on "::1" (for RPC) and "::" (for P2P). + ipv6_addr_len_bytes = 32 + binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds)) + # Remove RPC ports. They are not relevant for this test. + binds = set(filter(lambda e: e[1] != rpc_port(i), binds)) + assert_equal(binds, set(self.expected[i][1])) + self.stop_node(i) + self.log.info(f"Stopped node {i}") + +if __name__ == '__main__': + BindExtraTest().main() diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index 6c5857c5ce..e44ce9b57d 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -6,8 +6,19 @@ import time -from test_framework.blocktools import create_block, NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex +from test_framework.blocktools import ( + NORMAL_GBT_REQUEST_PARAMS, + add_witness_commitment, + create_block, +) +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -89,7 +100,7 @@ class BIP68Test(BitcoinTestFramework): tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)] - tx1_signed = self.nodes[0].signrawtransactionwithwallet(ToHex(tx1))["hex"] + tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) tx1_id = int(tx1_id, 16) @@ -102,13 +113,13 @@ class BIP68Test(BitcoinTestFramework): tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] tx2.rehash() - assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx2)) + assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex()) # Setting the version back down to 1 should disable the sequence lock, # so this should be accepted. tx2.nVersion = 1 - self.nodes[0].sendrawtransaction(ToHex(tx2)) + self.nodes[0].sendrawtransaction(tx2.serialize().hex()) # Calculate the median time past of a prior block ("confirmations" before # the current tip). @@ -193,9 +204,9 @@ class BIP68Test(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value)) value += utxos[j]["amount"]*COIN # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output - tx_size = len(ToHex(tx))//2 + 120*num_inputs + 50 + tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50 tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2WPKH_SCRIPT)) - rawtx = self.nodes[0].signrawtransactionwithwallet(ToHex(tx))["hex"] + rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"] if (using_sequence_locks and not should_pass): # This transaction should be rejected @@ -215,7 +226,7 @@ class BIP68Test(BitcoinTestFramework): # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) - tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) + tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Anyone-can-spend mempool tx. @@ -224,8 +235,8 @@ class BIP68Test(BitcoinTestFramework): tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] - tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] - tx2 = FromHex(tx2, tx2_raw) + tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] + tx2 = tx_from_hex(tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) @@ -246,10 +257,10 @@ class BIP68Test(BitcoinTestFramework): if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool - assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) + assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, tx.serialize().hex()) else: # sendrawtransaction should succeed if the tx is not in the mempool - node.sendrawtransaction(ToHex(tx)) + node.sendrawtransaction(tx.serialize().hex()) return tx @@ -299,7 +310,7 @@ class BIP68Test(BitcoinTestFramework): utxos = self.nodes[0].listunspent() tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"]*COIN) - raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] + raw_tx5 = self.nodes[0].signrawtransactionwithwallet(tx5.serialize().hex())["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) @@ -325,7 +336,7 @@ class BIP68Test(BitcoinTestFramework): block.rehash() block.solve() tip = block.sha256 - assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(ToHex(block))) + assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(block.serialize().hex())) tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) tmpl['previousblockhash'] = '%x' % tip tmpl['transactions'] = [] @@ -348,7 +359,7 @@ class BIP68Test(BitcoinTestFramework): assert not softfork_active(self.nodes[0], 'csv') txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) - tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) + tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Make an anyone-can-spend transaction @@ -358,11 +369,11 @@ class BIP68Test(BitcoinTestFramework): tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] # sign tx2 - tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] - tx2 = FromHex(tx2, tx2_raw) + tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] + tx2 = tx_from_hex(tx2_raw) tx2.rehash() - self.nodes[0].sendrawtransaction(ToHex(tx2)) + self.nodes[0].sendrawtransaction(tx2.serialize().hex()) # Now make an invalid spend of tx2 according to BIP68 sequence_value = 100 # 100 block relative locktime @@ -373,7 +384,7 @@ class BIP68Test(BitcoinTestFramework): tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] tx3.rehash() - assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3)) + assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex()) # make a block that violates bip68; ensure that the tip updates block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) @@ -404,9 +415,9 @@ class BIP68Test(BitcoinTestFramework): outputs = { self.nodes[1].getnewaddress() : 1.0 } rawtx = self.nodes[1].createrawtransaction(inputs, outputs) rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex'] - tx = FromHex(CTransaction(), rawtxfund) + tx = tx_from_hex(rawtxfund) tx.nVersion = 2 - tx_signed = self.nodes[1].signrawtransactionwithwallet(ToHex(tx))["hex"] + tx_signed = self.nodes[1].signrawtransactionwithwallet(tx.serialize().hex())["hex"] self.nodes[1].sendrawtransaction(tx_signed) if __name__ == '__main__': diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 158efb52c9..c11eabc917 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -37,17 +37,17 @@ from test_framework.script import ( OP_CHECKSIGVERIFY, OP_ELSE, OP_ENDIF, - OP_EQUAL, OP_DROP, OP_FALSE, - OP_HASH160, OP_IF, OP_INVALIDOPCODE, OP_RETURN, OP_TRUE, SIGHASH_ALL, LegacySignatureHash, - hash160, +) +from test_framework.script_util import ( + script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -469,8 +469,7 @@ class FullBlockTest(BitcoinTestFramework): # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) - redeem_script_hash = hash160(redeem_script) - p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) + p2sh_script = script_to_p2sh_script(redeem_script) # Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE # This must be signed because it is spending a coinbase @@ -591,6 +590,8 @@ class FullBlockTest(BitcoinTestFramework): b44.hashPrevBlock = self.tip.sha256 b44.nBits = 0x207fffff b44.vtx.append(coinbase) + tx = self.create_and_sign_transaction(out[14], 1) + b44.vtx.append(tx) b44.hashMerkleRoot = b44.calc_merkle_root() b44.solve() self.tip = b44 @@ -678,7 +679,7 @@ class FullBlockTest(BitcoinTestFramework): # Test block timestamps # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) # \-> b54 (15) - # + # -> b44 (14)\-> b48 () self.move_tip(43) b53 = self.next_block(53, spend=out[14]) self.send_blocks([b53], False) @@ -698,6 +699,21 @@ class FullBlockTest(BitcoinTestFramework): self.send_blocks([b55], True) self.save_spendable_output() + # The block which was previously rejected because of being "too far(3 hours)" must be accepted 2 hours later. + # The new block is only 1 hour into future now and we must reorg onto to the new longer chain. + # The new bestblock b48p is invalidated manually. + # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) + # \-> b54 (15) + # -> b44 (14)\-> b48 () -> b48p () + self.log.info("Accept a previously rejected future block at a later time") + node.setmocktime(int(time.time()) + 2*60*60) + self.move_tip(48) + self.block_heights[b48.sha256] = self.block_heights[b44.sha256] + 1 # b48 is a parent of b44 + b48p = self.next_block("48p") + self.send_blocks([b48, b48p], success=True) # Reorg to the longer chain + node.invalidateblock(b48p.hash) # mark b48p as invalid + node.setmocktime(0) + # Test Merkle tree malleability # # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57p2 (16) @@ -1308,7 +1324,7 @@ class FullBlockTest(BitcoinTestFramework): return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script) # sign a transaction, using the key we know about - # this signs input 0 in tx, which is assumed to be spending output n in spend_tx + # this signs input 0 in tx, which is assumed to be spending output 0 in spend_tx def sign_tx(self, tx, spend_tx): scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index cf270b25ee..5d8ec2a8da 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -22,7 +22,6 @@ from test_framework.messages import ( CTransaction, CTxIn, CTxOut, - ToHex, ) from test_framework.script import ( CScript, @@ -170,7 +169,7 @@ class CoinStatsIndexTest(BitcoinTestFramework): tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b'')) tx2.vout.append(CTxOut(int(20.99 * COIN), CScript([OP_RETURN] + [OP_FALSE]*30))) - tx2_hex = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))['hex'] + tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())['hex'] self.nodes[0].sendrawtransaction(tx2_hex) # Include both txs in a block @@ -207,7 +206,7 @@ class CoinStatsIndexTest(BitcoinTestFramework): block_time = self.nodes[0].getblock(tip)['time'] + 1 block = create_block(int(tip, 16), cb, block_time) block.solve() - self.nodes[0].submitblock(ToHex(block)) + self.nodes[0].submitblock(block.serialize().hex()) self.sync_all() self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash')) @@ -281,6 +280,7 @@ class CoinStatsIndexTest(BitcoinTestFramework): # Add another block, so we don't depend on reconsiderblock remembering which # blocks were touched by invalidateblock index_node.generate(1) + self.sync_all() # Ensure that removing and re-adding blocks yields consistent results block = index_node.getblockhash(99) diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 2b56bc78f5..c532300ce2 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -36,7 +36,6 @@ from test_framework.messages import ( CTransaction, CTxIn, CTxOut, - ToHex, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -208,7 +207,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): tx.vout.append(CTxOut(output_amount, hex_str_to_bytes(utxo['scriptPubKey']))) # Sign and send the transaction to get into the mempool - tx_signed_hex = node.signrawtransactionwithwallet(ToHex(tx))['hex'] + tx_signed_hex = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] node.sendrawtransaction(tx_signed_hex) num_transactions += 1 diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 8f522aee66..5322b02414 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -6,8 +6,23 @@ from decimal import Decimal import random -from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN -from test_framework.script import CScript, OP_1, OP_DROP, OP_2, OP_HASH160, OP_EQUAL, hash160, OP_TRUE +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, +) +from test_framework.script import ( + CScript, + OP_1, + OP_2, + OP_DROP, + OP_TRUE, +) +from test_framework.script_util import ( + script_to_p2sh_script, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -22,8 +37,8 @@ from test_framework.util import ( # time signing. REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP]) REDEEM_SCRIPT_2 = CScript([OP_2, OP_DROP]) -P2SH_1 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_1), OP_EQUAL]) -P2SH_2 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_2), OP_EQUAL]) +P2SH_1 = script_to_p2sh_script(REDEEM_SCRIPT_1) +P2SH_2 = script_to_p2sh_script(REDEEM_SCRIPT_2) # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])] @@ -64,11 +79,11 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee # the ScriptSig that will satisfy the ScriptPubKey. for inp in tx.vin: inp.scriptSig = SCRIPT_SIG[inp.prevout.n] - txid = from_node.sendrawtransaction(hexstring=ToHex(tx), maxfeerate=0) + txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee}) unconflist.append({"txid": txid, "vout": 1, "amount": amount}) - return (ToHex(tx), fee) + return (tx.serialize().hex(), fee) def split_inputs(from_node, txins, txouts, initial_split=False): @@ -91,10 +106,10 @@ def split_inputs(from_node, txins, txouts, initial_split=False): # If this is the initial split we actually need to sign the transaction # Otherwise we just need to insert the proper ScriptSig if (initial_split): - completetx = from_node.signrawtransactionwithwallet(ToHex(tx))["hex"] + completetx = from_node.signrawtransactionwithwallet(tx.serialize().hex())["hex"] else: tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]] - completetx = ToHex(tx) + completetx = tx.serialize().hex() txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0) txouts.append({"txid": txid, "vout": 0, "amount": half_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index f09bffe2d4..cedb7b57ca 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -11,8 +11,12 @@ This test takes 30 mins or more (up to 2 hours) import os from test_framework.blocktools import create_coinbase -from test_framework.messages import CBlock, ToHex -from test_framework.script import CScript, OP_RETURN, OP_NOP +from test_framework.messages import CBlock +from test_framework.script import ( + CScript, + OP_NOP, + OP_RETURN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -62,7 +66,7 @@ def mine_large_blocks(node, n): block.solve() # Submit to the node - node.submitblock(ToHex(block)) + node.submitblock(block.serialize().hex()) previousblockhash = block.sha256 height += 1 diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 0bb04ae267..ed944274e3 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -7,16 +7,22 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut +from test_framework.messages import ( + BIP125_SEQUENCE_NUMBER, + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, +) from test_framework.script import CScript, OP_DROP from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT +from test_framework.wallet import MiniWallet MAX_REPLACEMENT_LIMIT = 100 -def txToHex(tx): - return tx.serialize().hex() def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): """Create a txout with a given amount and scriptPubKey @@ -26,12 +32,12 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ - fee = 1*COIN - while node.getbalance() < satoshi_round((amount + fee)/COIN): + fee = 1 * COIN + while node.getbalance() < satoshi_round((amount + fee) / COIN): node.generate(COINBASE_MATURITY) new_addr = node.getnewaddress() - txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) + txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN)) tx1 = node.getrawtransaction(txid, 1) txid = int(txid, 16) i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout']))) @@ -41,7 +47,7 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): tx2.vout = [CTxOut(amount, scriptPubKey)] tx2.rehash() - signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) + signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex()) txid = node.sendrawtransaction(signed_tx['hex'], 0) @@ -78,10 +84,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - # Leave IBD - self.nodes[0].generate(1) - - make_utxo(self.nodes[0], 1*COIN) + make_utxo(self.nodes[0], 1 * COIN) # Ensure nodes are synced self.sync_all() @@ -119,11 +122,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.log.info("Running test no inherited signaling...") self.test_no_inherited_signaling() + self.log.info("Running test replacement relay fee...") + self.test_replacement_relay_fee() + self.log.info("Passed") def test_simple_doublespend(self): """Simple doublespend""" - tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) # make_utxo may have generated a bunch of blocks, so we need to sync # before we can spend the coins generated, or else the resulting @@ -133,7 +139,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = txToHex(tx1a) + tx1a_hex = tx1a.serialize().hex() tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) self.sync_all() @@ -142,7 +148,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)] - tx1b_hex = txToHex(tx1b) + tx1b_hex = tx1b.serialize().hex() # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) @@ -151,7 +157,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx1b_hex = txToHex(tx1b) + tx1b_hex = tx1b.serialize().hex() # Works when enabled tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) @@ -165,18 +171,18 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_doublespend_chain(self): """Doublespend of a long chain""" - initial_nValue = 50*COIN + initial_nValue = 50 * COIN tx0_outpoint = make_utxo(self.nodes[0], initial_nValue) prevout = tx0_outpoint remaining_value = initial_nValue chain_txids = [] - while remaining_value > 10*COIN: - remaining_value -= 1*COIN + while remaining_value > 10 * COIN: + remaining_value -= 1 * COIN tx = CTransaction() tx.vin = [CTxIn(prevout, nSequence=0)] tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))] - tx_hex = txToHex(tx) + tx_hex = tx.serialize().hex() txid = self.nodes[0].sendrawtransaction(tx_hex, 0) chain_txids.append(txid) prevout = COutPoint(int(txid, 16), 0) @@ -186,7 +192,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] dbl_tx.vout = [CTxOut(initial_nValue - 30 * COIN, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = txToHex(dbl_tx) + dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) @@ -195,7 +201,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] dbl_tx.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = txToHex(dbl_tx) + dbl_tx_hex = dbl_tx.serialize().hex() self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) mempool = self.nodes[0].getrawmempool() @@ -205,10 +211,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_doublespend_tree(self): """Doublespend of a big tree of transactions""" - initial_nValue = 50*COIN + initial_nValue = 50 * COIN tx0_outpoint = make_utxo(self.nodes[0], initial_nValue) - def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _total_txs=None): + def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001 * COIN, _total_txs=None): if _total_txs is None: _total_txs = [0] if _total_txs[0] >= max_txs: @@ -223,7 +229,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx = CTransaction() tx.vin = [CTxIn(prevout, nSequence=0)] tx.vout = vout - tx_hex = txToHex(tx) + tx_hex = tx.serialize().hex() assert len(tx.serialize()) < 100000 txid = self.nodes[0].sendrawtransaction(tx_hex, 0) @@ -239,7 +245,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): _total_txs=_total_txs): yield x - fee = int(0.0001*COIN) + fee = int(0.0001 * COIN) n = MAX_REPLACEMENT_LIMIT tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) assert_equal(len(tree_txs), n) @@ -248,7 +254,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = txToHex(dbl_tx) + dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) @@ -256,7 +262,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] dbl_tx.vout = [CTxOut(initial_nValue - fee * n - 1 * COIN, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = txToHex(dbl_tx) + dbl_tx_hex = dbl_tx.serialize().hex() self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) mempool = self.nodes[0].getrawmempool() @@ -267,8 +273,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Try again, but with more total transactions than the "max txs # double-spent at once" anti-DoS limit. - for n in (MAX_REPLACEMENT_LIMIT+1, MAX_REPLACEMENT_LIMIT*2): - fee = int(0.0001*COIN) + for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2): + fee = int(0.0001 * COIN) tx0_outpoint = make_utxo(self.nodes[0], initial_nValue) tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) assert_equal(len(tree_txs), n) @@ -276,7 +282,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = txToHex(dbl_tx) + dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) @@ -286,33 +292,33 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_replacement_feeperkb(self): """Replacement requires fee-per-KB to be higher""" - tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = txToHex(tx1a) + tx1a_hex = tx1a.serialize().hex() self.nodes[0].sendrawtransaction(tx1a_hex, 0) # Higher fee, but the fee per KB is much lower, so the replacement is # rejected. tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))] - tx1b_hex = txToHex(tx1b) + tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))] + tx1b_hex = tx1b.serialize().hex() # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) def test_spends_of_conflicting_outputs(self): """Replacements that spend conflicting tx outputs are rejected""" - utxo1 = make_utxo(self.nodes[0], int(1.2*COIN)) - utxo2 = make_utxo(self.nodes[0], 3*COIN) + utxo1 = make_utxo(self.nodes[0], int(1.2 * COIN)) + utxo2 = make_utxo(self.nodes[0], 3 * COIN) tx1a = CTransaction() tx1a.vin = [CTxIn(utxo1, nSequence=0)] tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = txToHex(tx1a) + tx1a_hex = tx1a.serialize().hex() tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) tx1a_txid = int(tx1a_txid, 16) @@ -322,7 +328,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)] tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)) tx2.vout = tx1a.vout - tx2_hex = txToHex(tx2) + tx2_hex = tx2.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) @@ -331,7 +337,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1b = CTransaction() tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1b_hex = txToHex(tx1b) + tx1b_hex = tx1b.serialize().hex() tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) tx1b_txid = int(tx1b_txid, 16) @@ -339,26 +345,26 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0), CTxIn(COutPoint(tx1b_txid, 0))] tx2.vout = tx1a.vout - tx2_hex = txToHex(tx2) + tx2_hex = tx2.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) def test_new_unconfirmed_inputs(self): """Replacements that add new unconfirmed inputs are rejected""" - confirmed_utxo = make_utxo(self.nodes[0], int(1.1*COIN)) - unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1*COIN), False) + confirmed_utxo = make_utxo(self.nodes[0], int(1.1 * COIN)) + unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1 * COIN), False) tx1 = CTransaction() tx1.vin = [CTxIn(confirmed_utxo)] tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1_hex = txToHex(tx1) + tx1_hex = tx1.serialize().hex() self.nodes[0].sendrawtransaction(tx1_hex, 0) tx2 = CTransaction() tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)] tx2.vout = tx1.vout - tx2_hex = txToHex(tx2) + tx2_hex = tx2.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0) @@ -369,42 +375,42 @@ class ReplaceByFeeTest(BitcoinTestFramework): # transactions # Start by creating a single transaction with many outputs - initial_nValue = 10*COIN + initial_nValue = 10 * COIN utxo = make_utxo(self.nodes[0], initial_nValue) - fee = int(0.0001*COIN) - split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1)) + fee = int(0.0001 * COIN) + split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1)) outputs = [] - for _ in range(MAX_REPLACEMENT_LIMIT+1): + for _ in range(MAX_REPLACEMENT_LIMIT + 1): outputs.append(CTxOut(split_value, CScript([1]))) splitting_tx = CTransaction() splitting_tx.vin = [CTxIn(utxo, nSequence=0)] splitting_tx.vout = outputs - splitting_tx_hex = txToHex(splitting_tx) + splitting_tx_hex = splitting_tx.serialize().hex() txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0) txid = int(txid, 16) # Now spend each of those outputs individually - for i in range(MAX_REPLACEMENT_LIMIT+1): + for i in range(MAX_REPLACEMENT_LIMIT + 1): tx_i = CTransaction() tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)] tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)] - tx_i_hex = txToHex(tx_i) + tx_i_hex = tx_i.serialize().hex() self.nodes[0].sendrawtransaction(tx_i_hex, 0) # Now create doublespend of the whole lot; should fail. # Need a big enough fee to cover all spending transactions and have # a higher fee rate - double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1) + double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1) inputs = [] - for i in range(MAX_REPLACEMENT_LIMIT+1): + for i in range(MAX_REPLACEMENT_LIMIT + 1): inputs.append(CTxIn(COutPoint(txid, i), nSequence=0)) double_tx = CTransaction() double_tx.vin = inputs double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))] - double_tx_hex = txToHex(double_tx) + double_tx_hex = double_tx.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0) @@ -413,18 +419,18 @@ class ReplaceByFeeTest(BitcoinTestFramework): double_tx = CTransaction() double_tx.vin = inputs[0:-1] double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))] - double_tx_hex = txToHex(double_tx) + double_tx_hex = double_tx.serialize().hex() self.nodes[0].sendrawtransaction(double_tx_hex, 0) def test_opt_in(self): """Replacing should only work if orig tx opted in""" - tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) # Create a non-opting in transaction tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)] tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = txToHex(tx1a) + tx1a_hex = tx1a.serialize().hex() tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) # This transaction isn't shown as replaceable @@ -434,25 +440,25 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx1b_hex = txToHex(tx1b) + tx1b_hex = tx1b.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0) - tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) # Create a different non-opting in transaction tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)] tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx2a_hex = txToHex(tx2a) + tx2a_hex = tx2a.serialize().hex() tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0) # Still shouldn't be able to double-spend tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx2b_hex = txToHex(tx2b) + tx2b_hex = tx2b.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0) @@ -467,8 +473,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx3a = CTransaction() tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff), CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)] - tx3a.vout = [CTxOut(int(0.9*COIN), CScript([b'c'])), CTxOut(int(0.9*COIN), CScript([b'd']))] - tx3a_hex = txToHex(tx3a) + tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))] + tx3a_hex = tx3a.serialize().hex() tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0) @@ -478,12 +484,12 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx3b = CTransaction() tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx3b_hex = txToHex(tx3b) + tx3b_hex = tx3b.serialize().hex() tx3c = CTransaction() tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)] tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx3c_hex = txToHex(tx3c) + tx3c_hex = tx3c.serialize().hex() self.nodes[0].sendrawtransaction(tx3b_hex, 0) # If tx3b was accepted, tx3c won't look like a replacement, @@ -495,25 +501,25 @@ class ReplaceByFeeTest(BitcoinTestFramework): # correctly used by replacement logic # 1. Check that feeperkb uses modified fees - tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = txToHex(tx1a) + tx1a_hex = tx1a.serialize().hex() tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) # Higher fee, but the actual fee per KB is much lower. tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))] - tx1b_hex = txToHex(tx1b) + tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))] + tx1b_hex = tx1b.serialize().hex() # Verify tx1b cannot replace tx1a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) # Use prioritisetransaction to set tx1a's fee to 0. - self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1*COIN)) + self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN)) # Now tx1b should be able to replace tx1a tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) @@ -521,12 +527,12 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert tx1b_txid in self.nodes[0].getrawmempool() # 2. Check that absolute fee checks use modified fee. - tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) + tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx2a_hex = txToHex(tx2a) + tx2a_hex = tx2a.serialize().hex() self.nodes[0].sendrawtransaction(tx2a_hex, 0) # Lower fee, but we'll prioritise it @@ -534,13 +540,13 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)] tx2b.rehash() - tx2b_hex = txToHex(tx2b) + tx2b_hex = tx2b.serialize().hex() # Verify tx2b cannot replace tx2a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0) # Now prioritise tx2b to have a higher modified fee - self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1*COIN)) + self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN)) # tx2b should now be accepted tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0) @@ -550,11 +556,11 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_rpc(self): us0 = self.nodes[0].listunspent()[0] ins = [us0] - outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)} + outs = {self.nodes[0].getnewaddress(): Decimal(1.0000000)} rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True) rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False) - json0 = self.nodes[0].decoderawtransaction(rawtx0) - json1 = self.nodes[0].decoderawtransaction(rawtx1) + json0 = self.nodes[0].decoderawtransaction(rawtx0) + json1 = self.nodes[0].decoderawtransaction(rawtx1) assert_equal(json0["vin"][0]["sequence"], 4294967293) assert_equal(json1["vin"][0]["sequence"], 4294967295) @@ -562,74 +568,77 @@ class ReplaceByFeeTest(BitcoinTestFramework): frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True}) frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False}) - json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex']) - json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex']) + json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex']) + json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex']) assert_equal(json0["vin"][0]["sequence"], 4294967293) assert_equal(json1["vin"][0]["sequence"], 4294967294) def test_no_inherited_signaling(self): - # Send tx from which to conflict outputs later - base_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) - self.nodes[0].generate(1) - self.sync_blocks() + wallet = MiniWallet(self.nodes[0]) + wallet.scan_blocks(start=76, num=1) + confirmed_utxo = wallet.get_utxo() # Create an explicitly opt-in parent transaction - optin_parent_tx = self.nodes[0].createrawtransaction([{ - 'txid': base_txid, - 'vout': 0, - "sequence": 0xfffffffd, - }], {self.nodes[0].getnewaddress(): Decimal("9.99998")}) - - optin_parent_tx = self.nodes[0].signrawtransactionwithwallet(optin_parent_tx) - - # Broadcast parent tx - optin_parent_txid = self.nodes[0].sendrawtransaction(hexstring=optin_parent_tx["hex"], maxfeerate=0) - assert optin_parent_txid in self.nodes[0].getrawmempool() - - replacement_parent_tx = self.nodes[0].createrawtransaction([{ - 'txid': base_txid, - 'vout': 0, - "sequence": 0xfffffffd, - }], {self.nodes[0].getnewaddress(): Decimal("9.90000")}) - - replacement_parent_tx = self.nodes[0].signrawtransactionwithwallet(replacement_parent_tx) + optin_parent_tx = wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=BIP125_SEQUENCE_NUMBER, + fee_rate=Decimal('0.01'), + ) + assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) + + replacement_parent_tx = wallet.create_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=BIP125_SEQUENCE_NUMBER, + fee_rate=Decimal('0.02'), + ) # Test if parent tx can be replaced. - res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']], maxfeerate=0)[0] + res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']])[0] # Parent can be replaced. assert_equal(res['allowed'], True) # Create an opt-out child tx spending the opt-in parent - optout_child_tx = self.nodes[0].createrawtransaction([{ - 'txid': optin_parent_txid, - 'vout': 0, - "sequence": 0xffffffff, - }], {self.nodes[0].getnewaddress(): Decimal("9.99990")}) - - optout_child_tx = self.nodes[0].signrawtransactionwithwallet(optout_child_tx) - - # Broadcast child tx - optout_child_txid = self.nodes[0].sendrawtransaction(hexstring=optout_child_tx["hex"], maxfeerate=0) - assert optout_child_txid in self.nodes[0].getrawmempool() - - replacement_child_tx = self.nodes[0].createrawtransaction([{ - 'txid': optin_parent_txid, - 'vout': 0, - "sequence": 0xffffffff, - }], {self.nodes[0].getnewaddress(): Decimal("9.00000")}) - - replacement_child_tx = self.nodes[0].signrawtransactionwithwallet(replacement_child_tx) + parent_utxo = wallet.get_utxo(txid=optin_parent_tx['txid']) + optout_child_tx = wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=parent_utxo, + sequence=0xffffffff, + fee_rate=Decimal('0.01'), + ) + + # Reports true due to inheritance + assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable']) + + replacement_child_tx = wallet.create_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=parent_utxo, + sequence=0xffffffff, + fee_rate=Decimal('0.02'), + mempool_valid=False, + ) # Broadcast replacement child tx # BIP 125 : # 1. The original transactions signal replaceability explicitly or through inheritance as described in the above # Summary section. - # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does. + # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx`) does. # The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction. # See CVE-2021-31876 for further explanations. - assert optin_parent_txid in self.nodes[0].getrawmempool() + assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0) + def test_replacement_relay_fee(self): + wallet = MiniWallet(self.nodes[0]) + wallet.scan_blocks(start=77, num=1) + tx = wallet.send_self_transfer(from_node=self.nodes[0])['tx'] + + # Higher fee, higher feerate, different txid, but the replacement does not provide a relay + # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB. + tx.vout[0].nValue -= 1 + assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) + if __name__ == '__main__': ReplaceByFeeTest().main() diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index ad8767556b..9cf46d9d11 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -5,7 +5,6 @@ """Test the SegWit changeover logic.""" from decimal import Decimal -from io import BytesIO from test_framework.address import ( key_to_p2pkh, @@ -14,9 +13,34 @@ from test_framework.address import ( script_to_p2sh_p2wsh, script_to_p2wsh, ) -from test_framework.blocktools import witness_script, send_to_witness -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, sha256, ToHex -from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, OP_TRUE, OP_DROP +from test_framework.blocktools import ( + send_to_witness, + witness_script, +) +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + tx_from_hex, +) +from test_framework.script import ( + CScript, + OP_0, + OP_1, + OP_2, + OP_CHECKMULTISIG, + OP_CHECKSIG, + OP_DROP, + OP_TRUE, +) +from test_framework.script_util import ( + key_to_p2pkh_script, + key_to_p2wpkh_script, + script_to_p2sh_script, + script_to_p2wsh_script, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -179,7 +203,7 @@ class SegWitTest(BitcoinTestFramework): assert self.nodes[1].getblock(blockhash, False) == self.nodes[2].getblock(blockhash, False) for tx_id in segwit_tx_list: - tx = FromHex(CTransaction(), self.nodes[2].gettransaction(tx_id)["hex"]) + tx = tx_from_hex(self.nodes[2].gettransaction(tx_id)["hex"]) assert self.nodes[2].getrawtransaction(tx_id, False, blockhash) != self.nodes[0].getrawtransaction(tx_id, False, blockhash) assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].getrawtransaction(tx_id, False, blockhash) assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) != self.nodes[2].gettransaction(tx_id)["hex"] @@ -225,12 +249,12 @@ class SegWitTest(BitcoinTestFramework): # tx1 is allowed to appear in the block, but no others. txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996")) hex_tx = self.nodes[0].gettransaction(txid)['hex'] - tx = FromHex(CTransaction(), hex_tx) + tx = tx_from_hex(hex_tx) assert tx.wit.is_null() # This should not be a segwit input assert txid1 in self.nodes[0].getrawmempool() tx1_hex = self.nodes[0].gettransaction(txid1)['hex'] - tx1 = FromHex(CTransaction(), tx1_hex) + tx1 = tx_from_hex(tx1_hex) # Check that wtxid is properly reported in mempool entry (txid1) assert_equal(int(self.nodes[0].getmempoolentry(txid1)["wtxid"], 16), tx1.calc_sha256(True)) @@ -243,9 +267,9 @@ class SegWitTest(BitcoinTestFramework): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(txid1, 16), 0), b'')) tx.vout.append(CTxOut(int(49.99 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) - tx2_hex = self.nodes[0].signrawtransactionwithwallet(ToHex(tx))['hex'] + tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())['hex'] txid2 = self.nodes[0].sendrawtransaction(tx2_hex) - tx = FromHex(CTransaction(), tx2_hex) + tx = tx_from_hex(tx2_hex) assert not tx.wit.is_null() # Check that wtxid is properly reported in mempool entry (txid2) @@ -260,7 +284,7 @@ class SegWitTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(int(txid2, 16), 0), b"")) tx.vout.append(CTxOut(int(49.95 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) # Huge fee tx.calc_sha256() - txid3 = self.nodes[0].sendrawtransaction(hexstring=ToHex(tx), maxfeerate=0) + txid3 = self.nodes[0].sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) assert tx.wit.is_null() assert txid3 in self.nodes[0].getrawmempool() @@ -329,7 +353,7 @@ class SegWitTest(BitcoinTestFramework): multisig_without_privkey_address = self.nodes[0].addmultisigaddress(2, [pubkeys[3], pubkeys[4]])['address'] script = CScript([OP_2, hex_str_to_bytes(pubkeys[3]), hex_str_to_bytes(pubkeys[4]), OP_2, OP_CHECKMULTISIG]) - solvable_after_importaddress.append(CScript([OP_HASH160, hash160(script), OP_EQUAL])) + solvable_after_importaddress.append(script_to_p2sh_script(script)) for i in compressed_spendable_address: v = self.nodes[0].getaddressinfo(i) @@ -403,10 +427,10 @@ class SegWitTest(BitcoinTestFramework): op0 = CScript([OP_0]) # 2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe is the P2SH(P2PKH) version of mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V unsolvable_address_key = hex_str_to_bytes("02341AEC7587A51CDE5279E0630A531AEA2615A9F80B17E8D9376327BAEAA59E3D") - unsolvablep2pkh = CScript([OP_DUP, OP_HASH160, hash160(unsolvable_address_key), OP_EQUALVERIFY, OP_CHECKSIG]) - unsolvablep2wshp2pkh = CScript([OP_0, sha256(unsolvablep2pkh)]) - p2shop0 = CScript([OP_HASH160, hash160(op0), OP_EQUAL]) - p2wshop1 = CScript([OP_0, sha256(op1)]) + unsolvablep2pkh = key_to_p2pkh_script(unsolvable_address_key) + unsolvablep2wshp2pkh = script_to_p2wsh_script(unsolvablep2pkh) + p2shop0 = script_to_p2sh_script(op0) + p2wshop1 = script_to_p2wsh_script(op1) unsolvable_after_importaddress.append(unsolvablep2pkh) unsolvable_after_importaddress.append(unsolvablep2wshp2pkh) unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script @@ -426,16 +450,16 @@ class SegWitTest(BitcoinTestFramework): if (v['isscript']): bare = hex_str_to_bytes(v['hex']) importlist.append(bare.hex()) - importlist.append(CScript([OP_0, sha256(bare)]).hex()) + importlist.append(script_to_p2wsh_script(bare).hex()) else: pubkey = hex_str_to_bytes(v['pubkey']) p2pk = CScript([pubkey, OP_CHECKSIG]) - p2pkh = CScript([OP_DUP, OP_HASH160, hash160(pubkey), OP_EQUALVERIFY, OP_CHECKSIG]) + p2pkh = key_to_p2pkh_script(pubkey) importlist.append(p2pk.hex()) importlist.append(p2pkh.hex()) - importlist.append(CScript([OP_0, hash160(pubkey)]).hex()) - importlist.append(CScript([OP_0, sha256(p2pk)]).hex()) - importlist.append(CScript([OP_0, sha256(p2pkh)]).hex()) + importlist.append(key_to_p2wpkh_script(pubkey).hex()) + importlist.append(script_to_p2wsh_script(p2pk).hex()) + importlist.append(script_to_p2wsh_script(p2pkh).hex()) importlist.append(unsolvablep2pkh.hex()) importlist.append(unsolvablep2wshp2pkh.hex()) @@ -590,31 +614,29 @@ class SegWitTest(BitcoinTestFramework): def p2sh_address_to_script(self, v): bare = CScript(hex_str_to_bytes(v['hex'])) p2sh = CScript(hex_str_to_bytes(v['scriptPubKey'])) - p2wsh = CScript([OP_0, sha256(bare)]) - p2sh_p2wsh = CScript([OP_HASH160, hash160(p2wsh), OP_EQUAL]) + p2wsh = script_to_p2wsh_script(bare) + p2sh_p2wsh = script_to_p2sh_script(p2wsh) return([bare, p2sh, p2wsh, p2sh_p2wsh]) def p2pkh_address_to_script(self, v): pubkey = hex_str_to_bytes(v['pubkey']) - p2wpkh = CScript([OP_0, hash160(pubkey)]) - p2sh_p2wpkh = CScript([OP_HASH160, hash160(p2wpkh), OP_EQUAL]) + p2wpkh = key_to_p2wpkh_script(pubkey) + p2sh_p2wpkh = script_to_p2sh_script(p2wpkh) p2pk = CScript([pubkey, OP_CHECKSIG]) p2pkh = CScript(hex_str_to_bytes(v['scriptPubKey'])) - p2sh_p2pk = CScript([OP_HASH160, hash160(p2pk), OP_EQUAL]) - p2sh_p2pkh = CScript([OP_HASH160, hash160(p2pkh), OP_EQUAL]) - p2wsh_p2pk = CScript([OP_0, sha256(p2pk)]) - p2wsh_p2pkh = CScript([OP_0, sha256(p2pkh)]) - p2sh_p2wsh_p2pk = CScript([OP_HASH160, hash160(p2wsh_p2pk), OP_EQUAL]) - p2sh_p2wsh_p2pkh = CScript([OP_HASH160, hash160(p2wsh_p2pkh), OP_EQUAL]) + p2sh_p2pk = script_to_p2sh_script(p2pk) + p2sh_p2pkh = script_to_p2sh_script(p2pkh) + p2wsh_p2pk = script_to_p2wsh_script(p2pk) + p2wsh_p2pkh = script_to_p2wsh_script(p2pkh) + p2sh_p2wsh_p2pk = script_to_p2sh_script(p2wsh_p2pk) + p2sh_p2wsh_p2pkh = script_to_p2sh_script(p2wsh_p2pkh) return [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] def create_and_mine_tx_from_txids(self, txids, success=True): tx = CTransaction() for i in txids: - txtmp = CTransaction() txraw = self.nodes[0].getrawtransaction(i, 0, txs_mined[i]) - f = BytesIO(hex_str_to_bytes(txraw)) - txtmp.deserialize(f) + txtmp = tx_from_hex(txraw) for j in range(len(txtmp.vout)): tx.vin.append(CTxIn(COutPoint(int('0x' + i, 0), j))) tx.vout.append(CTxOut(0, CScript())) diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index fc04853199..f27ab2057c 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -19,7 +19,6 @@ from test_framework.messages import ( CTxIn, CTxInWitness, CTxOut, - ToHex, ) from test_framework.script import ( ANNEX_TAG, @@ -58,7 +57,6 @@ from test_framework.script import ( OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, - OP_HASH160, OP_IF, OP_NOP, OP_NOT, @@ -77,12 +75,17 @@ from test_framework.script import ( is_op_success, taproot_construct, ) +from test_framework.script_util import ( + key_to_p2wpkh_script, + keyhash_to_p2pkh_script, + script_to_p2sh_script, + script_to_p2wsh_script, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_raises_rpc_error, assert_equal from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey from test_framework.address import ( hash160, - sha256, ) from collections import OrderedDict, namedtuple from io import BytesIO @@ -459,13 +462,13 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh= # P2WPKH assert script is None pubkeyhash = hash160(pkh) - spk = CScript([OP_0, pubkeyhash]) - conf["scriptcode"] = CScript([OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]) + spk = key_to_p2wpkh_script(pkh) + conf["scriptcode"] = keyhash_to_p2pkh_script(pubkeyhash) conf["script_witv0"] = None conf["inputs"] = [getter("sign"), pkh] elif script is not None: # P2WSH - spk = CScript([OP_0, sha256(script)]) + spk = script_to_p2wsh_script(script) conf["scriptcode"] = script conf["script_witv0"] = script else: @@ -476,7 +479,7 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh= # P2PKH assert script is None pubkeyhash = hash160(pkh) - spk = CScript([OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]) + spk = keyhash_to_p2pkh_script(pubkeyhash) conf["scriptcode"] = spk conf["inputs"] = [getter("sign"), pkh] elif script is not None: @@ -497,7 +500,7 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh= if p2sh: # P2SH wrapper can be combined with anything else conf["script_p2sh"] = spk - spk = CScript([OP_HASH160, hash160(spk), OP_EQUAL]) + spk = script_to_p2sh_script(spk) conf = {**conf, **kwargs} @@ -519,9 +522,9 @@ def add_spender(spenders, *args, **kwargs): def random_checksig_style(pubkey): """Creates a random CHECKSIG* tapscript that would succeed with only the valid signature on witness stack.""" opcode = random.choice([OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD]) - if (opcode == OP_CHECKSIGVERIFY): + if opcode == OP_CHECKSIGVERIFY: ret = CScript([pubkey, opcode, OP_1]) - elif (opcode == OP_CHECKSIGADD): + elif opcode == OP_CHECKSIGADD: num = random.choice([0, 0x7fffffff, -0x7fffffff]) ret = CScript([num, pubkey, opcode, num + 1, OP_EQUAL]) else: @@ -1190,19 +1193,36 @@ def dump_json_test(tx, input_utxos, idx, success, failure): # Data type to keep track of UTXOs, where they were created, and how to spend them. UTXOData = namedtuple('UTXOData', 'outpoint,output,spender') + class TaprootTest(BitcoinTestFramework): def add_options(self, parser): parser.add_argument("--dumptests", dest="dump_tests", default=False, action="store_true", help="Dump generated test cases to directory set by TEST_DUMP_DIR environment variable") + parser.add_argument("--previous_release", dest="previous_release", default=False, action="store_true", + help="Use a previous release as taproot-inactive node") def skip_test_if_missing_module(self): self.skip_if_no_wallet() + if self.options.previous_release: + self.skip_if_no_previous_releases() def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True # Node 0 has Taproot inactive, Node 1 active. - self.extra_args = [["-par=1", "-vbparams=taproot:1:1"], ["-par=1"]] + self.extra_args = [["-par=1"], ["-par=1"]] + if self.options.previous_release: + self.wallet_names = [None, self.default_wallet_name] + else: + self.extra_args[0].append("-vbparams=taproot:1:1") + + def setup_nodes(self): + self.add_nodes(self.num_nodes, self.extra_args, versions=[ + 200100 if self.options.previous_release else None, + None, + ]) + self.start_nodes() + self.import_deterministic_coinbase_privkeys() def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False): @@ -1224,7 +1244,7 @@ class TaprootTest(BitcoinTestFramework): block_response = node.submitblock(block.serialize().hex()) if err_msg is not None: assert block_response is not None and err_msg in block_response, "Missing error message '%s' from block response '%s': %s" % (err_msg, "(None)" if block_response is None else block_response, msg) - if (accept): + if accept: assert node.getbestblockhash() == block.hash, "Failed to accept: %s (response: %s)" % (msg, block_response) self.tip = block.sha256 self.lastblockhash = block.hash @@ -1306,7 +1326,7 @@ class TaprootTest(BitcoinTestFramework): # Add change fund_tx.vout.append(CTxOut(balance - 10000, random.choice(host_spks))) # Ask the wallet to sign - ss = BytesIO(bytes.fromhex(node.signrawtransactionwithwallet(ToHex(fund_tx))["hex"])) + ss = BytesIO(bytes.fromhex(node.signrawtransactionwithwallet(fund_tx.serialize().hex())["hex"])) fund_tx.deserialize(ss) # Construct UTXOData entries fund_tx.rehash() diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index ce00faffee..afc0bdb8c5 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -9,7 +9,7 @@ import struct from test_framework.messages import ( CBlock, COutPoint, - FromHex, + from_hex, ) from test_framework.muhash import MuHash3072 from test_framework.test_framework import BitcoinTestFramework @@ -32,13 +32,13 @@ class UTXOSetHashTest(BitcoinTestFramework): # Generate 100 blocks and remove the first since we plan to spend its # coinbase block_hashes = wallet.generate(1) + node.generate(99) - blocks = list(map(lambda block: FromHex(CBlock(), node.getblock(block, False)), block_hashes)) + blocks = list(map(lambda block: from_hex(CBlock(), node.getblock(block, False)), block_hashes)) blocks.pop(0) # Create a spending transaction and mine a block which includes it txid = wallet.send_self_transfer(from_node=node)['txid'] tx_block = node.generateblock(output=wallet.get_address(), transactions=[txid]) - blocks.append(FromHex(CBlock(), node.getblock(tx_block['hash'], False))) + blocks.append(from_hex(CBlock(), node.getblock(tx_block['hash'], False))) # Serialize the outputs that should be in the UTXO set and add them to # a MuHash object diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index 062a7affb5..1c9e237d78 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -21,8 +21,8 @@ VB_TOP_BITS = 0x20000000 VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT) -WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT) -VB_PATTERN = re.compile("Warning: unknown new rules activated.*versionbit") +WARN_UNKNOWN_RULES_ACTIVE = "Unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT) +VB_PATTERN = re.compile("Unknown new rules activated.*versionbit") class VersionBitsWarningTest(BitcoinTestFramework): def set_test_params(self): diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 30cd499b3f..22eec59600 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -10,10 +10,12 @@ from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_greater_than_or_equal, assert_raises_process_error, assert_raises_rpc_error, get_auth_cookie, ) +import time # The block reward of coinbaseoutput.nValue (50) BTC/block matures after # COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect @@ -248,6 +250,12 @@ class TestBitcoinCli(BitcoinTestFramework): self.nodes[0].wait_for_rpc_connection() assert_equal(blocks, BLOCKS + 25) + self.log.info("Test -rpcwait option waits at most -rpcwaittimeout seconds for startup") + self.stop_node(0) # stop the node so we time out + start_time = time.time() + assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo) + assert_greater_than_or_equal(time.time(), start_time + 5) + if __name__ == '__main__': TestBitcoinCli().main() diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 94e162b748..15f352d68c 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -5,10 +5,21 @@ """Test the ZMQ notification interface.""" import struct -from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE, ADDRESS_BCRT1_P2WSH_OP_TRUE -from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment +from test_framework.address import ( + ADDRESS_BCRT1_P2WSH_OP_TRUE, + ADDRESS_BCRT1_UNSPENDABLE, +) +from test_framework.blocktools import ( + add_witness_commitment, + create_block, + create_coinbase, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import CTransaction, hash256, FromHex +from test_framework.messages import ( + CTransaction, + hash256, + tx_from_hex, +) from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -393,10 +404,10 @@ class ZMQTest (BitcoinTestFramework): bump_info = self.nodes[0].bumpfee(orig_txid) # Mine the pre-bump tx block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1)) - tx = FromHex(CTransaction(), raw_tx) + tx = tx_from_hex(raw_tx) block.vtx.append(tx) for txid in more_tx: - tx = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) + tx = tx_from_hex(self.nodes[0].getrawtransaction(txid)) block.vtx.append(tx) add_witness_commitment(block) block.solve() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 12aac3ab65..60c0953f6f 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -5,7 +5,6 @@ """Test mempool acceptance of raw transactions.""" from decimal import Decimal -from io import BytesIO import math from test_framework.test_framework import BitcoinTestFramework @@ -14,26 +13,27 @@ from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, COIN, COutPoint, - CTransaction, + CTxIn, CTxOut, MAX_BLOCK_BASE_SIZE, MAX_MONEY, + tx_from_hex, ) from test_framework.script import ( - hash160, CScript, OP_0, OP_2, OP_3, OP_CHECKMULTISIG, - OP_EQUAL, OP_HASH160, OP_RETURN, ) +from test_framework.script_util import ( + script_to_p2sh_script, +) from test_framework.util import ( assert_equal, assert_raises_rpc_error, - hex_str_to_bytes, ) @@ -91,8 +91,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later outputs=[{node.getnewaddress(): Decimal('0.3') - fee}], ))['hex'] - tx = CTransaction() - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], @@ -107,7 +106,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): outputs=[{node.getnewaddress(): output_amount}], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) + tx = tx_from_hex(raw_tx_final) fee_expected = coin['amount'] - output_amount self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], @@ -126,11 +125,11 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction that replaces a mempool transaction') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}], @@ -141,7 +140,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): # Send the transaction that replaces the mempool transaction and opts out of replaceability node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) # take original raw_tx_0 - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee # skip re-signing the tx self.check_mempool_result( @@ -151,7 +150,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with missing inputs, that never existed') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) # skip re-signing the tx self.check_mempool_result( @@ -160,7 +159,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with missing inputs, that existed once in the past') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) + tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) @@ -190,7 +189,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): inputs=[{'txid': txid_spend_both, 'vout': 0}], outputs=[{node.getnewaddress(): 0.05}], ))['hex'] - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], @@ -199,17 +198,17 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with no outputs') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout = [] # Skip re-signing the transaction for context independent checks from now on - # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']))) + # tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A really large transaction') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-oversize'}], @@ -217,7 +216,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with negative output value') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout[0].nValue *= -1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-negative'}], @@ -226,7 +225,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): # The following two validations prevent overflow of the output amounts (see CVE-2010-5139). self.log.info('A transaction with too large output value') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout[0].nValue = MAX_MONEY + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge'}], @@ -234,7 +233,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with too large sum of output values') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = MAX_MONEY self.check_mempool_result( @@ -243,36 +242,44 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction with duplicate inputs') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vin = [tx.vin[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-inputs-duplicate'}], rawtxs=[tx.serialize().hex()], ) + self.log.info('A non-coinbase transaction with coinbase-like outpoint') + tx = tx_from_hex(raw_tx_reference) + tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff))) + self.check_mempool_result( + result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-prevout-null'}], + rawtxs=[tx.serialize().hex()], + ) + self.log.info('A coinbase transaction') # Pick the input of the first tx we signed, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) + tx = tx_from_hex(raw_tx_coinbase_spent) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'coinbase'}], rawtxs=[tx.serialize().hex()], ) self.log.info('Some nonstandard transactions') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.nVersion = 3 # A version currently non-standard self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'version'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptpubkey'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) key = ECKey() key.generate() pubkey = key.get_pubkey().get_bytes() @@ -281,34 +288,34 @@ class MempoolAcceptanceTest(BitcoinTestFramework): result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bare-multisig'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-not-pushonly'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(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])) + tx = tx_from_hex(raw_tx_reference) + output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=script_to_p2sh_script(b'burn')) 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 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout[0] = output_p2sh_burn tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}], rawtxs=[tx.serialize().hex()], ) - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( @@ -317,7 +324,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A timelocked transaction') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( @@ -326,7 +333,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A transaction that is locked by BIP68 sequence logic') - tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) + tx = tx_from_hex(raw_tx_reference) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one # Can skip re-signing the tx because of early rejection self.check_mempool_result( diff --git a/test/functional/mempool_accept_wtxid.py b/test/functional/mempool_accept_wtxid.py new file mode 100755 index 0000000000..dd1f8997ad --- /dev/null +++ b/test/functional/mempool_accept_wtxid.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test mempool acceptance in case of an already known transaction +with identical non-witness data different witness. +""" + +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxInWitness, + CTxOut, + sha256, +) +from test_framework.script import ( + CScript, + OP_0, + OP_ELSE, + OP_ENDIF, + OP_EQUAL, + OP_HASH160, + OP_IF, + OP_TRUE, + hash160, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +class MempoolWtxidTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def run_test(self): + node = self.nodes[0] + + self.log.info('Start with empty mempool and 101 blocks') + # The last 100 coinbase transactions are premature + blockhash = node.generate(101)[0] + txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"] + assert_equal(node.getmempoolinfo()['size'], 0) + + self.log.info("Submit parent with multiple script branches to mempool") + hashlock = hash160(b'Preimage') + witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF]) + witness_program = sha256(witness_script) + script_pubkey = CScript([OP_0, witness_program]) + + parent = CTransaction() + parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b"")) + parent.vout.append(CTxOut(int(9.99998 * COIN), script_pubkey)) + parent.rehash() + + privkeys = [node.get_deterministic_priv_key().key] + raw_parent = node.signrawtransactionwithkey(hexstring=parent.serialize().hex(), privkeys=privkeys)['hex'] + parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0) + node.generate(1) + + # Create a new transaction with witness solving first branch + child_witness_script = CScript([OP_TRUE]) + child_witness_program = sha256(child_witness_script) + child_script_pubkey = CScript([OP_0, child_witness_program]) + + child_one = CTransaction() + child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b"")) + child_one.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey)) + child_one.wit.vtxinwit.append(CTxInWitness()) + child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script] + child_one_wtxid = child_one.getwtxid() + child_one_txid = child_one.rehash() + + # Create another identical transaction with witness solving second branch + child_two = CTransaction() + child_two.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b"")) + child_two.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey)) + child_two.wit.vtxinwit.append(CTxInWitness()) + child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script] + child_two_wtxid = child_two.getwtxid() + child_two_txid = child_two.rehash() + + assert_equal(child_one_txid, child_two_txid) + assert child_one_wtxid != child_two_wtxid + + self.log.info("Submit one child to the mempool") + txid_submitted = node.sendrawtransaction(child_one.serialize().hex()) + assert_equal(node.getrawmempool(True)[txid_submitted]['wtxid'], child_one_wtxid) + + # testmempoolaccept reports the "already in mempool" error + assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{ + "txid": child_one_txid, + "wtxid": child_one_wtxid, + "allowed": False, + "reject-reason": "txn-already-in-mempool" + }]) + testres_child_two = node.testmempoolaccept([child_two.serialize().hex()])[0] + assert_equal(testres_child_two, { + "txid": child_two_txid, + "wtxid": child_two_wtxid, + "allowed": False, + "reject-reason": "txn-same-nonwitness-data-in-mempool" + }) + + # sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool + node.sendrawtransaction(child_one.serialize().hex()) + # sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool + node.sendrawtransaction(child_two.serialize().hex()) + + +if __name__ == '__main__': + MempoolWtxidTest().main() diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index 6de6778909..87f40b7f2b 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -7,10 +7,7 @@ NOTE: The test is designed to prevent cases when compatibility is broken accidentally. In case we need to break mempool compatibility we can continue to use the test by just bumping the version number. -Download node binaries: -test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 - -Only v0.15.2 is required by this test. The rest is used in other backwards compatibility tests. +The previous release v0.15.2 is required by this test, see test/README.md. """ import os diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index 1e9895e621..fcd8b061fa 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -11,7 +11,11 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + chain_transaction, +) MAX_ANCESTORS = 25 MAX_DESCENDANTS = 25 @@ -24,23 +28,6 @@ class MempoolPackagesTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - # Build a transaction that spends parent_txid:vout - # Return amount sent - def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs): - send_value = satoshi_round((value - fee)/num_outputs) - inputs = [] - for (txid, vout) in zip(parent_txids, vouts): - inputs.append({'txid' : txid, 'vout' : vout}) - outputs = {} - for _ in range(num_outputs): - outputs[node.getnewaddress()] = send_value - rawtx = node.createrawtransaction(inputs, outputs, 0, True) - signedtx = node.signrawtransactionwithwallet(rawtx) - txid = node.sendrawtransaction(signedtx['hex']) - fulltx = node.getrawtransaction(txid, 1) - assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output - return (txid, send_value) - def run_test(self): # Mine some blocks and have them mature. self.nodes[0].generate(COINBASE_MATURITY + 1) @@ -53,32 +40,32 @@ class MempoolPackagesTest(BitcoinTestFramework): # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] for _ in range(4): - (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) + (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) vout = 0 value = sent_value chain.append([txid, value]) for _ in range(MAX_ANCESTORS - 4): - (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) + (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) value = sent_value chain.append([txid, value]) - (second_chain, second_chain_value) = self.chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) + (second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) # Check mempool has MAX_ANCESTORS + 1 transactions in it assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1) # Adding one more transaction on to the chain should fail. - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) # ...even if it chains on from some point in the middle of the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) # ...even if it chains on to two parent transactions with one in the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) # ...especially if its > 40k weight - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) # But not if it chains directly off the first transaction - (replacable_txid, replacable_orig_value) = self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + (replacable_txid, replacable_orig_value) = chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) # and the second chain should work just fine - self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) # Make sure we can RBF the chain which used our carve-out rule second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))} diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 606717d890..5fc3ec23ae 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -13,6 +13,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, + chain_transaction, satoshi_round, ) @@ -42,21 +43,6 @@ class MempoolPackagesTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - # Build a transaction that spends parent_txid:vout - # Return amount sent - def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs): - send_value = satoshi_round((value - fee)/num_outputs) - inputs = [ {'txid' : parent_txid, 'vout' : vout} ] - outputs = {} - for _ in range(num_outputs): - outputs[node.getnewaddress()] = send_value - rawtx = node.createrawtransaction(inputs, outputs) - signedtx = node.signrawtransactionwithwallet(rawtx) - txid = node.sendrawtransaction(signedtx['hex']) - fulltx = node.getrawtransaction(txid, 1) - assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output - return (txid, send_value) - def run_test(self): # Mine some blocks and have them mature. peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs @@ -71,7 +57,7 @@ class MempoolPackagesTest(BitcoinTestFramework): chain = [] witness_chain = [] for _ in range(MAX_ANCESTORS): - (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1) + (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) value = sent_value chain.append(txid) # We need the wtxids to check P2P announcements @@ -189,7 +175,7 @@ class MempoolPackagesTest(BitcoinTestFramework): assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) # Adding one more transaction on to the chain should fail. - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1) # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. @@ -238,7 +224,7 @@ class MempoolPackagesTest(BitcoinTestFramework): transaction_package = [] tx_children = [] # First create one parent tx with 10 children - (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10) + (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 10) parent_transaction = txid for i in range(10): transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) @@ -247,7 +233,7 @@ class MempoolPackagesTest(BitcoinTestFramework): chain = [] # save sent txs for the purpose of checking node1's mempool later (see below) for _ in range(MAX_DESCENDANTS - 1): utxo = transaction_package.pop(0) - (txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) + (txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) chain.append(txid) if utxo['txid'] is parent_transaction: tx_children.append(txid) @@ -263,7 +249,7 @@ class MempoolPackagesTest(BitcoinTestFramework): # Sending one more chained transaction will fail utxo = transaction_package.pop(0) - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) + assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) # Check that node1's mempool is as expected, containing: # - txs from previous ancestor test (-> custom ancestor limit) @@ -321,13 +307,13 @@ class MempoolPackagesTest(BitcoinTestFramework): value = send_value # Create tx1 - tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) + tx1_id, _ = chain_transaction(self.nodes[0], [tx0_id], [0], value, fee, 1) # Create tx2-7 vout = 1 txid = tx0_id for _ in range(6): - (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) + (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 1) vout = 0 value = sent_value diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py index 87297989ba..1a414959b9 100755 --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -23,30 +23,29 @@ import time class AddrReceiver(P2PInterface): num_ipv4_received = 0 + test_addr_contents = False - def on_addr(self, message): - for addr in message.addrs: - assert_equal(addr.nServices, 9) - if not 8333 <= addr.port < 8343: - raise AssertionError("Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) - assert addr.ip.startswith('123.123.123.') - self.num_ipv4_received += 1 - - -class GetAddrStore(P2PInterface): - getaddr_received = False - num_ipv4_received = 0 - - def on_getaddr(self, message): - self.getaddr_received = True + def __init__(self, test_addr_contents=False): + super().__init__() + self.test_addr_contents = test_addr_contents def on_addr(self, message): for addr in message.addrs: self.num_ipv4_received += 1 + if(self.test_addr_contents): + # relay_tests checks the content of the addr messages match + # expectations based on the message creation in setup_addr_msg + assert_equal(addr.nServices, 9) + if not 8333 <= addr.port < 8343: + raise AssertionError("Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) + assert addr.ip.startswith('123.123.123.') def addr_received(self): return self.num_ipv4_received != 0 + def getaddr_received(self): + return self.message_count['getaddr'] > 0 + class AddrTest(BitcoinTestFramework): counter = 0 @@ -79,7 +78,7 @@ class AddrTest(BitcoinTestFramework): def send_addr_msg(self, source, msg, receivers): source.send_and_ping(msg) # pop m_next_addr_send timer - self.mocktime += 5 * 60 + self.mocktime += 10 * 60 self.nodes[0].setmocktime(self.mocktime) for peer in receivers: peer.sync_send_with_ping() @@ -101,7 +100,7 @@ class AddrTest(BitcoinTestFramework): num_receivers = 7 receivers = [] for _ in range(num_receivers): - receivers.append(self.nodes[0].add_p2p_connection(AddrReceiver())) + receivers.append(self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True))) # Keep this with length <= 10. Addresses from larger messages are not # relayed. @@ -125,8 +124,8 @@ class AddrTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() self.log.info('Check relay of addresses received from outbound peers') - inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver()) - full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(GetAddrStore(), p2p_idx=0, connection_type="outbound-full-relay") + inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True)) + full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay") msg = self.setup_addr_msg(2) self.send_addr_msg(full_outbound_peer, msg, [inbound_peer]) self.log.info('Check that the first addr message received from an outbound peer is not relayed') @@ -142,7 +141,7 @@ class AddrTest(BitcoinTestFramework): assert_equal(inbound_peer.num_ipv4_received, 2) self.log.info('Check address relay to outbound peers') - block_relay_peer = self.nodes[0].add_outbound_p2p_connection(GetAddrStore(), p2p_idx=1, connection_type="block-relay-only") + block_relay_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=1, connection_type="block-relay-only") msg3 = self.setup_addr_msg(2) self.send_addr_msg(inbound_peer, msg3, [full_outbound_peer, block_relay_peer]) @@ -156,17 +155,17 @@ class AddrTest(BitcoinTestFramework): def getaddr_tests(self): self.log.info('Test getaddr behavior') self.log.info('Check that we send a getaddr message upon connecting to an outbound-full-relay peer') - full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(GetAddrStore(), p2p_idx=0, connection_type="outbound-full-relay") + full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay") full_outbound_peer.sync_with_ping() - assert full_outbound_peer.getaddr_received + assert full_outbound_peer.getaddr_received() self.log.info('Check that we do not send a getaddr message upon connecting to a block-relay-only peer') - block_relay_peer = self.nodes[0].add_outbound_p2p_connection(GetAddrStore(), p2p_idx=1, connection_type="block-relay-only") + block_relay_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=1, connection_type="block-relay-only") block_relay_peer.sync_with_ping() - assert_equal(block_relay_peer.getaddr_received, False) + assert_equal(block_relay_peer.getaddr_received(), False) self.log.info('Check that we answer getaddr messages only from inbound peers') - inbound_peer = self.nodes[0].add_p2p_connection(GetAddrStore()) + inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver()) inbound_peer.sync_with_ping() # Add some addresses to addrman @@ -182,7 +181,7 @@ class AddrTest(BitcoinTestFramework): self.mocktime += 5 * 60 self.nodes[0].setmocktime(self.mocktime) - inbound_peer.wait_until(inbound_peer.addr_received) + inbound_peer.wait_until(lambda: inbound_peer.addr_received() is True) assert_equal(full_outbound_peer.num_ipv4_received, 0) assert_equal(block_relay_peer.num_ipv4_received, 0) @@ -196,9 +195,9 @@ class AddrTest(BitcoinTestFramework): self.mocktime = int(time.time()) self.log.info('Check that we send getaddr messages') - full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(GetAddrStore(), p2p_idx=0, connection_type="outbound-full-relay") + full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay") full_outbound_peer.sync_with_ping() - assert full_outbound_peer.getaddr_received + assert full_outbound_peer.getaddr_received() self.log.info('Check that we relay address messages') addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py index 03662babef..63fc2a98d4 100755 --- a/test/functional/p2p_blockfilters.py +++ b/test/functional/p2p_blockfilters.py @@ -24,7 +24,7 @@ from test_framework.util import ( assert_equal, ) -class CFiltersClient(P2PInterface): +class FiltersClient(P2PInterface): def __init__(self): super().__init__() # Store the cfilters received. @@ -39,6 +39,7 @@ class CFiltersClient(P2PInterface): """Store cfilters received in a list.""" self.cfilters.append(message) + class CompactFiltersTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -51,8 +52,8 @@ class CompactFiltersTest(BitcoinTestFramework): def run_test(self): # Node 0 supports COMPACT_FILTERS, node 1 does not. - node0 = self.nodes[0].add_p2p_connection(CFiltersClient()) - node1 = self.nodes[1].add_p2p_connection(CFiltersClient()) + peer_0 = self.nodes[0].add_p2p_connection(FiltersClient()) + peer_1 = self.nodes[1].add_p2p_connection(FiltersClient()) # Nodes 0 & 1 share the same first 999 blocks in the chain. self.nodes[0].generate(999) @@ -61,16 +62,16 @@ class CompactFiltersTest(BitcoinTestFramework): # Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting self.disconnect_nodes(0, 1) - self.nodes[0].generate(1) - self.wait_until(lambda: self.nodes[0].getblockcount() == 1000) - stale_block_hash = self.nodes[0].getblockhash(1000) + stale_block_hash = self.nodes[0].generate(1)[0] + self.nodes[0].syncwithvalidationinterfacequeue() + assert_equal(self.nodes[0].getblockcount(), 1000) self.nodes[1].generate(1001) - self.wait_until(lambda: self.nodes[1].getblockcount() == 2000) + assert_equal(self.nodes[1].getblockcount(), 2000) # Check that nodes have signalled NODE_COMPACT_FILTERS correctly. - assert node0.nServices & NODE_COMPACT_FILTERS != 0 - assert node1.nServices & NODE_COMPACT_FILTERS == 0 + assert peer_0.nServices & NODE_COMPACT_FILTERS != 0 + assert peer_1.nServices & NODE_COMPACT_FILTERS == 0 # Check that the localservices is as expected. assert int(self.nodes[0].getnetworkinfo()['localservices'], 16) & NODE_COMPACT_FILTERS != 0 @@ -79,10 +80,10 @@ class CompactFiltersTest(BitcoinTestFramework): self.log.info("get cfcheckpt on chain to be re-orged out.") request = msg_getcfcheckpt( filter_type=FILTER_TYPE_BASIC, - stop_hash=int(stale_block_hash, 16) + stop_hash=int(stale_block_hash, 16), ) - node0.send_and_ping(message=request) - response = node0.last_message['cfcheckpt'] + peer_0.send_and_ping(message=request) + response = peer_0.last_message['cfcheckpt'] assert_equal(response.filter_type, request.filter_type) assert_equal(response.stop_hash, request.stop_hash) assert_equal(len(response.headers), 1) @@ -90,6 +91,7 @@ class CompactFiltersTest(BitcoinTestFramework): self.log.info("Reorg node 0 to a new chain.") self.connect_nodes(0, 1) self.sync_blocks(timeout=600) + self.nodes[0].syncwithvalidationinterfacequeue() main_block_hash = self.nodes[0].getblockhash(1000) assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize" @@ -98,10 +100,10 @@ class CompactFiltersTest(BitcoinTestFramework): tip_hash = self.nodes[0].getbestblockhash() request = msg_getcfcheckpt( filter_type=FILTER_TYPE_BASIC, - stop_hash=int(tip_hash, 16) + stop_hash=int(tip_hash, 16), ) - node0.send_and_ping(request) - response = node0.last_message['cfcheckpt'] + peer_0.send_and_ping(request) + response = peer_0.last_message['cfcheckpt'] assert_equal(response.filter_type, request.filter_type) assert_equal(response.stop_hash, request.stop_hash) @@ -109,51 +111,51 @@ class CompactFiltersTest(BitcoinTestFramework): tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header'] assert_equal( response.headers, - [int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)] + [int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)], ) self.log.info("Check that peers can fetch cfcheckpt on stale chain.") request = msg_getcfcheckpt( filter_type=FILTER_TYPE_BASIC, - stop_hash=int(stale_block_hash, 16) + stop_hash=int(stale_block_hash, 16), ) - node0.send_and_ping(request) - response = node0.last_message['cfcheckpt'] + peer_0.send_and_ping(request) + response = peer_0.last_message['cfcheckpt'] stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header'] assert_equal( response.headers, - [int(header, 16) for header in (stale_cfcheckpt,)] + [int(header, 16) for header in (stale_cfcheckpt, )], ) self.log.info("Check that peers can fetch cfheaders on active chain.") request = msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, start_height=1, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ) - node0.send_and_ping(request) - response = node0.last_message['cfheaders'] + peer_0.send_and_ping(request) + response = peer_0.last_message['cfheaders'] main_cfhashes = response.hashes assert_equal(len(main_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), - int(main_cfcheckpt, 16) + int(main_cfcheckpt, 16), ) self.log.info("Check that peers can fetch cfheaders on stale chain.") request = msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, start_height=1, - stop_hash=int(stale_block_hash, 16) + stop_hash=int(stale_block_hash, 16), ) - node0.send_and_ping(request) - response = node0.last_message['cfheaders'] + peer_0.send_and_ping(request) + response = peer_0.last_message['cfheaders'] stale_cfhashes = response.hashes assert_equal(len(stale_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), - int(stale_cfcheckpt, 16) + int(stale_cfcheckpt, 16), ) self.log.info("Check that peers can fetch cfilters.") @@ -161,11 +163,10 @@ class CompactFiltersTest(BitcoinTestFramework): request = msg_getcfilters( filter_type=FILTER_TYPE_BASIC, start_height=1, - stop_hash=int(stop_hash, 16) + stop_hash=int(stop_hash, 16), ) - node0.send_message(request) - node0.sync_with_ping() - response = node0.pop_cfilters() + peer_0.send_and_ping(request) + response = peer_0.pop_cfilters() assert_equal(len(response), 10) self.log.info("Check that cfilter responses are correct.") @@ -180,11 +181,10 @@ class CompactFiltersTest(BitcoinTestFramework): request = msg_getcfilters( filter_type=FILTER_TYPE_BASIC, start_height=1000, - stop_hash=int(stale_block_hash, 16) + stop_hash=int(stale_block_hash, 16), ) - node0.send_message(request) - node0.sync_with_ping() - response = node0.pop_cfilters() + peer_0.send_and_ping(request) + response = peer_0.pop_cfilters() assert_equal(len(response), 1) cfilter = response[0] @@ -197,23 +197,23 @@ class CompactFiltersTest(BitcoinTestFramework): requests = [ msg_getcfcheckpt( filter_type=FILTER_TYPE_BASIC, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ), msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, start_height=1000, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ), msg_getcfilters( filter_type=FILTER_TYPE_BASIC, start_height=1000, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ), ] for request in requests: - node1 = self.nodes[1].add_p2p_connection(P2PInterface()) - node1.send_message(request) - node1.wait_for_disconnect() + peer_1 = self.nodes[1].add_p2p_connection(P2PInterface()) + peer_1.send_message(request) + peer_1.wait_for_disconnect() self.log.info("Check that invalid requests result in disconnection.") requests = [ @@ -221,18 +221,18 @@ class CompactFiltersTest(BitcoinTestFramework): msg_getcfilters( filter_type=FILTER_TYPE_BASIC, start_height=0, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ), # Requesting too many filter headers results in disconnection. msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, start_height=0, - stop_hash=int(tip_hash, 16) + stop_hash=int(tip_hash, 16), ), # Requesting unknown filter type results in disconnection. msg_getcfcheckpt( filter_type=255, - stop_hash=int(main_block_hash, 16) + stop_hash=int(main_block_hash, 16), ), # Requesting unknown hash results in disconnection. msg_getcfcheckpt( @@ -241,9 +241,10 @@ class CompactFiltersTest(BitcoinTestFramework): ), ] for request in requests: - node0 = self.nodes[0].add_p2p_connection(P2PInterface()) - node0.send_message(request) - node0.wait_for_disconnect() + peer_0 = self.nodes[0].add_p2p_connection(P2PInterface()) + peer_0.send_message(request) + peer_0.wait_for_disconnect() + def compute_last_header(prev_header, hashes): """Compute the last filter header from a starting header and a sequence of filter hashes.""" @@ -252,5 +253,6 @@ def compute_last_header(prev_header, hashes): header = hash256(ser_uint256(filter_hash) + header) return uint256_from_str(header) + if __name__ == '__main__': CompactFiltersTest().main() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 3e4f2f974d..b4e662de2e 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -15,11 +15,56 @@ from test_framework.blocktools import ( add_witness_commitment, create_block, ) -from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex -from test_framework.p2p import p2p_lock, P2PInterface -from test_framework.script import CScript, OP_TRUE, OP_DROP +from test_framework.messages import ( + BlockTransactions, + BlockTransactionsRequest, + CBlock, + CBlockHeader, + CInv, + COutPoint, + CTransaction, + CTxIn, + CTxInWitness, + CTxOut, + from_hex, + HeaderAndShortIDs, + MSG_BLOCK, + MSG_CMPCT_BLOCK, + MSG_WITNESS_FLAG, + NODE_NETWORK, + P2PHeaderAndShortIDs, + PrefilledTransaction, + calculate_shortid, + msg_block, + msg_blocktxn, + msg_cmpctblock, + msg_getblocktxn, + msg_getdata, + msg_getheaders, + msg_headers, + msg_inv, + msg_no_witness_block, + msg_no_witness_blocktxn, + msg_sendcmpct, + msg_sendheaders, + msg_tx, + ser_uint256, + tx_from_hex, +) +from test_framework.p2p import ( + P2PInterface, + p2p_lock, +) +from test_framework.script import ( + CScript, + OP_DROP, + OP_TRUE, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, softfork_active +from test_framework.util import ( + assert_equal, + softfork_active, +) # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): @@ -257,7 +302,7 @@ class CompactBlocksTest(BitcoinTestFramework): for _ in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] - tx = FromHex(CTransaction(), hex_tx) + tx = tx_from_hex(hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True @@ -276,7 +321,7 @@ class CompactBlocksTest(BitcoinTestFramework): block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. - block = FromHex(CBlock(), node.getblock("%064x" % block_hash, False)) + block = from_hex(CBlock(), node.getblock("%064x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() @@ -569,7 +614,7 @@ class CompactBlocksTest(BitcoinTestFramework): current_height = chain_height while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): block_hash = node.getblockhash(current_height) - block = FromHex(CBlock(), node.getblock(block_hash, False)) + block = from_hex(CBlock(), node.getblock(block_hash, False)) msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), []) @@ -672,9 +717,9 @@ class CompactBlocksTest(BitcoinTestFramework): [l.clear_block_announcement() for l in listeners] - # ToHex() won't serialize with witness, but this block has no witnesses - # anyway. TODO: repeat this test with witness tx's to a segwit node. - node.submitblock(ToHex(block)) + # serialize without witness (this block has no witnesses anyway). + # TODO: repeat this test with witness tx's to a segwit node. + node.submitblock(block.serialize().hex()) for l in listeners: l.wait_until(lambda: "cmpctblock" in l.last_message, timeout=30) diff --git a/test/functional/p2p_compactblocks_hb.py b/test/functional/p2p_compactblocks_hb.py new file mode 100755 index 0000000000..a3d30a6f04 --- /dev/null +++ b/test/functional/p2p_compactblocks_hb.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test compact blocks HB selection logic.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class CompactBlocksConnectionTest(BitcoinTestFramework): + """Test class for verifying selection of HB peer connections.""" + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 6 + + def peer_info(self, from_node, to_node): + """Query from_node for its getpeerinfo about to_node.""" + for peerinfo in self.nodes[from_node].getpeerinfo(): + if "(testnode%i)" % to_node in peerinfo['subver']: + return peerinfo + return None + + def setup_network(self): + self.setup_nodes() + # Start network with everyone disconnected + self.sync_all() + + def relay_block_through(self, peer): + """Relay a new block through peer peer, and return HB status between 1 and [2,3,4,5].""" + self.connect_nodes(peer, 0) + self.nodes[0].generate(1) + self.sync_blocks() + self.disconnect_nodes(peer, 0) + status_to = [self.peer_info(1, i)['bip152_hb_to'] for i in range(2, 6)] + status_from = [self.peer_info(i, 1)['bip152_hb_from'] for i in range(2, 6)] + assert_equal(status_to, status_from) + return status_to + + def run_test(self): + self.log.info("Testing reserved high-bandwidth mode slot for outbound peer...") + + # Connect everyone to node 0, and mine some blocks to get all nodes out of IBD. + for i in range(1, 6): + self.connect_nodes(i, 0) + self.nodes[0].generate(2) + self.sync_blocks() + for i in range(1, 6): + self.disconnect_nodes(i, 0) + + # Construct network topology: + # - Node 0 is the block producer + # - Node 1 is the "target" node being tested + # - Nodes 2-5 are intermediaries. + # - Node 1 has an outbound connection to node 2 + # - Node 1 has inbound connections from nodes 3-5 + self.connect_nodes(3, 1) + self.connect_nodes(4, 1) + self.connect_nodes(5, 1) + self.connect_nodes(1, 2) + + # Mine blocks subsequently relaying through nodes 3,4,5 (inbound to node 1) + for nodeid in range(3, 6): + status = self.relay_block_through(nodeid) + assert_equal(status, [False, nodeid >= 3, nodeid >= 4, nodeid >= 5]) + + # And again through each. This should not change HB status. + for nodeid in range(3, 6): + status = self.relay_block_through(nodeid) + assert_equal(status, [False, True, True, True]) + + # Now relay one block through peer 2 (outbound from node 1), so it should take HB status + # from one of the inbounds. + status = self.relay_block_through(2) + assert_equal(status[0], True) + assert_equal(sum(status), 3) + + # Now relay again through nodes 3,4,5. Since 2 is outbound, it should remain HB. + for nodeid in range(3, 6): + status = self.relay_block_through(nodeid) + assert status[0] + assert status[nodeid - 2] + assert_equal(sum(status), 3) + + # Reconnect peer 2, and retry. Now the three inbounds should be HB again. + self.disconnect_nodes(1, 2) + self.connect_nodes(1, 2) + for nodeid in range(3, 6): + status = self.relay_block_through(nodeid) + assert not status[0] + assert status[nodeid - 2] + assert_equal(status, [False, True, True, True]) + + +if __name__ == '__main__': + CompactBlocksConnectionTest().main() diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py index 2349afa1ee..52a47c9bc2 100755 --- a/test/functional/p2p_dos_header_tree.py +++ b/test/functional/p2p_dos_header_tree.py @@ -6,7 +6,7 @@ from test_framework.messages import ( CBlockHeader, - FromHex, + from_hex, ) from test_framework.p2p import ( P2PInterface, @@ -42,8 +42,8 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.headers = [l for l in h_lines if not l.startswith(FORK_PREFIX)] self.headers_fork = [l[len(FORK_PREFIX):] for l in h_lines if l.startswith(FORK_PREFIX)] - self.headers = [FromHex(CBlockHeader(), h) for h in self.headers] - self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork] + self.headers = [from_hex(CBlockHeader(), h) for h in self.headers] + self.headers_fork = [from_hex(CBlockHeader(), h) for h in self.headers_fork] self.log.info("Feed all non-fork headers, including and up to the first checkpoint") peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) diff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py index a525996493..35bce7c69e 100755 --- a/test/functional/p2p_eviction.py +++ b/test/functional/p2p_eviction.py @@ -20,7 +20,11 @@ from test_framework.blocktools import ( create_block, create_coinbase, ) -from test_framework.messages import CTransaction, FromHex, msg_pong, msg_tx +from test_framework.messages import ( + msg_pong, + msg_tx, + tx_from_hex, +) from test_framework.p2p import P2PDataStore, P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -89,7 +93,7 @@ class P2PEvict(BitcoinTestFramework): 'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'], }], )['hex'] - txpeer.send_message(msg_tx(FromHex(CTransaction(), sigtx))) + txpeer.send_message(msg_tx(tx_from_hex(sigtx))) protected_peers.add(current_peer) self.log.info("Create 8 peers and protect them from eviction by having faster pings") diff --git a/test/functional/p2p_i2p_ports.py b/test/functional/p2p_i2p_ports.py new file mode 100755 index 0000000000..13188b9305 --- /dev/null +++ b/test/functional/p2p_i2p_ports.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test ports handling for I2P hosts +""" + +import re + +from test_framework.test_framework import BitcoinTestFramework + + +class I2PPorts(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + # The test assumes that an I2P SAM proxy is not listening here. + self.extra_args = [["-i2psam=127.0.0.1:60000"]] + + def run_test(self): + node = self.nodes[0] + + self.log.info("Ensure we don't try to connect if port!=0") + addr = "zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:8333" + raised = False + try: + with node.assert_debug_log(expected_msgs=[f"Error connecting to {addr}"]): + node.addnode(node=addr, command="onetry") + except AssertionError as e: + raised = True + if not re.search(r"Expected messages .* does not partially match log", str(e)): + raise AssertionError(f"Assertion raised as expected, but with an unexpected message: {str(e)}") + if not raised: + raise AssertionError("Assertion should have been raised") + + self.log.info("Ensure we try to connect if port=0 and get an error due to missing I2P proxy") + addr = "h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0" + with node.assert_debug_log(expected_msgs=[f"Error connecting to {addr}"]): + node.addnode(node=addr, command="onetry") + + +if __name__ == '__main__': + I2PPorts().main() diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 483f25f48c..91666d0f08 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -9,8 +9,11 @@ In this test we connect to one node over p2p, and test block requests: 2) Invalid block with duplicated transaction should be re-requested. 3) Invalid block with bad coinbase value should be rejected and not re-requested. +4) Invalid block due to future timestamp is later accepted when that timestamp +becomes valid. """ import copy +import time from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script from test_framework.messages import COIN @@ -18,6 +21,9 @@ from test_framework.p2p import P2PDataStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60 + + class InvalidBlockRequestTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -133,5 +139,18 @@ class InvalidBlockRequestTest(BitcoinTestFramework): self.log.info("Test inflation by duplicating input") peer.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate') + self.log.info("Test accepting identical block after rejecting it due to a future timestamp.") + t = int(time.time()) + node.setmocktime(t) + # Set block time +1 second past max future validity + block = create_block(tip, create_coinbase(height), t + MAX_FUTURE_BLOCK_TIME + 1) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + # Need force_send because the block will get rejected without a getdata otherwise + peer.send_blocks_and_test([block], node, force_send=True, success=False, reject_reason='time-too-new') + node.setmocktime(t + 1) + peer.send_blocks_and_test([block], node, success=True) + + if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py index 62652d949d..594a28d662 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -9,9 +9,8 @@ Test that permissions are correctly calculated and applied from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE from test_framework.messages import ( - CTransaction, CTxInWitness, - FromHex, + tx_from_hex, ) from test_framework.p2p import P2PDataStore from test_framework.script import ( @@ -105,8 +104,7 @@ class P2PPermissionsTests(BitcoinTestFramework): p2p_rebroadcast_wallet = self.nodes[1].add_p2p_connection(P2PDataStore()) self.log.debug("Send a tx from the wallet initially") - tx = FromHex( - CTransaction(), + tx = tx_from_hex( self.nodes[0].createrawtransaction( inputs=[{ 'txid': block_op_true['tx'][0], diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 9d32c1cb86..ead9d852fe 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -40,8 +40,7 @@ from test_framework.messages import ( ser_uint256, ser_vector, sha256, - uint256_from_str, - FromHex, + tx_from_hex, ) from test_framework.p2p import ( P2PInterface, @@ -60,12 +59,8 @@ from test_framework.script import ( OP_CHECKMULTISIG, OP_CHECKSIG, OP_DROP, - OP_DUP, OP_ELSE, OP_ENDIF, - OP_EQUAL, - OP_EQUALVERIFY, - OP_HASH160, OP_IF, OP_RETURN, OP_TRUE, @@ -77,6 +72,12 @@ from test_framework.script import ( LegacySignatureHash, hash160, ) +from test_framework.script_util import ( + key_to_p2wpkh_script, + keyhash_to_p2pkh_script, + script_to_p2sh_script, + script_to_p2wsh_script, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -100,10 +101,6 @@ class UTXO(): self.n = n self.nValue = value -def get_p2pkh_script(pubkeyhash): - """Get the script associated with a P2PKH.""" - return CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)]) - def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key): """Add signature for a P2PK witness program.""" tx_hash = SegwitV0SignatureHash(script, tx_to, in_idx, hashtype, value) @@ -492,11 +489,8 @@ class SegWitTest(BitcoinTestFramework): # Create two outputs, a p2wsh and p2sh-p2wsh witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) - - p2sh_pubkey = hash160(script_pubkey) - p2sh_script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + script_pubkey = script_to_p2wsh_script(witness_program) + p2sh_script_pubkey = script_to_p2sh_script(script_pubkey) value = self.utxo[0].nValue // 3 @@ -631,11 +625,8 @@ class SegWitTest(BitcoinTestFramework): V0 segwit inputs may only be mined after activation, but not before.""" witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) - - p2sh_pubkey = hash160(witness_program) - p2sh_script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + script_pubkey = script_to_p2wsh_script(witness_program) + p2sh_script_pubkey = script_to_p2sh_script(witness_program) # First prepare a p2sh output (so that spending it will pass standardness) p2sh_tx = CTransaction() @@ -662,6 +653,7 @@ class SegWitTest(BitcoinTestFramework): test_transaction_acceptance(self.nodes[1], self.std_node, tx, with_witness=True, accepted=True) # Now create something that looks like a P2PKH output. This won't be spendable. + witness_hash = sha256(witness_program) script_pubkey = CScript([OP_0, hash160(witness_hash)]) tx2 = CTransaction() # tx was accepted, so we spend the second output. @@ -740,10 +732,8 @@ class SegWitTest(BitcoinTestFramework): # Prepare the p2sh-wrapped witness output witness_program = CScript([OP_DROP, OP_TRUE]) - witness_hash = sha256(witness_program) - p2wsh_pubkey = CScript([OP_0, witness_hash]) - p2sh_witness_hash = hash160(p2wsh_pubkey) - script_pubkey = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) + p2wsh_pubkey = script_to_p2wsh_script(witness_program) + script_pubkey = script_to_p2sh_script(p2wsh_pubkey) script_sig = CScript([p2wsh_pubkey]) # a push of the redeem script # Fund the P2SH output @@ -837,8 +827,7 @@ class SegWitTest(BitcoinTestFramework): # Let's construct a witness program witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) tx.vout.append(CTxOut(self.utxo[0].nValue - 1000, script_pubkey)) tx.rehash() @@ -951,8 +940,7 @@ class SegWitTest(BitcoinTestFramework): NUM_OUTPUTS = 50 witness_program = CScript([OP_2DROP] * NUM_DROPS + [OP_TRUE]) - witness_hash = uint256_from_str(sha256(witness_program)) - script_pubkey = CScript([OP_0, ser_uint256(witness_hash)]) + script_pubkey = script_to_p2wsh_script(witness_program) prevout = COutPoint(self.utxo[0].sha256, self.utxo[0].n) value = self.utxo[0].nValue @@ -1054,8 +1042,7 @@ class SegWitTest(BitcoinTestFramework): block = self.build_next_block() witness_program = CScript([OP_DROP, OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) # First try extra witness data on a tx that doesn't require a witness tx = CTransaction() @@ -1127,8 +1114,7 @@ class SegWitTest(BitcoinTestFramework): block = self.build_next_block() witness_program = CScript([OP_DROP, OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) @@ -1166,8 +1152,7 @@ class SegWitTest(BitcoinTestFramework): # This program is 19 max pushes (9937 bytes), then 64 more opcode-bytes. 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]) + long_script_pubkey = script_to_p2wsh_script(long_witness_program) block = self.build_next_block() @@ -1190,8 +1175,7 @@ class SegWitTest(BitcoinTestFramework): # Try again with one less byte in the witness program 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]) + script_pubkey = script_to_p2wsh_script(witness_program) tx.vout[0] = CTxOut(tx.vout[0].nValue, script_pubkey) tx.rehash() @@ -1210,8 +1194,7 @@ class SegWitTest(BitcoinTestFramework): """Test that vin length must match vtxinwit length.""" witness_program = CScript([OP_DROP, OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) # Create a transaction that splits our utxo into many outputs tx = CTransaction() @@ -1318,8 +1301,7 @@ class SegWitTest(BitcoinTestFramework): # Now try to add extra witness data to a valid witness tx. witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx_hash, 0), b"")) tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_pubkey)) @@ -1331,9 +1313,8 @@ class SegWitTest(BitcoinTestFramework): # Add too-large for IsStandard witness and check that it does not enter reject filter p2sh_program = CScript([OP_TRUE]) - p2sh_pubkey = hash160(p2sh_program) witness_program2 = CScript([b'a' * 400000]) - tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]))) + tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, script_to_p2sh_script(p2sh_program))) tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program2] tx3.rehash() @@ -1482,8 +1463,7 @@ class SegWitTest(BitcoinTestFramework): block = self.build_next_block() # Change the output of the block to be a witness output. witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) block.vtx[0].vout[0].scriptPubKey = script_pubkey # This next line will rehash the coinbase and update the merkle # root, and solve. @@ -1530,7 +1510,7 @@ class SegWitTest(BitcoinTestFramework): # Test 1: P2WPKH # First create a P2WPKH output that uses an uncompressed pubkey pubkeyhash = hash160(pubkey) - script_pkh = CScript([OP_0, pubkeyhash]) + script_pkh = key_to_p2wpkh_script(pubkey) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(utxo.sha256, utxo.n), b"")) tx.vout.append(CTxOut(utxo.nValue - 1000, script_pkh)) @@ -1544,13 +1524,12 @@ class SegWitTest(BitcoinTestFramework): # Now try to spend it. Send it to a P2WSH output, which we'll # use in the next test. witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) - witness_hash = sha256(witness_program) - script_wsh = CScript([OP_0, witness_hash]) + script_wsh = script_to_p2wsh_script(witness_program) tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_wsh)) - script = get_p2pkh_script(pubkeyhash) + script = keyhash_to_p2pkh_script(pubkeyhash) sig_hash = SegwitV0SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx2.wit.vtxinwit.append(CTxInWitness()) @@ -1567,8 +1546,7 @@ class SegWitTest(BitcoinTestFramework): # Test 2: P2WSH # Try to spend the P2WSH output created in last test. # Send it to a P2SH(P2WSH) output, which we'll use in the next test. - p2sh_witness_hash = hash160(script_wsh) - script_p2sh = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) + script_p2sh = script_to_p2sh_script(script_wsh) script_sig = CScript([script_wsh]) tx3 = CTransaction() @@ -1587,7 +1565,7 @@ class SegWitTest(BitcoinTestFramework): # Test 3: P2SH(P2WSH) # Try to spend the P2SH output created in the last test. # Send it to a P2PKH output, which we'll use in the next test. - script_pubkey = get_p2pkh_script(pubkeyhash) + script_pubkey = keyhash_to_p2pkh_script(pubkeyhash) tx4 = CTransaction() tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), script_sig)) tx4.vout.append(CTxOut(tx3.vout[0].nValue - 1000, script_pubkey)) @@ -1624,8 +1602,7 @@ class SegWitTest(BitcoinTestFramework): pubkey = key.get_pubkey().get_bytes() witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) # First create a witness output for use in the tests. tx = CTransaction() @@ -1744,7 +1721,7 @@ class SegWitTest(BitcoinTestFramework): # Now test witness version 0 P2PKH transactions pubkeyhash = hash160(pubkey) - script_pkh = CScript([OP_0, pubkeyhash]) + script_pkh = key_to_p2wpkh_script(pubkey) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(temp_utxos[0].sha256, temp_utxos[0].n), b"")) tx.vout.append(CTxOut(temp_utxos[0].nValue, script_pkh)) @@ -1754,7 +1731,7 @@ class SegWitTest(BitcoinTestFramework): tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE]))) - script = get_p2pkh_script(pubkeyhash) + script = keyhash_to_p2pkh_script(pubkeyhash) sig_hash = SegwitV0SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL @@ -1806,8 +1783,7 @@ class SegWitTest(BitcoinTestFramework): # rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped # in P2SH). p2sh_program = CScript([OP_TRUE]) - p2sh_pubkey = hash160(p2sh_program) - script_pubkey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + script_pubkey = script_to_p2sh_script(p2sh_program) # Now check that unnecessary witnesses can't be used to blind a node # to a transaction, eg by violating standardness checks. @@ -1872,11 +1848,10 @@ class SegWitTest(BitcoinTestFramework): # For each script, generate a pair of P2WSH and P2SH-P2WSH output. outputvalue = (self.utxo[0].nValue - 1000) // (len(scripts) * 2) for i in scripts: - p2wsh = CScript([OP_0, sha256(i)]) - p2sh = hash160(p2wsh) + p2wsh = script_to_p2wsh_script(i) p2wsh_scripts.append(p2wsh) tx.vout.append(CTxOut(outputvalue, p2wsh)) - tx.vout.append(CTxOut(outputvalue, CScript([OP_HASH160, p2sh, OP_EQUAL]))) + tx.vout.append(CTxOut(outputvalue, script_to_p2sh_script(p2wsh))) tx.rehash() txid = tx.sha256 test_transaction_acceptance(self.nodes[0], self.test_node, tx, with_witness=False, accepted=True) @@ -1890,13 +1865,13 @@ class SegWitTest(BitcoinTestFramework): for i in range(len(scripts)): p2wsh_tx = CTransaction() p2wsh_tx.vin.append(CTxIn(COutPoint(txid, i * 2))) - p2wsh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) + p2wsh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(b"")]))) p2wsh_tx.wit.vtxinwit.append(CTxInWitness()) p2wsh_tx.rehash() p2wsh_txs.append(p2wsh_tx) p2sh_tx = CTransaction() p2sh_tx.vin.append(CTxIn(COutPoint(txid, i * 2 + 1), CScript([p2wsh_scripts[i]]))) - p2sh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) + p2sh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(b"")]))) p2sh_tx.wit.vtxinwit.append(CTxInWitness()) p2sh_tx.rehash() p2sh_txs.append(p2sh_tx) @@ -1991,8 +1966,7 @@ class SegWitTest(BitcoinTestFramework): # Keep this under MAX_OPS_PER_SCRIPT (201) witness_program = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKMULTISIG] * 5 + [OP_CHECKSIG] * 193 + [OP_ENDIF]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) sigops_per_script = 20 * 5 + 193 * 1 # We'll produce 2 extra outputs, one with a program that would take us @@ -2008,14 +1982,12 @@ class SegWitTest(BitcoinTestFramework): # N(=MAX_SIGOP_COST//sigops_per_script) outputs of our transaction, # would push us just over the block sigop limit. witness_program_toomany = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG] * (extra_sigops_available + 1) + [OP_ENDIF]) - witness_hash_toomany = sha256(witness_program_toomany) - script_pubkey_toomany = CScript([OP_0, witness_hash_toomany]) + script_pubkey_toomany = script_to_p2wsh_script(witness_program_toomany) # If we spend this script instead, we would exactly reach our sigop # limit (for witness sigops). witness_program_justright = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG] * (extra_sigops_available) + [OP_ENDIF]) - witness_hash_justright = sha256(witness_program_justright) - script_pubkey_justright = CScript([OP_0, witness_hash_justright]) + script_pubkey_justright = script_to_p2wsh_script(witness_program_justright) # First split our available utxo into a bunch of outputs split_value = self.utxo[0].nValue // outputs @@ -2122,14 +2094,14 @@ class SegWitTest(BitcoinTestFramework): unspent = next(u for u in self.nodes[0].listunspent() if u['spendable'] and u['address'].startswith('bcrt')) raw = self.nodes[0].createrawtransaction([{"txid": unspent['txid'], "vout": unspent['vout']}], {self.nodes[0].getnewaddress(): 1}) - tx = FromHex(CTransaction(), raw) + tx = tx_from_hex(raw) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True) with self.nodes[0].assert_debug_log(['Superfluous witness record']): self.test_node.send_and_ping(msg_bogus_tx(tx)) raw = self.nodes[0].signrawtransactionwithwallet(raw) assert raw['complete'] raw = raw['hex'] - tx = FromHex(CTransaction(), raw) + tx = tx_from_hex(raw) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True) with self.nodes[0].assert_debug_log(['Unknown transaction optional data']): self.test_node.send_and_ping(msg_bogus_tx(tx)) @@ -2148,8 +2120,7 @@ class SegWitTest(BitcoinTestFramework): # Create a Segwit output from the latest UTXO # and announce it to the network witness_program = CScript([OP_TRUE]) - witness_hash = sha256(witness_program) - script_pubkey = CScript([OP_0, witness_hash]) + script_pubkey = script_to_p2wsh_script(witness_program) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index 4bf96cb0e6..3e962b4450 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -8,13 +8,12 @@ Test transaction download behavior from test_framework.messages import ( CInv, - CTransaction, - FromHex, MSG_TX, MSG_TYPE_MASK, MSG_WTX, msg_inv, msg_notfound, + tx_from_hex, ) from test_framework.p2p import ( P2PInterface, @@ -100,7 +99,7 @@ class TxDownloadTest(BitcoinTestFramework): hexstring=tx, privkeys=[self.nodes[0].get_deterministic_priv_key().key], )['hex'] - ctx = FromHex(CTransaction(), tx) + ctx = tx_from_hex(tx) txid = int(ctx.rehash(), 16) self.log.info( diff --git a/test/functional/rpc_addresses_deprecation.py b/test/functional/rpc_addresses_deprecation.py index bc0559f3b5..ac430f5b39 100755 --- a/test/functional/rpc_addresses_deprecation.py +++ b/test/functional/rpc_addresses_deprecation.py @@ -4,9 +4,9 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test deprecation of reqSigs and addresses RPC fields.""" -from io import BytesIO - -from test_framework.messages import CTransaction +from test_framework.messages import ( + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -35,8 +35,7 @@ class AddressesDeprecationTest(BitcoinTestFramework): signed = node.signrawtransactionwithwallet(raw)['hex'] # This transaction is derived from test/util/data/txcreatemultisig1.json - tx = CTransaction() - tx.deserialize(BytesIO(hex_str_to_bytes(signed))) + tx = tx_from_hex(signed) tx.vout[0].scriptPubKey = hex_str_to_bytes("522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae") tx_signed = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] txid = node.sendrawtransaction(hexstring=tx_signed, maxfeerate=0) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 00324347ed..90715cae26 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -31,7 +31,7 @@ from test_framework.blocktools import ( ) from test_framework.messages import ( CBlockHeader, - FromHex, + from_hex, msg_block, ) from test_framework.p2p import P2PInterface @@ -314,7 +314,7 @@ class BlockchainTest(BitcoinTestFramework): header_hex = node.getblockheader(blockhash=besthash, verbose=False) assert_is_hex_string(header_hex) - header = FromHex(CBlockHeader(), header_hex) + header = from_hex(CBlockHeader(), header_hex) header.calc_sha256() assert_equal(header.hash, besthash) diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index af515f3a27..816ec67492 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -97,6 +97,9 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): sorted_key_desc = descsum_create('sh(multi(2,{}))'.format(sorted_key_str)) assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address']) + # Check that bech32m is currently not allowed + assert_raises_rpc_error(-5, "createmultisig cannot create bech32m multisig addresses", self.nodes[0].createmultisig, 2, self.pub, "bech32m") + def check_addmultisigaddress_errors(self): if self.options.descriptors: return @@ -108,6 +111,10 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): self.nodes[0].importaddress(a) assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) + # Bech32m address type is disallowed for legacy wallets + pubs = [self.nodes[1].getaddressinfo(addr)["pubkey"] for addr in addresses] + assert_raises_rpc_error(-5, "Bech32m multisig addresses cannot be created with legacy wallets", self.nodes[0].addmultisigaddress, 2, pubs, "", "bech32m") + def checkbalances(self): node0, node1, node2 = self.nodes node0.generate(COINBASE_MATURITY) diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 01b8cb1854..f6643c7167 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -4,11 +4,16 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test decoding scripts via decodescript RPC command.""" -from test_framework.messages import CTransaction, sha256 +from test_framework.messages import ( + sha256, + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, hex_str_to_bytes +from test_framework.util import ( + assert_equal, + hex_str_to_bytes, +) -from io import BytesIO class DecodeScriptTest(BitcoinTestFramework): def set_test_params(self): @@ -179,8 +184,7 @@ class DecodeScriptTest(BitcoinTestFramework): assert_equal('0 3045022100ae3b4e589dfc9d48cb82d41008dc5fa6a86f94d5c54f9935531924602730ab8002202f88cf464414c4ed9fa11b773c5ee944f66e9b05cc1e51d97abc22ce098937ea[ALL] 3045022100b44883be035600e9328a01b66c7d8439b74db64187e76b99a68f7893b701d5380220225bf286493e4c4adcf928c40f785422572eb232f84a0b83b0dea823c3a19c75[ALL] 5221020743d44be989540d27b1b4bbbcfd17721c337cb6bc9af20eb8a32520b393532f2102c0120a1dda9e51a938d39ddd9fe0ebc45ea97e1d27a7cbd671d5431416d3dd87210213820eb3d5f509d7438c9eeecb4157b2f595105e7cd564b3cdbb9ead3da41eed53ae', rpc_result['vin'][0]['scriptSig']['asm']) assert_equal('OP_DUP OP_HASH160 dc863734a218bfe83ef770ee9d41a27f824a6e56 OP_EQUALVERIFY OP_CHECKSIG', rpc_result['vout'][0]['scriptPubKey']['asm']) assert_equal('OP_HASH160 2a5edea39971049a540474c6a99edf0aa4074c58 OP_EQUAL', rpc_result['vout'][1]['scriptPubKey']['asm']) - txSave = CTransaction() - txSave.deserialize(BytesIO(hex_str_to_bytes(tx))) + txSave = tx_from_hex(tx) # make sure that a specifically crafted op_return value will not pass all the IsDERSignature checks and then get decoded as a sighash type tx = '01000000015ded05872fdbda629c7d3d02b194763ce3b9b1535ea884e3c8e765d42e316724020000006b48304502204c10d4064885c42638cbff3585915b322de33762598321145ba033fc796971e2022100bb153ad3baa8b757e30a2175bd32852d2e1cb9080f84d7e32fcdfd667934ef1b012103163c0ff73511ea1743fb5b98384a2ff09dd06949488028fd819f4d83f56264efffffffff0200000000000000000b6a0930060201000201000180380100000000001976a9141cabd296e753837c086da7a45a6c2fe0d49d7b7b88ac00000000' diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 4b07a32c54..fa98c44152 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -551,7 +551,7 @@ class RawTransactionsTest(BitcoinTestFramework): # creating the key must be impossible because the wallet is locked outputs = {self.nodes[0].getnewaddress():1.1} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", self.nodes[1].fundrawtransaction, rawtx) + assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx) # Refill the keypool. self.nodes[1].walletpassphrase("test", 100) diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index d0848d9bd6..4b2ed20958 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -5,7 +5,6 @@ """RPCs that handle raw transaction packages.""" from decimal import Decimal -from io import BytesIO import random from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE @@ -13,8 +12,8 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, COIN, - CTransaction, CTxInWitness, + tx_from_hex, ) from test_framework.script import ( CScript, @@ -22,7 +21,6 @@ from test_framework.script import ( ) from test_framework.util import ( assert_equal, - hex_str_to_bytes, ) class RPCPackagesTest(BitcoinTestFramework): @@ -97,9 +95,8 @@ class RPCPackagesTest(BitcoinTestFramework): "amount": parent_value, }] if parent_locking_script else None signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys, prevtxs=prevtxs) - tx = CTransaction() assert signedtx["complete"] - tx.deserialize(BytesIO(hex_str_to_bytes(signedtx["hex"]))) + tx = tx_from_hex(signedtx["hex"]) return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex()) def test_independent(self): @@ -110,8 +107,7 @@ class RPCPackagesTest(BitcoinTestFramework): self.log.info("Test an otherwise valid package with an extra garbage tx appended") garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {self.address: 1}) - tx = CTransaction() - tx.deserialize(BytesIO(hex_str_to_bytes(garbage_tx))) + tx = tx_from_hex(garbage_tx) # Only the txid and wtxids are returned because validation is incomplete for the independent txns. # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package, # it terminates immediately to avoid unnecessary, expensive signature verification. @@ -123,8 +119,7 @@ class RPCPackagesTest(BitcoinTestFramework): coin = self.coins.pop() tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], {self.address : coin["amount"] - Decimal("0.0001")}) - tx_bad_sig = CTransaction() - tx_bad_sig.deserialize(BytesIO(hex_str_to_bytes(tx_bad_sig_hex))) + tx_bad_sig = tx_from_hex(tx_bad_sig_hex) testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex]) # By the time the signature for the last transaction is checked, all the other transactions # have been fully validated, which is why the node returns full validation results for all @@ -141,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework): {self.address : coin["amount"] - Decimal("0.999")}) tx_high_fee_signed = node.signrawtransactionwithkey(hexstring=tx_high_fee_raw, privkeys=self.privkeys) assert tx_high_fee_signed["complete"] - tx_high_fee = CTransaction() - tx_high_fee.deserialize(BytesIO(hex_str_to_bytes(tx_high_fee_signed["hex"]))) + tx_high_fee = tx_from_hex(tx_high_fee_signed["hex"]) testres_high_fee = node.testmempoolaccept([tx_high_fee_signed["hex"]]) assert_equal(testres_high_fee, [ {"txid": tx_high_fee.rehash(), "wtxid": tx_high_fee.getwtxid(), "allowed": False, "reject-reason": "max-fee-exceeded"} @@ -198,9 +192,8 @@ class RPCPackagesTest(BitcoinTestFramework): rawtx = node.createrawtransaction(inputs, outputs) parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) - parent_tx = CTransaction() assert parent_signed["complete"] - parent_tx.deserialize(BytesIO(hex_str_to_bytes(parent_signed["hex"]))) + parent_tx = tx_from_hex(parent_signed["hex"]) parent_txid = parent_tx.rehash() assert node.testmempoolaccept([parent_signed["hex"]])[0]["allowed"] @@ -213,8 +206,7 @@ class RPCPackagesTest(BitcoinTestFramework): # Child B rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : child_value}) - tx_child_b = CTransaction() - tx_child_b.deserialize(BytesIO(hex_str_to_bytes(rawtx_b))) + tx_child_b = tx_from_hex(rawtx_b) tx_child_b.wit.vtxinwit = [CTxInWitness()] tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] tx_child_b_hex = tx_child_b.serialize().hex() @@ -293,10 +285,8 @@ class RPCPackagesTest(BitcoinTestFramework): rawtx2 = node.createrawtransaction(inputs, output2) signedtx1 = node.signrawtransactionwithkey(hexstring=rawtx1, privkeys=self.privkeys) signedtx2 = node.signrawtransactionwithkey(hexstring=rawtx2, privkeys=self.privkeys) - tx1 = CTransaction() - tx1.deserialize(BytesIO(hex_str_to_bytes(signedtx1["hex"]))) - tx2 = CTransaction() - tx2.deserialize(BytesIO(hex_str_to_bytes(signedtx2["hex"]))) + tx1 = tx_from_hex(signedtx1["hex"]) + tx2 = tx_from_hex(signedtx2["hex"]) assert signedtx1["complete"] assert signedtx2["complete"] @@ -327,19 +317,17 @@ class RPCPackagesTest(BitcoinTestFramework): raw_replaceable_tx = node.createrawtransaction(inputs, output) signed_replaceable_tx = node.signrawtransactionwithkey(hexstring=raw_replaceable_tx, privkeys=self.privkeys) testres_replaceable = node.testmempoolaccept([signed_replaceable_tx["hex"]]) - replaceable_tx = CTransaction() - replaceable_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) + replaceable_tx = tx_from_hex(signed_replaceable_tx["hex"]) assert_equal(testres_replaceable, [ {"txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), "allowed": True, "vsize": replaceable_tx.get_vsize(), "fees": { "base": fee }} ]) # Replacement transaction is identical except has double the fee - replacement_tx = CTransaction() - replacement_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) + replacement_tx = tx_from_hex(signed_replaceable_tx["hex"]) replacement_tx.vout[0].nValue -= int(fee * COIN) # Doubled fee signed_replacement_tx = node.signrawtransactionwithkey(replacement_tx.serialize().hex(), self.privkeys) - replacement_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replacement_tx["hex"]))) + replacement_tx = tx_from_hex(signed_replacement_tx["hex"]) self.log.info("Test that transactions within a package cannot replace each other") testres_rbf_conflicting = node.testmempoolaccept([signed_replaceable_tx["hex"], signed_replacement_tx["hex"]]) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 53ddf24e47..3ff74dc5a4 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -14,16 +14,17 @@ Test the following RPCs: from collections import OrderedDict from decimal import Decimal -from io import BytesIO from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import CTransaction, ToHex +from test_framework.messages import ( + CTransaction, + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, find_vout_for_address, - hex_str_to_bytes, ) @@ -55,6 +56,10 @@ class RawTransactionsTest(BitcoinTestFramework): ["-txindex"], ["-txindex"], ] + # whitelist all peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") + self.supports_cli = False def skip_test_if_missing_module(self): @@ -127,23 +132,22 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') self.log.info('Check that createrawtransaction accepts an array and object as outputs') - tx = CTransaction() # One output - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs={address: 99})))) + tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs={address: 99})) assert_equal(len(tx.vout), 1) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}]), ) # Two outputs - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]))))) + tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]))) assert_equal(len(tx.vout), 2) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]), ) # Multiple mixed outputs - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')]))))) + tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')]))) assert_equal(len(tx.vout), 3) assert_equal( tx.serialize().hex(), @@ -450,14 +454,14 @@ class RawTransactionsTest(BitcoinTestFramework): # As transaction version is unsigned, this should convert to its unsigned equivalent. tx = CTransaction() tx.nVersion = -0x80000000 - rawtx = ToHex(tx) + rawtx = tx.serialize().hex() decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], 0x80000000) # Test the maximum transaction version number that fits in a signed 32-bit integer. tx = CTransaction() tx.nVersion = 0x7fffffff - rawtx = ToHex(tx) + rawtx = tx.serialize().hex() decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], 0x7fffffff) diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py index fd5f8aa098..36873f964b 100755 --- a/test/functional/rpc_setban.py +++ b/test/functional/rpc_setban.py @@ -22,7 +22,7 @@ class SetBanTests(BitcoinTestFramework): # Node 0 connects to Node 1, check that the noban permission is not granted self.connect_nodes(0, 1) peerinfo = self.nodes[1].getpeerinfo()[0] - assert(not 'noban' in peerinfo['permissions']) + assert not "noban" in peerinfo["permissions"] # Node 0 get banned by Node 1 self.nodes[1].setban("127.0.0.1", "add") @@ -36,27 +36,40 @@ class SetBanTests(BitcoinTestFramework): self.restart_node(1, ['-whitelist=127.0.0.1']) self.connect_nodes(0, 1) peerinfo = self.nodes[1].getpeerinfo()[0] - assert('noban' in peerinfo['permissions']) + assert "noban" in peerinfo["permissions"] # If we remove the ban, Node 0 should be able to reconnect even without noban permission self.nodes[1].setban("127.0.0.1", "remove") self.restart_node(1, []) self.connect_nodes(0, 1) peerinfo = self.nodes[1].getpeerinfo()[0] - assert(not 'noban' in peerinfo['permissions']) + assert not "noban" in peerinfo["permissions"] self.log.info("Test that a non-IP address can be banned/unbanned") node = self.nodes[1] tor_addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion" ip_addr = "1.2.3.4" - assert(not self.is_banned(node, tor_addr)) - assert(not self.is_banned(node, ip_addr)) + assert not self.is_banned(node, tor_addr) + assert not self.is_banned(node, ip_addr) + node.setban(tor_addr, "add") - assert(self.is_banned(node, tor_addr)) - assert(not self.is_banned(node, ip_addr)) + assert self.is_banned(node, tor_addr) + assert not self.is_banned(node, ip_addr) + + self.log.info("Test the ban list is preserved through restart") + + self.restart_node(1) + assert self.is_banned(node, tor_addr) + assert not self.is_banned(node, ip_addr) + node.setban(tor_addr, "remove") - assert(not self.is_banned(self.nodes[1], tor_addr)) - assert(not self.is_banned(node, ip_addr)) + assert not self.is_banned(self.nodes[1], tor_addr) + assert not self.is_banned(node, ip_addr) + + self.restart_node(1) + assert not self.is_banned(node, tor_addr) + assert not self.is_banned(node, ip_addr) + if __name__ == '__main__': SetBanTests().main() diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index 16b0019866..f3627d1e37 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -5,17 +5,41 @@ """Test transaction signing using the signrawtransaction* RPCs.""" from test_framework.blocktools import COINBASE_MATURITY -from test_framework.address import check_script, script_to_p2sh, script_to_p2wsh +from test_framework.address import ( + script_to_p2sh, + script_to_p2wsh, +) from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, find_vout_for_address, hex_str_to_bytes -from test_framework.messages import sha256, CTransaction, CTxInWitness -from test_framework.script import CScript, OP_0, OP_CHECKSIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE -from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_p2wsh_script, script_to_p2wsh_script +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + find_vout_for_address, + hex_str_to_bytes, +) +from test_framework.messages import ( + CTxInWitness, + tx_from_hex, +) +from test_framework.script import ( + CScript, + OP_CHECKLOCKTIMEVERIFY, + OP_CHECKSIG, + OP_CHECKSEQUENCEVERIFY, + OP_DROP, + OP_TRUE, +) +from test_framework.script_util import ( + key_to_p2pkh_script, + script_to_p2sh_p2wsh_script, + script_to_p2wsh_script, +) from test_framework.wallet_util import bytes_to_wif -from decimal import Decimal, getcontext -from io import BytesIO +from decimal import ( + Decimal, + getcontext, +) class SignRawTransactionsTest(BitcoinTestFramework): def set_test_params(self): @@ -206,7 +230,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(), 'P2PK': CScript([hex_str_to_bytes(embedded_pubkey), OP_CHECKSIG]).hex() }.get(tx_type, "Invalid tx_type") - redeem_script = CScript([OP_0, sha256(check_script(witness_script))]).hex() + redeem_script = script_to_p2wsh_script(witness_script).hex() addr = script_to_p2sh(redeem_script) script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey'] # Fund that address @@ -265,8 +289,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): ) # Set the witness script - ctx = CTransaction() - ctx.deserialize(BytesIO(hex_str_to_bytes(tx))) + ctx = tx_from_hex(tx) ctx.wit.vtxinwit.append(CTxInWitness()) ctx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), script] tx = ctx.serialize_with_witness().hex() @@ -301,8 +324,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): ) # Set the witness script - ctx = CTransaction() - ctx.deserialize(BytesIO(hex_str_to_bytes(tx))) + ctx = tx_from_hex(tx) ctx.wit.vtxinwit.append(CTxInWitness()) ctx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), script] tx = ctx.serialize_with_witness().hex() diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py index bf96b6353c..67af6b8f8e 100755 --- a/test/functional/rpc_txoutproof.py +++ b/test/functional/rpc_txoutproof.py @@ -5,9 +5,15 @@ """Test gettxoutproof and verifytxoutproof RPCs.""" from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import CMerkleBlock, FromHex, ToHex +from test_framework.messages import ( + CMerkleBlock, + from_hex, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) from test_framework.wallet import MiniWallet @@ -88,10 +94,10 @@ class MerkleBlockTest(BitcoinTestFramework): assert txid1 in self.nodes[0].verifytxoutproof(proof) assert txid2 in self.nodes[1].verifytxoutproof(proof) - tweaked_proof = FromHex(CMerkleBlock(), proof) + tweaked_proof = from_hex(CMerkleBlock(), proof) # Make sure that our serialization/deserialization is working - assert txid1 in self.nodes[0].verifytxoutproof(ToHex(tweaked_proof)) + assert txid1 in self.nodes[0].verifytxoutproof(tweaked_proof.serialize().hex()) # Check to see if we can go up the merkle tree and pass this off as a # single-transaction block @@ -100,7 +106,7 @@ class MerkleBlockTest(BitcoinTestFramework): tweaked_proof.txn.vBits = [True] + [False]*7 for n in self.nodes: - assert not n.verifytxoutproof(ToHex(tweaked_proof)) + assert not n.verifytxoutproof(tweaked_proof.serialize().hex()) # TODO: try more variants, eg transactions at different depths, and # verify that the proofs are invalid diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index f35ea6c122..833a215993 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -23,25 +23,25 @@ from .messages import ( CTxIn, CTxInWitness, CTxOut, - FromHex, - ToHex, hash256, hex_str_to_bytes, ser_uint256, - sha256, + tx_from_hex, uint256_from_str, ) from .script import ( CScript, CScriptNum, CScriptOp, - OP_0, OP_1, OP_CHECKMULTISIG, OP_CHECKSIG, OP_RETURN, OP_TRUE, - hash160, +) +from .script_util import ( + key_to_p2wpkh_script, + script_to_p2wsh_script, ) from .util import assert_equal @@ -79,7 +79,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl if txlist: for tx in txlist: if not hasattr(tx, 'calc_sha256'): - tx = FromHex(CTransaction(), tx) + tx = tx_from_hex(tx) block.vtx.append(tx) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() @@ -166,7 +166,7 @@ def create_transaction(node, txid, to_address, *, amount): sign for the output that is being spent. """ raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) - tx = FromHex(CTransaction(), raw_tx) + tx = tx_from_hex(raw_tx) return tx def create_raw_transaction(node, txid, to_address, *, amount): @@ -181,11 +181,6 @@ def create_raw_transaction(node, txid, to_address, *, amount): signed_psbt = wrpc.walletprocesspsbt(psbt) psbt = signed_psbt['psbt'] final_psbt = node.finalizepsbt(psbt) - if not final_psbt["complete"]: - node.log.info(f'final_psbt={final_psbt}') - for w in node.listwallets(): - wrpc = node.get_wallet_rpc(w) - node.log.info(f'listunspent={wrpc.listunspent()}') assert_equal(final_psbt["complete"], True) return final_psbt['hex'] @@ -212,13 +207,11 @@ def witness_script(use_p2wsh, pubkey): scriptPubKey.""" if not use_p2wsh: # P2WPKH instead - pubkeyhash = hash160(hex_str_to_bytes(pubkey)) - pkscript = CScript([OP_0, pubkeyhash]) + pkscript = key_to_p2wpkh_script(pubkey) else: # 1-of-1 multisig witness_program = CScript([OP_1, hex_str_to_bytes(pubkey), OP_1, OP_CHECKMULTISIG]) - scripthash = sha256(witness_program) - pkscript = CScript([OP_0, scripthash]) + pkscript = script_to_p2wsh_script(witness_program) return pkscript.hex() def create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount): @@ -248,9 +241,9 @@ def send_to_witness(use_p2wsh, node, utxo, pubkey, encode_p2sh, amount, sign=Tru return node.sendrawtransaction(signed["hex"]) else: if (insert_redeem_script): - tx = FromHex(CTransaction(), tx_to_witness) + tx = tx_from_hex(tx_to_witness) tx.vin[0].scriptSig += CScript([hex_str_to_bytes(insert_redeem_script)]) - tx_to_witness = ToHex(tx) + tx_to_witness = tx.serialize().hex() return node.sendrawtransaction(tx_to_witness) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index fdc254e5fc..065e8961ae 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -40,7 +40,7 @@ MAX_BLOOM_HASH_FUNCS = 50 COIN = 100000000 # 1 btc in satoshis MAX_MONEY = 21000000 * COIN -BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out +BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68) MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result @@ -191,14 +191,20 @@ def ser_string_vector(l): return r -# Deserialize from a hex string representation (eg from RPC) -def FromHex(obj, hex_string): +def from_hex(obj, hex_string): + """Deserialize from a hex string representation (e.g. from RPC) + + Note that there is no complementary helper like e.g. `to_hex` for the + inverse operation. To serialize a message object to a hex string, simply + use obj.serialize().hex()""" obj.deserialize(BytesIO(hex_str_to_bytes(hex_string))) return obj -# Convert a binary-serializable object to hex (eg for submission via RPC) -def ToHex(obj): - return obj.serialize().hex() + +def tx_from_hex(hex_string): + """Deserialize from hex string to a transaction object""" + return from_hex(CTransaction(), hex_string) + # Objects that map to bitcoind objects, which can be serialized/deserialized diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index a89a26caea..40360c54a0 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -194,6 +194,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="Run test using legacy wallets", dest='descriptors') self.add_options(parser) + # Running TestShell in a Jupyter notebook causes an additional -f argument + # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument + # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168 + parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1") self.options = parser.parse_args() self.options.previous_releases_path = previous_releases_path diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 462019566c..35dbfbba8d 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -19,7 +19,6 @@ import unittest from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException -from io import BytesIO from typing import Callable, Optional logger = logging.getLogger("TestFramework.utils") @@ -481,6 +480,28 @@ def create_confirmed_utxos(fee, node, count): return utxos +def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs): + """Build and send a transaction that spends the given inputs (specified + by lists of parent_txid:vout each), with the desired total value and fee, + equally divided up to the desired number of outputs. + + Returns a tuple with the txid and the amount sent per output. + """ + send_value = satoshi_round((value - fee)/num_outputs) + inputs = [] + for (txid, vout) in zip(parent_txids, vouts): + inputs.append({'txid' : txid, 'vout' : vout}) + outputs = {} + for _ in range(num_outputs): + outputs[node.getnewaddress()] = send_value + rawtx = node.createrawtransaction(inputs, outputs, 0, True) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx['hex']) + fulltx = node.getrawtransaction(txid, 1) + assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output + return (txid, send_value) + + # Create large OP_RETURN txouts that can be appended to a transaction # to make it large (helper for constructing large transactions). def gen_return_txouts(): @@ -506,7 +527,7 @@ def gen_return_txouts(): def create_lots_of_big_transactions(node, txouts, utxos, num, fee): addr = node.getnewaddress() txids = [] - from .messages import CTransaction + from .messages import tx_from_hex for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] @@ -514,8 +535,7 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee): change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) - tx = CTransaction() - tx.deserialize(BytesIO(hex_str_to_bytes(rawtx))) + tx = tx_from_hex(rawtx) for txout in txouts: tx.vout.append(txout) newtx = tx.serialize().hex() diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 0962a5cb54..47ec6b0be2 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -88,12 +88,20 @@ class MiniWallet: if out['scriptPubKey']['hex'] == self._scriptPubKey.hex(): self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']}) - def sign_tx(self, tx): + def sign_tx(self, tx, fixed_length=True): """Sign tx that has been created by MiniWallet in P2PK mode""" assert self._priv_key is not None (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) assert err is None - tx.vin[0].scriptSig = CScript([self._priv_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): + # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes) + # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes + der_sig = b'' + while not len(der_sig) == 71: + der_sig = self._priv_key.sign_ecdsa(sighash) + if not fixed_length: + break + tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) def generate(self, num_blocks): """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" @@ -124,23 +132,26 @@ class MiniWallet: else: return self._utxos[index] - def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, locktime=0): + def send_self_transfer(self, **kwargs): """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" - tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend) - self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex']) + tx = self.create_self_transfer(**kwargs) + self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex']) return tx - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0): + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" self._utxos = sorted(self._utxos, key=lambda k: k['value']) utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee - vsize = Decimal(96) + if self._priv_key is None: + vsize = Decimal(96) # anyone-can-spend + else: + vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) send_value = satoshi_round(utxo_to_spend['value'] - fee_rate * (vsize / 1000)) fee = utxo_to_spend['value'] - send_value assert send_value > 0 tx = CTransaction() - tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']))] + tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)] tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)] tx.nLockTime = locktime if not self._address: @@ -159,10 +170,7 @@ class MiniWallet: tx_info = from_node.testmempoolaccept([tx_hex])[0] assert_equal(mempool_valid, tx_info['allowed']) if mempool_valid: - # TODO: for P2PK, vsize is not constant due to varying scriptSig length, - # so only check this for anyone-can-spend outputs right now - if self._priv_key is None: - assert_equal(tx_info['vsize'], vsize) + assert_equal(tx_info['vsize'], vsize) assert_equal(tx_info['fees']['base'], fee) return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx} diff --git a/test/functional/test_framework/wallet_util.py b/test/functional/test_framework/wallet_util.py index b9c0fb6691..acbc040741 100755 --- a/test/functional/test_framework/wallet_util.py +++ b/test/functional/test_framework/wallet_util.py @@ -17,17 +17,15 @@ from test_framework.address import ( from test_framework.key import ECKey from test_framework.script import ( CScript, - OP_0, OP_2, OP_3, OP_CHECKMULTISIG, - OP_CHECKSIG, - OP_DUP, - OP_EQUAL, - OP_EQUALVERIFY, - OP_HASH160, - hash160, - sha256, +) +from test_framework.script_util import ( + key_to_p2pkh_script, + key_to_p2wpkh_script, + script_to_p2sh_script, + script_to_p2wsh_script, ) from test_framework.util import hex_str_to_bytes @@ -57,15 +55,14 @@ def get_key(node): Returns a named tuple of privkey, pubkey and all address and scripts.""" addr = node.getnewaddress() pubkey = node.getaddressinfo(addr)['pubkey'] - pkh = hash160(hex_str_to_bytes(pubkey)) return Key(privkey=node.dumpprivkey(addr), pubkey=pubkey, - p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(), + p2pkh_script=key_to_p2pkh_script(pubkey).hex(), p2pkh_addr=key_to_p2pkh(pubkey), - p2wpkh_script=CScript([OP_0, pkh]).hex(), + p2wpkh_script=key_to_p2wpkh_script(pubkey).hex(), p2wpkh_addr=key_to_p2wpkh(pubkey), - p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(), - p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(), + p2sh_p2wpkh_script=script_to_p2sh_script(key_to_p2wpkh_script(pubkey)).hex(), + p2sh_p2wpkh_redeem_script=key_to_p2wpkh_script(pubkey).hex(), p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey)) def get_generate_key(): @@ -76,15 +73,14 @@ def get_generate_key(): eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) pubkey = eckey.get_pubkey().get_bytes().hex() - pkh = hash160(hex_str_to_bytes(pubkey)) return Key(privkey=privkey, pubkey=pubkey, - p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(), + p2pkh_script=key_to_p2pkh_script(pubkey).hex(), p2pkh_addr=key_to_p2pkh(pubkey), - p2wpkh_script=CScript([OP_0, pkh]).hex(), + p2wpkh_script=key_to_p2wpkh_script(pubkey).hex(), p2wpkh_addr=key_to_p2wpkh(pubkey), - p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(), - p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(), + p2sh_p2wpkh_script=script_to_p2sh_script(key_to_p2wpkh_script(pubkey)).hex(), + p2sh_p2wpkh_redeem_script=key_to_p2wpkh_script(pubkey).hex(), p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey)) def get_multisig(node): @@ -98,15 +94,15 @@ def get_multisig(node): addrs.append(addr['address']) pubkeys.append(addr['pubkey']) script_code = CScript([OP_2] + [hex_str_to_bytes(pubkey) for pubkey in pubkeys] + [OP_3, OP_CHECKMULTISIG]) - witness_script = CScript([OP_0, sha256(script_code)]) + witness_script = script_to_p2wsh_script(script_code) return Multisig(privkeys=[node.dumpprivkey(addr) for addr in addrs], pubkeys=pubkeys, - p2sh_script=CScript([OP_HASH160, hash160(script_code), OP_EQUAL]).hex(), + p2sh_script=script_to_p2sh_script(script_code).hex(), p2sh_addr=script_to_p2sh(script_code), redeem_script=script_code.hex(), p2wsh_script=witness_script.hex(), p2wsh_addr=script_to_p2wsh(script_code), - p2sh_p2wsh_script=CScript([OP_HASH160, witness_script, OP_EQUAL]).hex(), + p2sh_p2wsh_script=script_to_p2sh_script(witness_script).hex(), p2sh_p2wsh_addr=script_to_p2sh_p2wsh(script_code)) def test_address(node, address, **kwargs): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c9a8cc5611..8afd8b3bc1 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -19,6 +19,7 @@ import datetime import os import time import shutil +import signal import subprocess import sys import tempfile @@ -110,6 +111,7 @@ BASE_SCRIPTS = [ 'wallet_dump.py --legacy-wallet', 'wallet_listtransactions.py --legacy-wallet', 'wallet_listtransactions.py --descriptors', + 'feature_taproot.py --previous_release', 'feature_taproot.py', 'rpc_signer.py', 'wallet_signer.py --descriptors', @@ -123,8 +125,6 @@ BASE_SCRIPTS = [ 'wallet_abandonconflict.py --legacy-wallet', 'wallet_abandonconflict.py --descriptors', 'feature_csv_activation.py', - 'rpc_rawtransaction.py --legacy-wallet', - 'rpc_rawtransaction.py --descriptors', 'wallet_address_types.py --legacy-wallet', 'wallet_address_types.py --descriptors', 'feature_bip68_sequence.py', @@ -138,6 +138,7 @@ BASE_SCRIPTS = [ 'interface_zmq.py', 'rpc_invalid_address_message.py', 'interface_bitcoin_cli.py', + 'feature_bind_extra.py', 'mempool_resurrect.py', 'wallet_txn_doublespend.py --mineblock', 'tool_wallet.py --legacy-wallet', @@ -170,9 +171,12 @@ BASE_SCRIPTS = [ 'feature_proxy.py', 'rpc_signrawtransaction.py --legacy-wallet', 'rpc_signrawtransaction.py --descriptors', + 'rpc_rawtransaction.py --legacy-wallet', + 'rpc_rawtransaction.py --descriptors', 'wallet_groups.py --legacy-wallet', 'p2p_addrv2_relay.py', 'wallet_groups.py --descriptors', + 'p2p_compactblocks_hb.py', 'p2p_disconnect_ban.py', 'rpc_decodescript.py', 'rpc_blockchain.py', @@ -277,6 +281,7 @@ BASE_SCRIPTS = [ 'feature_asmap.py', 'mempool_unbroadcast.py', 'mempool_compatibility.py', + 'mempool_accept_wtxid.py', 'rpc_deriveaddresses.py', 'rpc_deriveaddresses.py --usecli', 'p2p_ping.py', @@ -289,6 +294,7 @@ BASE_SCRIPTS = [ 'p2p_permissions.py', 'feature_blocksdir.py', 'wallet_startup.py', + 'p2p_i2p_ports.py', 'feature_config_args.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', @@ -548,9 +554,11 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= all_passed = all(map(lambda test_result: test_result.was_successful, test_results)) and coverage_passed - # This will be a no-op unless failfast is True in which case there may be dangling - # processes which need to be killed. - job_queue.kill_and_join() + # Clean up dangling processes if any. This may only happen with --failfast option. + # Killing the process group will also terminate the current process but that is + # not an issue + if len(job_queue.jobs): + os.killpg(os.getpgid(0), signal.SIGKILL) sys.exit(not all_passed) @@ -647,16 +655,6 @@ class TestHandler: print('.', end='', flush=True) dot_count += 1 - def kill_and_join(self): - """Send SIGKILL to all jobs and block until all have ended.""" - procs = [i[2] for i in self.jobs] - - for proc in procs: - proc.kill() - - for proc in procs: - proc.wait() - class TestResult(): def __init__(self, name, status, time): diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index 6d93cf412f..9b97d08424 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -373,5 +373,15 @@ class AddressTypeTest(BitcoinTestFramework): self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit') self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32') + if self.options.descriptors: + self.log.info("Descriptor wallets do not have bech32m addresses by default yet") + # TODO: Remove this when they do + assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getnewaddress, "", "bech32m") + assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getrawchangeaddress, "bech32m") + else: + self.log.info("Legacy wallets cannot make bech32m addresses") + assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getnewaddress, "", "bech32m") + assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getrawchangeaddress, "bech32m") + if __name__ == '__main__': AddressTypeTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index a052ec7477..b5afc3785e 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -420,6 +420,9 @@ class WalletTest(BitcoinTestFramework): # This will raise an exception for importing an invalid pubkey assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f") + # Bech32m addresses cannot be imported into a legacy wallet + assert_raises_rpc_error(-5, "Bech32m addresses cannot be imported into legacy wallets", self.nodes[0].importaddress, "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6") + # Import address and private key to check correct behavior of spendable unspents # 1. Send some coins to generate new UTXO address_to_import = self.nodes[2].getnewaddress() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index b21461ee7b..c04986038d 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -14,17 +14,23 @@ added in the future, they should try to follow the same convention and not make assumptions about execution order. """ from decimal import Decimal -import io -from test_framework.blocktools import COINBASE_MATURITY -from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness -from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction +from test_framework.blocktools import ( + COINBASE_MATURITY, + add_witness_commitment, + create_block, + create_coinbase, + send_to_witness, +) +from test_framework.messages import ( + BIP125_SEQUENCE_NUMBER, + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, - hex_str_to_bytes, ) WALLET_PASSPHRASE = "test" @@ -576,9 +582,7 @@ def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): def submit_block_with_tx(node, tx): - ctx = CTransaction() - ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx))) - + ctx = tx_from_hex(tx) tip = node.getbestblockhash() height = node.getblockcount() + 1 block_time = node.getblockheader(tip)["mediantime"] + 1 diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index f32acb8e15..d9d135a986 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -6,7 +6,9 @@ from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import CTransaction, FromHex, ToHex +from test_framework.messages import ( + tx_from_hex, +) from test_framework.util import ( assert_approx, assert_equal, @@ -154,10 +156,10 @@ class WalletGroupTest(BitcoinTestFramework): self.log.info("Fill a wallet with 10,000 outputs corresponding to the same scriptPubKey") for _ in range(5): raw_tx = self.nodes[0].createrawtransaction([{"txid":"0"*64, "vout":0}], [{addr2[0]: 0.05}]) - tx = FromHex(CTransaction(), raw_tx) + tx = tx_from_hex(raw_tx) tx.vin = [] tx.vout = [tx.vout[0]] * 2000 - funded_tx = self.nodes[0].fundrawtransaction(ToHex(tx)) + funded_tx = self.nodes[0].fundrawtransaction(tx.serialize().hex()) signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx['hex']) self.nodes[0].sendrawtransaction(signed_tx['hex']) self.nodes[0].generate(1) diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index a2da16e5a3..262175c789 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -79,7 +79,6 @@ class ImportDescriptorsTest(BitcoinTestFramework): # RPC importdescriptors ----------------------------------------------- # # Test import fails if no descriptor present - key = get_generate_key() self.log.info("Import should fail if a descriptor is not provided") self.test_importdesc({"timestamp": "now"}, success=False, @@ -89,10 +88,10 @@ class ImportDescriptorsTest(BitcoinTestFramework): # # Test importing of a P2PKH descriptor key = get_generate_key() self.log.info("Should import a p2pkh descriptor") - self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"), - "timestamp": "now", - "label": "Descriptor import test"}, - success=True) + import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"), + "timestamp": "now", + "label": "Descriptor import test"} + self.test_importdesc(import_request, success=True) test_address(w1, key.p2pkh_addr, solvable=True, @@ -100,11 +99,15 @@ class ImportDescriptorsTest(BitcoinTestFramework): labels=["Descriptor import test"]) assert_equal(w1.getwalletinfo()['keypoolsize'], 0) + self.log.info("Test can import same descriptor with public key twice") + self.test_importdesc(import_request, success=True) + + self.log.info("Test can update descriptor label") + self.test_importdesc({**import_request, "label": "Updated label"}, success=True) + test_address(w1, key.p2pkh_addr, solvable=True, ismine=True, labels=["Updated label"]) + self.log.info("Internal addresses cannot have labels") - self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"), - "timestamp": "now", - "internal": True, - "label": "Descriptor import test"}, + self.test_importdesc({**import_request, "internal": True}, success=False, error_code=-8, error_message="Internal addresses should not have a label") @@ -252,6 +255,39 @@ class ImportDescriptorsTest(BitcoinTestFramework): self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, success=False, error_code=-8, error_message='Range is too large') + self.log.info("Verify we can only extend descriptor's range") + range_request = {"desc": descsum_create(desc), "timestamp": "now", "range": [5, 10], 'active': True} + self.test_importdesc(range_request, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 6) + self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 11) + self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21) + # Can keep range the same + self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21) + + self.test_importdesc({**range_request, "range": [5, 10]}, wallet=wpriv, success=False, + error_code=-8, error_message='new range must include current range = [0,20]') + self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=False, + error_code=-8, error_message='new range must include current range = [0,20]') + self.test_importdesc({**range_request, "range": [5, 20]}, wallet=wpriv, success=False, + error_code=-8, error_message='new range must include current range = [0,20]') + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21) + + self.log.info("Check we can change descriptor internal flag") + self.test_importdesc({**range_request, "range": [0, 20], "internal": True}, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getnewaddress, '', 'p2sh-segwit') + assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 21) + wpriv.getrawchangeaddress('p2sh-segwit') + + self.test_importdesc({**range_request, "range": [0, 20], "internal": False}, wallet=wpriv, success=True) + assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21) + wpriv.getnewaddress('', 'p2sh-segwit') + assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 0) + assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getrawchangeaddress, 'p2sh-segwit') + # Make sure ranged imports import keys in order w1 = self.nodes[1].get_wallet_rpc('w1') self.log.info('Key ranges should be imported in order') @@ -303,9 +339,21 @@ class ImportDescriptorsTest(BitcoinTestFramework): w1.keypoolrefill() assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3) + self.log.info("Check we can change next_index") + # go back and forth with next_index + for i in [4, 0, 2, 1, 3]: + self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'), + 'active': True, + 'range': [0, 9], + 'next_index': i, + 'timestamp': 'now' + }, + success=True) + assert_equal(w1.getnewaddress('', 'bech32'), addresses[i]) + # Check active=False default self.log.info('Check imported descriptors are not active by default') - self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'), + self.test_importdesc({'desc': descsum_create('pkh([12345678/1h]' + xpub + '/*)'), 'range' : [0, 2], 'timestamp': 'now', 'internal': True @@ -313,6 +361,32 @@ class ImportDescriptorsTest(BitcoinTestFramework): success=True) assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy') + self.log.info('Check can activate inactive descriptor') + self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'), + 'range': [0, 5], + 'active': True, + 'timestamp': 'now', + 'internal': True + }, + success=True) + address = w1.getrawchangeaddress('legacy') + assert_equal(address, "mpA2Wh9dvZT7yfELq1UnrUmAoc5qCkMetg") + + self.log.info('Check can deactivate active descriptor') + self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'), + 'range': [0, 5], + 'active': False, + 'timestamp': 'now', + 'internal': True + }, + success=True) + assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy') + + self.log.info('Verify activation state is persistent') + w1.unloadwallet() + self.nodes[1].loadwallet('w1') + assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy') + # # Test importing a descriptor containing a WIF private key wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh" address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg" @@ -322,6 +396,10 @@ class ImportDescriptorsTest(BitcoinTestFramework): "timestamp": "now"}, success=True, wallet=wpriv) + + self.log.info('Test can import same descriptor with private key twice') + self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, wallet=wpriv) + test_address(wpriv, address, solvable=True, @@ -339,14 +417,25 @@ class ImportDescriptorsTest(BitcoinTestFramework): wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv") assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0) - self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#m2sr93jn", + xprv1 = 'tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52' + acc_xpub1 = 'tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8' # /84'/0'/0' + chg_xpub1 = 'tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf' # /84'/1'/0' + xprv2 = 'tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq' + acc_xprv2 = 'tprv8gVCsmRAxVSxyUpsL13Y7ZEWBFPWbgS5E2MmFVNGuANrknvmmn2vWnmHvU8AwEFYzR2ji6EeZLSCLVacsYkvor3Pcb5JY5FGcevqTwYvdYx' + acc_xpub2 = 'tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH' + chg_xpub2 = 'tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh' + xprv3 = 'tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1' + acc_xpub3 = 'tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E' + chg_xpub3 = 'tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb' + + self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/0h/0h/*," + xprv2 + "/84h/0h/0h/*," + xprv3 + "/84h/0h/0h/*))#m2sr93jn", "active": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_priv) - self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#q3sztvx5", + self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/1h/0h/*," + xprv2 + "/84h/1h/0h/*," + xprv3 + "/84h/1h/0h/*))#q3sztvx5", "active": True, "internal" : True, "range": 1000, @@ -374,14 +463,14 @@ class ImportDescriptorsTest(BitcoinTestFramework): wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub") assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0) - self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#tsry0s5e", + self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 +"/*))#tsry0s5e", "active": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_pub) - self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#c08a2rzv", + self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))#c08a2rzv", "active": True, "internal" : True, "range": 1000, @@ -396,8 +485,15 @@ class ImportDescriptorsTest(BitcoinTestFramework): change_addr = wmulti_pub.getrawchangeaddress('bech32') assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) + + # generate some utxos for next tests txid = w0.sendtoaddress(addr, 10) vout = find_vout_for_address(self.nodes[0], txid, addr) + + addr2 = wmulti_pub.getnewaddress('', 'bech32') + txid2 = w0.sendtoaddress(addr2, 10) + vout2 = find_vout_for_address(self.nodes[0], txid2, addr2) + self.nodes[0].generate(6) self.sync_all() assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance()) @@ -411,14 +507,14 @@ class ImportDescriptorsTest(BitcoinTestFramework): wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1") res = wmulti_priv1.importdescriptors([ { - "desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), + "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"), "active": True, "range": 1000, "next_index": 0, "timestamp": "now" }, { - "desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), + "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/1h/0h/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"), "active": True, "internal" : True, "range": 1000, @@ -434,14 +530,14 @@ class ImportDescriptorsTest(BitcoinTestFramework): wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2') res = wmulti_priv2.importdescriptors([ { - "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), + "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*," + xprv2 + "/84h/0h/0h/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"), "active": True, "range": 1000, "next_index": 0, "timestamp": "now" }, { - "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), + "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*," + xprv2 + "/84h/1h/0h/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"), "active": True, "internal" : True, "range": 1000, @@ -531,6 +627,33 @@ class ImportDescriptorsTest(BitcoinTestFramework): ) + self.log.info("Amending multisig with new private keys") + self.nodes[1].createwallet(wallet_name="wmulti_priv3", descriptors=True) + wmulti_priv3 = self.nodes[1].get_wallet_rpc("wmulti_priv3") + res = wmulti_priv3.importdescriptors([ + { + "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"), + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now" + }]) + assert_equal(res[0]['success'], True) + res = wmulti_priv3.importdescriptors([ + { + "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xprv2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"), + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now" + }]) + assert_equal(res[0]['success'], True) + + rawtx = self.nodes[1].createrawtransaction([{'txid': txid2, 'vout': vout2}], {w0.getnewaddress(): 9.999}) + tx = wmulti_priv3.signrawtransactionwithwallet(rawtx) + assert_equal(tx['complete'], True) + self.nodes[1].sendrawtransaction(tx['hex']) + self.log.info("Combo descriptors cannot be active") self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"), "active": True, diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 0a00c5eed9..baeac655df 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -746,6 +746,27 @@ class ImportMultiTest(BitcoinTestFramework): assert 'hdmasterfingerprint' not in pub_import_info assert 'hdkeypath' not in pub_import_info + # Bech32m addresses and descriptors cannot be imported + self.log.info("Bech32m addresses and descriptors cannot be imported") + self.test_importmulti( + { + "scriptPubKey": {"address": "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6"}, + "timestamp": "now", + }, + success=False, + error_code=-5, + error_message="Bech32m addresses cannot be imported into legacy wallets", + ) + self.test_importmulti( + { + "desc": descsum_create("tr({})".format(pub)), + "timestamp": "now", + }, + success=False, + error_code=-5, + error_message="Bech32m descriptors cannot be imported into legacy wallets", + ) + # Import some public keys to the keypool of a no privkey wallet self.log.info("Adding pubkey to keypool of disableprivkey wallet") self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True) diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 3fe6adeebc..28bfc9116f 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -161,7 +161,7 @@ class KeyPoolTest(BitcoinTestFramework): # Using a fee rate (10 sat / byte) well above the minimum relay rate # creating a 5,000 sat transaction with change should not be possible - assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) + assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) # creating a 10,000 sat transaction without change, with a manual input, should still be possible res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 2d792bac52..a571454acf 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -135,31 +135,33 @@ class WalletLabelsTest(BitcoinTestFramework): # in the label. This is a no-op. change_label(node, labels[2].addresses[0], labels[2], labels[2]) - self.log.info('Check watchonly labels') - node.createwallet(wallet_name='watch_only', disable_private_keys=True) - wallet_watch_only = node.get_wallet_rpc('watch_only') - BECH32_VALID = { - '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn', - '✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr', - '✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw', - } - BECH32_INVALID = { - '❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8', - '❌_VER16_PROB01': 'bcrt1sqq5r4036', - } - for l in BECH32_VALID: - ad = BECH32_VALID[l] - wallet_watch_only.importaddress(label=l, rescan=False, address=ad) - node.generatetoaddress(1, ad) - assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}}) - assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0) - for l in BECH32_INVALID: - ad = BECH32_INVALID[l] - assert_raises_rpc_error( - -5, - "Address is not valid" if self.options.descriptors else "Invalid Bitcoin address or script", - lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad), - ) + if self.options.descriptors: + # This is a descriptor wallet test because of segwit v1+ addresses + self.log.info('Check watchonly labels') + node.createwallet(wallet_name='watch_only', disable_private_keys=True) + wallet_watch_only = node.get_wallet_rpc('watch_only') + BECH32_VALID = { + '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn', + '✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr', + '✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw', + } + BECH32_INVALID = { + '❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8', + '❌_VER16_PROB01': 'bcrt1sqq5r4036', + } + for l in BECH32_VALID: + ad = BECH32_VALID[l] + wallet_watch_only.importaddress(label=l, rescan=False, address=ad) + node.generatetoaddress(1, ad) + assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}}) + assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0) + for l in BECH32_INVALID: + ad = BECH32_INVALID[l] + assert_raises_rpc_error( + -5, + "Address is not valid" if self.options.descriptors else "Invalid Bitcoin address or script", + lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad), + ) class Label: diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py index c1444164ce..c2565d84f6 100755 --- a/test/functional/wallet_listdescriptors.py +++ b/test/functional/wallet_listdescriptors.py @@ -30,9 +30,10 @@ class ListDescriptorsTest(BitcoinTestFramework): node = self.nodes[0] assert_raises_rpc_error(-18, 'No wallet is loaded.', node.listdescriptors) - self.log.info('Test that the command is not available for legacy wallets.') - node.createwallet(wallet_name='w1', descriptors=False) - assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors) + if self.is_bdb_compiled(): + self.log.info('Test that the command is not available for legacy wallets.') + node.createwallet(wallet_name='w1', descriptors=False) + assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors) self.log.info('Test the command for empty descriptors wallet.') node.createwallet(wallet_name='w2', blank=True, descriptors=True) @@ -72,6 +73,10 @@ class ListDescriptorsTest(BitcoinTestFramework): } assert_equal(expected, wallet.listdescriptors()) + self.log.info("Test listdescriptors with encrypted wallet") + wallet.encryptwallet("pass") + assert_equal(expected, wallet.listdescriptors()) + self.log.info('Test non-active non-range combo descriptor') node.createwallet(wallet_name='w4', blank=True, descriptors=True) wallet = node.get_wallet_rpc('w4') diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 71573964de..8b503f5971 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -4,22 +4,17 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listtransactions API.""" from decimal import Decimal -from io import BytesIO -from test_framework.messages import COIN, CTransaction +from test_framework.messages import ( + COIN, + tx_from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, - hex_str_to_bytes, ) -def tx_from_hex(hexstring): - tx = CTransaction() - f = BytesIO(hex_str_to_bytes(hexstring)) - tx.deserialize(f) - return tx - class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 78d88f8aa5..37dee219e7 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -5,8 +5,10 @@ """Test that the wallet resends transactions periodically.""" import time -from test_framework.blocktools import create_block, create_coinbase -from test_framework.messages import ToHex +from test_framework.blocktools import ( + create_block, + create_coinbase, +) from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -48,7 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time) block.rehash() block.solve() - node.submitblock(ToHex(block)) + node.submitblock(block.serialize().hex()) # Set correct m_best_block_time, which is used in ResendWalletTransactions node.syncwithvalidationinterfacequeue() diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index 65ca7bdef7..9eb204bf37 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -6,6 +6,7 @@ import random +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create @@ -172,9 +173,9 @@ class WalletTaprootTest(BitcoinTestFramework): """Test generation and spending of P2TR address outputs.""" def set_test_params(self): - self.num_nodes = 2 + self.num_nodes = 3 self.setup_clean_chain = True - self.extra_args = [['-keypool=100'], ['-keypool=100']] + self.extra_args = [['-keypool=100'], ['-keypool=100'], ["-vbparams=taproot:1:1"]] self.supports_cli = False def skip_test_if_missing_module(self): @@ -225,19 +226,124 @@ class WalletTaprootTest(BitcoinTestFramework): result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) assert(result[0]['success']) for i in range(4): - addr_g = self.addr_gen.getnewaddress(address_type='bech32') + addr_g = self.addr_gen.getnewaddress(address_type='bech32m') if treefn is not None: addr_r = self.make_addr(treefn, keys, i) assert_equal(addr_g, addr_r) + desc_a = self.addr_gen.getaddressinfo(addr_g)['desc'] + if desc.startswith("tr("): + assert desc_a.startswith("tr(") + rederive = self.nodes[1].deriveaddresses(desc_a) + assert_equal(len(rederive), 1) + assert_equal(rederive[0], addr_g) + + # tr descriptors cannot be imported when Taproot is not active + result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert(result[0]["success"]) + result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + assert(result[0]["success"]) + if desc.startswith("tr"): + result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert(not result[0]["success"]) + assert_equal(result[0]["error"]["code"], -4) + assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + assert(not result[0]["success"]) + assert_equal(result[0]["error"]["code"], -4) + assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + + def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): + self.log.info("Testing %s through sendtoaddress" % comment) + desc_pay = self.make_desc(pattern, privmap, keys_pay) + desc_change = self.make_desc(pattern, privmap, keys_change) + desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) + desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) + assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) + assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) + result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + for i in range(4): + addr_g = self.rpc_online.getnewaddress(address_type='bech32m') + if treefn is not None: + addr_r = self.make_addr(treefn, keys_pay, i) + assert_equal(addr_g, addr_r) + boring_balance = int(self.boring.getbalance() * 100000000) + to_amnt = random.randrange(1000000, boring_balance) + self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + test_balance = int(self.rpc_online.getbalance() * 100000000) + ret_amnt = random.randrange(100000, test_balance) + res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.rpc_online.gettransaction(res)["confirmations"] > 0) + + def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change): + self.log.info("Testing %s through PSBT" % comment) + desc_pay = self.make_desc(pattern, privmap, keys_pay, False) + desc_change = self.make_desc(pattern, privmap, keys_change, False) + desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) + desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) + assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) + assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) + result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + for i in range(4): + addr_g = self.psbt_online.getnewaddress(address_type='bech32m') + if treefn is not None: + addr_r = self.make_addr(treefn, keys_pay, i) + assert_equal(addr_g, addr_r) + boring_balance = int(self.boring.getbalance() * 100000000) + to_amnt = random.randrange(1000000, boring_balance) + self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + test_balance = int(self.psbt_online.getbalance() * 100000000) + ret_amnt = random.randrange(100000, test_balance) + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0]})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt) + assert(res['complete']) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) def do_test(self, comment, pattern, privmap, treefn, nkeys): - keys = self.rand_keys(nkeys) - self.do_test_addr(comment, pattern, privmap, treefn, keys) + keys = self.rand_keys(nkeys * 4) + self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys]) + self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys]) + self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) def run_test(self): self.log.info("Creating wallets...") + self.nodes[0].createwallet(wallet_name="privs_tr_enabled", descriptors=True, blank=True) + self.privs_tr_enabled = self.nodes[0].get_wallet_rpc("privs_tr_enabled") + self.nodes[2].createwallet(wallet_name="privs_tr_disabled", descriptors=True, blank=True) + self.privs_tr_disabled=self.nodes[2].get_wallet_rpc("privs_tr_disabled") + self.nodes[0].createwallet(wallet_name="pubs_tr_enabled", descriptors=True, blank=True, disable_private_keys=True) + self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled") + self.nodes[2].createwallet(wallet_name="pubs_tr_disabled", descriptors=True, blank=True, disable_private_keys=True) + self.pubs_tr_disabled=self.nodes[2].get_wallet_rpc("pubs_tr_disabled") + self.nodes[0].createwallet(wallet_name="boring") self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True) + self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True) + self.boring = self.nodes[0].get_wallet_rpc("boring") self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen") + self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online") + self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online") + self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline") + + self.log.info("Mining blocks...") + gen_addr = self.boring.getnewaddress() + self.nodes[0].generatetoaddress(101, gen_addr) self.do_test( "tr(XPRV)", @@ -254,6 +360,13 @@ class WalletTaprootTest(BitcoinTestFramework): 1 ) self.do_test( + "wpkh(XPRV)", + "wpkh($1/*)", + [True], + None, + 1 + ) + self.do_test( "tr(XPRV,{H,{H,XPUB}})", "tr($1/*,{pk($H),{pk($H),pk($2/*)}})", [True, False], @@ -261,12 +374,54 @@ class WalletTaprootTest(BitcoinTestFramework): 2 ) self.do_test( + "wsh(multi(1,XPRV,XPUB))", + "wsh(multi(1,$1/*,$2/*))", + [True, False], + None, + 2 + ) + self.do_test( + "tr(XPRV,{XPUB,XPUB})", + "tr($1/*,{pk($2/*),pk($2/*)})", + [True, False], + lambda k1, k2: (key(k1), [pk(k2), pk(k2)]), + 2 + ) + self.do_test( + "tr(XPRV,{{XPUB,H},{H,XPUB}})", + "tr($1/*,{{pk($2/*),pk($H)},{pk($H),pk($2/*)}})", + [True, False], + lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]]), + 2 + ) + self.do_test( "tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})", "tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})", [False, False, True], lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]), 3 ) + self.do_test( + "tr(XPRV,{XPUB,{{XPUB,{H,H}},{{H,H},XPUB}}})", + "tr($1/*,{pk($2/*),{{pk($2/*),{pk($H),pk($H)}},{{pk($H),pk($H)},pk($2/*)}}})", + [True, False], + lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]]), + 2 + ) + + self.log.info("Sending everything back...") + + txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance(), subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) + + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()}], None, {"subtractFeeFromOutputs": [0]})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt) + assert(res['complete']) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) if __name__ == '__main__': WalletTaprootTest().main() diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index 84ff9ad772..76b39201e3 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -4,12 +4,14 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs.""" -import io from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, ) -from test_framework.messages import CTransaction, COIN +from test_framework.messages import ( + COIN, + tx_from_hex, +) class TxnMallTest(BitcoinTestFramework): @@ -71,8 +73,7 @@ class TxnMallTest(BitcoinTestFramework): clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime) # createrawtransaction randomizes the order of its outputs, so swap them if necessary. - clone_tx = CTransaction() - clone_tx.deserialize(io.BytesIO(bytes.fromhex(clone_raw))) + clone_tx = tx_from_hex(clone_raw) if (rawtx1["vout"][0]["value"] == 40 and clone_tx.vout[0].nValue != 40*COIN or rawtx1["vout"][0]["value"] != 40 and clone_tx.vout[0].nValue == 40*COIN): (clone_tx.vout[0], clone_tx.vout[1]) = (clone_tx.vout[1], clone_tx.vout[0]) diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index e7dd7592b6..ad11f4b244 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -6,9 +6,8 @@ Test upgradewallet RPC. Download node binaries: -test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 - -Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py +Requires previous releases binaries, see test/README.md. +Only v0.15.2 and v0.16.3 are required by this test. """ import os diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index b177fbb4b2..01e4ef47a7 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -21,39 +21,47 @@ import hashlib SHA256_SUMS = { -"d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz", -"54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz", -"2b843506c3f1af0eeca5854a920264f9a829f02d0d50328005950ddcbe88874d": "bitcoin-0.15.2-i686-pc-linux-gnu.tar.gz", -"87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": "bitcoin-0.15.2-osx64.tar.gz", -"566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz", - -"0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz", -"fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz", -"75a537844313b0a84bdb61ffcdc5c4ce19a738f7ddf71007cd2edf664efd7c37": "bitcoin-0.16.3-i686-pc-linux-gnu.tar.gz", -"78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": "bitcoin-0.16.3-osx64.tar.gz", -"5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz", - -"5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz", -"d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz", -"d295fc93f39bbf0fd937b730a93184899a2eb6c3a6d53f3d857cbe77ef89b98c": "bitcoin-0.17.2-i686-pc-linux-gnu.tar.gz", -"a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": "bitcoin-0.17.2-osx64.tar.gz", -"943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz", - -"88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz", -"cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz", -"989e847b3e95fc9fedc0b109cae1b4fa43348f2f712e187a118461876af9bd16": "bitcoin-0.18.1-i686-pc-linux-gnu.tar.gz", -"b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": "bitcoin-0.18.1-osx64.tar.gz", -"425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz", -"600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz", - -"3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz", -"657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz", -"10d1e53208aa7603022f4acc084a046299ab4ccf25fe01e81b3fb6f856772589": "bitcoin-0.19.1-i686-pc-linux-gnu.tar.gz", -"1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": "bitcoin-0.19.1-osx64.tar.gz", -"aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz", -"5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz" + "d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz", + "54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz", + "2b843506c3f1af0eeca5854a920264f9a829f02d0d50328005950ddcbe88874d": "bitcoin-0.15.2-i686-pc-linux-gnu.tar.gz", + "87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": "bitcoin-0.15.2-osx64.tar.gz", + "566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz", + # + "0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz", + "fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz", + "75a537844313b0a84bdb61ffcdc5c4ce19a738f7ddf71007cd2edf664efd7c37": "bitcoin-0.16.3-i686-pc-linux-gnu.tar.gz", + "78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": "bitcoin-0.16.3-osx64.tar.gz", + "5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz", + # + "5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz", + "d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz", + "d295fc93f39bbf0fd937b730a93184899a2eb6c3a6d53f3d857cbe77ef89b98c": "bitcoin-0.17.2-i686-pc-linux-gnu.tar.gz", + "a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": "bitcoin-0.17.2-osx64.tar.gz", + "943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz", + # + "88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz", + "cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz", + "989e847b3e95fc9fedc0b109cae1b4fa43348f2f712e187a118461876af9bd16": "bitcoin-0.18.1-i686-pc-linux-gnu.tar.gz", + "b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": "bitcoin-0.18.1-osx64.tar.gz", + "425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz", + "600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz", + # + "3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz", + "657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz", + "10d1e53208aa7603022f4acc084a046299ab4ccf25fe01e81b3fb6f856772589": "bitcoin-0.19.1-i686-pc-linux-gnu.tar.gz", + "1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": "bitcoin-0.19.1-osx64.tar.gz", + "aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz", + "5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz", + # + "60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz", + "55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz", + "b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": "bitcoin-0.20.1-osx64.tar.gz", + "c378d4e21109f09e8829f3591e015c66632dff2925a60b64d259be05a334c30b": "bitcoin-0.20.1-osx.dmg", + "fa71cb52ee5e0459cbf5248cdec72df27995840c796f58b304607a1ed4c165af": "bitcoin-0.20.1-riscv64-linux-gnu.tar.gz", + "376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397": "bitcoin-0.20.1-x86_64-linux-gnu.tar.gz", } + @contextlib.contextmanager def pushd(new_dir) -> None: previous_dir = os.getcwd() diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index 51815963f6..c448fa6f9a 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -102,7 +102,7 @@ if ! PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; e EXIT_CODE=1 fi -if ! mypy --ignore-missing-imports $(git ls-files "test/functional/*.py" "contrib/devtools/*.py"); then +if ! mypy --ignore-missing-imports --show-error-codes $(git ls-files "test/functional/*.py" "contrib/devtools/*.py"); then EXIT_CODE=1 fi diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index 267c06ea20..9906b15e9a 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -7,6 +7,7 @@ hights hist inout invokable +keypair mor nin ser diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh index fbdf3c59c1..238fa63c45 100755 --- a/test/lint/lint-spelling.sh +++ b/test/lint/lint-spelling.sh @@ -15,6 +15,6 @@ if ! command -v codespell > /dev/null; then fi IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt -if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/qt/locale/" ":(exclude)src/qt/*.qrc" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)contrib/gitian-keys/keys.txt"); then +if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/qt/locale/" ":(exclude)src/qt/*.qrc" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)contrib/gitian-keys/keys.txt" ":(exclude)contrib/guix/patches"); then echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" fi diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 877adaccec..2850cfcea5 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -34,9 +34,6 @@ unsigned-integer-overflow:crypto/ unsigned-integer-overflow:FuzzedDataProvider.h unsigned-integer-overflow:hash.cpp unsigned-integer-overflow:leveldb/ -# temporary coinstats suppressions (will be removed and fixed in https://github.com/bitcoin/bitcoin/pull/22146) -unsigned-integer-overflow:node/coinstats.cpp -signed-integer-overflow:node/coinstats.cpp unsigned-integer-overflow:policy/fees.cpp unsigned-integer-overflow:prevector.h unsigned-integer-overflow:pubkey.h diff --git a/test/util/data/bitcoin-util-test.json b/test/util/data/bitcoin-util-test.json index 0a9846b4be..a648c0287a 100644 --- a/test/util/data/bitcoin-util-test.json +++ b/test/util/data/bitcoin-util-test.json @@ -1,4 +1,34 @@ [ + { "exec": "./bitcoin-util", + "args": ["foo"], + "return_code": 1, + "error_txt": "Error parsing command line arguments: Invalid command 'foo'", + "description": "" + }, + { "exec": "./bitcoin-util", + "args": ["help"], + "return_code": 1, + "error_txt": "Error parsing command line arguments: Invalid command 'help'", + "description": "`help` raises an error. Use `-help`" + }, + { "exec": "./bitcoin-util", + "args": ["grind"], + "return_code": 1, + "error_txt": "Must specify block header to grind", + "description": "" + }, + { "exec": "./bitcoin-util", + "args": ["grind", "1", "2"], + "return_code": 1, + "error_txt": "Must specify block header to grind", + "description": "" + }, + { "exec": "./bitcoin-util", + "args": ["grind", "aa"], + "return_code": 1, + "error_txt": "Could not decode block header", + "description": "" + }, { "exec": "./bitcoin-tx", "args": ["-create", "nversion=1"], "output_cmp": "blanktxv1.hex", |