diff options
73 files changed, 719 insertions, 305 deletions
diff --git a/Makefile.am b/Makefile.am index f8bef07c26..2ff6dd0a11 100644 --- a/Makefile.am +++ b/Makefile.am @@ -126,7 +126,7 @@ $(OSX_ZIP): deploydir cd $(APP_DIST_DIR) && find . | sort | $(ZIP) -X@ $@ $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(OSX_APP_BUILT) $(OSX_PACKAGING) - OTOOL=$(OTOOL) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) + OBJDUMP=$(OBJDUMP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) deploydir: $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt endif !BUILD_DARWIN diff --git a/configure.ac b/configure.ac index ee4400f21a..dfb7b99756 100644 --- a/configure.ac +++ b/configure.ac @@ -125,6 +125,7 @@ AC_PATH_PROG([GIT], [git]) AC_PATH_PROG([CCACHE], [ccache]) AC_PATH_PROG([XGETTEXT], [xgettext]) AC_PATH_PROG([HEXDUMP], [hexdump]) +AC_PATH_TOOL([OBJDUMP], [objdump]) AC_PATH_TOOL([OBJCOPY], [objcopy]) AC_PATH_PROG([DOXYGEN], [doxygen]) AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"]) @@ -760,7 +761,6 @@ case $host in ;; *) AC_PATH_TOOL([DSYMUTIL], [dsymutil], [dsymutil]) - AC_PATH_TOOL([OTOOL], [otool], [otool]) AC_PATH_PROG([ZIP], [zip], [zip]) dnl libtool will try to strip the static lib, which is a problem for diff --git a/contrib/asmap/README.md b/contrib/asmap/README.md new file mode 100644 index 0000000000..5fab4b285e --- /dev/null +++ b/contrib/asmap/README.md @@ -0,0 +1,12 @@ +# ASMap Tool + +Tool for performing various operations on textual and binary asmap files, +particularly encoding/compressing the raw data to the binary format that can +be used in Bitcoin Core with the `-asmap` option. + +Example usage: +``` +python3 asmap-tool.py encode /path/to/input.file /path/to/output.file +python3 asmap-tool.py decode /path/to/input.file /path/to/output.file +python3 asmap-tool.py diff /path/to/first.file /path/to/second.file +``` diff --git a/contrib/asmap/asmap-tool.py b/contrib/asmap/asmap-tool.py new file mode 100755 index 0000000000..09c28725e4 --- /dev/null +++ b/contrib/asmap/asmap-tool.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import argparse +import sys +import ipaddress +import math + +import asmap + +def load_file(input_file): + try: + contents = input_file.read() + except OSError as err: + sys.exit(f"Input file '{input_file.name}' cannot be read: {err.strerror}.") + try: + bin_asmap = asmap.ASMap.from_binary(contents) + except ValueError: + bin_asmap = None + txt_error = None + entries = None + try: + txt_contents = str(contents, encoding="utf-8") + except UnicodeError: + txt_error = "invalid UTF-8" + txt_contents = None + if txt_contents is not None: + entries = [] + for line in txt_contents.split("\n"): + idx = line.find('#') + if idx >= 0: + line = line[:idx] + line = line.lstrip(' ').rstrip(' \t\r\n') + if len(line) == 0: + continue + fields = line.split(' ') + if len(fields) != 2: + txt_error = f"unparseable line '{line}'" + entries = None + break + prefix, asn = fields + if len(asn) <= 2 or asn[:2] != "AS" or any(c < '0' or c > '9' for c in asn[2:]): + txt_error = f"invalid ASN '{asn}'" + entries = None + break + try: + net = ipaddress.ip_network(prefix) + except ValueError: + txt_error = f"invalid network '{prefix}'" + entries = None + break + entries.append((asmap.net_to_prefix(net), int(asn[2:]))) + if entries is not None and bin_asmap is not None and len(contents) > 0: + sys.exit(f"Input file '{input_file.name}' is ambiguous.") + if entries is not None: + state = asmap.ASMap() + state.update_multi(entries) + return state + if bin_asmap is not None: + return bin_asmap + sys.exit(f"Input file '{input_file.name}' is neither a valid binary asmap file nor valid text input ({txt_error}).") + + +def save_binary(output_file, state, fill): + contents = state.to_binary(fill=fill) + try: + output_file.write(contents) + output_file.close() + except OSError as err: + sys.exit(f"Output file '{output_file.name}' cannot be written to: {err.strerror}.") + +def save_text(output_file, state, fill, overlapping): + for prefix, asn in state.to_entries(fill=fill, overlapping=overlapping): + net = asmap.prefix_to_net(prefix) + try: + print(f"{net} AS{asn}", file=output_file) + except OSError as err: + sys.exit(f"Output file '{output_file.name}' cannot be written to: {err.strerror}.") + try: + output_file.close() + except OSError as err: + sys.exit(f"Output file '{output_file.name}' cannot be written to: {err.strerror}.") + +def main(): + parser = argparse.ArgumentParser(description="Tool for performing various operations on textual and binary asmap files.") + subparsers = parser.add_subparsers(title="valid subcommands", dest="subcommand") + + parser_encode = subparsers.add_parser("encode", help="convert asmap data to binary format") + parser_encode.add_argument('-f', '--fill', dest="fill", default=False, action="store_true", + help="permit reassigning undefined network ranges arbitrarily to reduce size") + parser_encode.add_argument('infile', nargs='?', type=argparse.FileType('rb'), default=sys.stdin.buffer, + help="input asmap file (text or binary); default is stdin") + parser_encode.add_argument('outfile', nargs='?', type=argparse.FileType('wb'), default=sys.stdout.buffer, + help="output binary asmap file; default is stdout") + + parser_decode = subparsers.add_parser("decode", help="convert asmap data to text format") + parser_decode.add_argument('-f', '--fill', dest="fill", default=False, action="store_true", + help="permit reassigning undefined network ranges arbitrarily to reduce length") + parser_decode.add_argument('-n', '--nonoverlapping', dest="overlapping", default=True, action="store_false", + help="output strictly non-overall ping network ranges (increases output size)") + parser_decode.add_argument('infile', nargs='?', type=argparse.FileType('rb'), default=sys.stdin.buffer, + help="input asmap file (text or binary); default is stdin") + parser_decode.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, + help="output text file; default is stdout") + + parser_diff = subparsers.add_parser("diff", help="compute the difference between two asmap files") + parser_diff.add_argument('-i', '--ignore-unassigned', dest="ignore_unassigned", default=False, action="store_true", + help="ignore unassigned ranges in the first input (useful when second input is filled)") + parser_diff.add_argument('infile1', type=argparse.FileType('rb'), + help="first file to compare (text or binary)") + parser_diff.add_argument('infile2', type=argparse.FileType('rb'), + help="second file to compare (text or binary)") + + args = parser.parse_args() + if args.subcommand is None: + parser.print_help() + elif args.subcommand == "encode": + state = load_file(args.infile) + save_binary(args.outfile, state, fill=args.fill) + elif args.subcommand == "decode": + state = load_file(args.infile) + save_text(args.outfile, state, fill=args.fill, overlapping=args.overlapping) + elif args.subcommand == "diff": + state1 = load_file(args.infile1) + state2 = load_file(args.infile2) + ipv4_changed = 0 + ipv6_changed = 0 + for prefix, old_asn, new_asn in state1.diff(state2): + if args.ignore_unassigned and old_asn == 0: + continue + net = asmap.prefix_to_net(prefix) + if isinstance(net, ipaddress.IPv4Network): + ipv4_changed += 1 << (32 - net.prefixlen) + elif isinstance(net, ipaddress.IPv6Network): + ipv6_changed += 1 << (128 - net.prefixlen) + if new_asn == 0: + print(f"# {net} was AS{old_asn}") + elif old_asn == 0: + print(f"{net} AS{new_asn} # was unassigned") + else: + print(f"{net} AS{new_asn} # was AS{old_asn}") + ipv4_change_str = "" if ipv4_changed == 0 else f" (2^{math.log2(ipv4_changed):.2f})" + ipv6_change_str = "" if ipv6_changed == 0 else f" (2^{math.log2(ipv6_changed):.2f})" + + print( + f"# {ipv4_changed}{ipv4_change_str} IPv4 addresses changed; " + f"{ipv6_changed}{ipv6_change_str} IPv6 addresses changed" + ) + else: + parser.print_help() + sys.exit("No command provided.") + +if __name__ == '__main__': + main() diff --git a/contrib/seeds/asmap.py b/contrib/asmap/asmap.py index 214805b5a5..2ae84a3f31 100644 --- a/contrib/seeds/asmap.py +++ b/contrib/asmap/asmap.py @@ -489,12 +489,14 @@ class ASMap: if ctx not in ret or cand.size < ret[ctx].size: ret[ctx] = cand - for ctx in set(left) | set(right): + union = set(left) | set(right) + sorted_union = sorted(union, key=lambda x: (x is None, x)) + for ctx in sorted_union: candidate(ctx, left.get(ctx), right.get(ctx), _BinNode.make_branch) candidate(ctx, left.get(None), right.get(ctx), _BinNode.make_branch) candidate(ctx, left.get(ctx), right.get(None), _BinNode.make_branch) if not hole: - for ctx in set(ret) - set([None]): + for ctx in sorted(set(ret) - set([None])): candidate(None, ctx, ret[ctx], _BinNode.make_default) if None in ret: ret = {ctx:enc for ctx, enc in ret.items() diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 4b1d72650d..eaa7b896be 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -77,7 +77,7 @@ class FrameworkInfo(object): bundleBinaryDirectory = "Contents/MacOS" @classmethod - def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: + def fromLibraryLine(cls, line: str) -> Optional['FrameworkInfo']: # Note: line must be trimmed if line == "": return None @@ -88,7 +88,7 @@ class FrameworkInfo(object): m = cls.reOLine.match(line) if m is None: - raise RuntimeError(f"otool line could not be parsed: {line}") + raise RuntimeError(f"Line could not be parsed: {line}") path = m.group(1) @@ -120,7 +120,7 @@ class FrameworkInfo(object): break i += 1 if i == len(parts): - raise RuntimeError(f"Could not find .framework or .dylib in otool line: {line}") + raise RuntimeError(f"Could not find .framework or .dylib in line: {line}") info.frameworkName = parts[i] info.frameworkDirectory = "/".join(parts[:i]) @@ -182,24 +182,24 @@ class DeploymentInfo(object): return False def getFrameworks(binaryPath: str, verbose: int) -> list[FrameworkInfo]: + objdump = os.getenv("OBJDUMP", "objdump") if verbose: - print(f"Inspecting with otool: {binaryPath}") - otoolbin=os.getenv("OTOOL", "otool") - otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, text=True) - if otool.returncode != 0: - sys.stderr.write(otool.stderr) + print(f"Inspecting with {objdump}: {binaryPath}") + output = run([objdump, "--macho", "--dylibs-used", binaryPath], stdout=PIPE, stderr=PIPE, text=True) + if output.returncode != 0: + sys.stderr.write(output.stderr) sys.stderr.flush() - raise RuntimeError(f"otool failed with return code {otool.returncode}") + raise RuntimeError(f"{objdump} failed with return code {output.returncode}") - otoolLines = otool.stdout.split("\n") - otoolLines.pop(0) # First line is the inspected binary + lines = output.stdout.split("\n") + lines.pop(0) # First line is the inspected binary if ".framework" in binaryPath or binaryPath.endswith(".dylib"): - otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency. + lines.pop(0) # Frameworks and dylibs list themselves as a dependency. libraries = [] - for line in otoolLines: + for line in lines: line = line.replace("@loader_path", os.path.dirname(binaryPath)) - info = FrameworkInfo.fromOtoolLibraryLine(line.strip()) + info = FrameworkInfo.fromLibraryLine(line.strip()) if info is not None: if verbose: print("Found framework:") diff --git a/contrib/seeds/makeseeds.py b/contrib/seeds/makeseeds.py index 79bb178b21..7fd0f7158e 100755 --- a/contrib/seeds/makeseeds.py +++ b/contrib/seeds/makeseeds.py @@ -9,11 +9,14 @@ import argparse import collections import ipaddress +from pathlib import Path import re import sys from typing import Union -from asmap import ASMap, net_to_prefix +asmap_dir = Path(__file__).parent.parent / "asmap" +sys.path.append(str(asmap_dir)) +from asmap import ASMap, net_to_prefix # noqa: E402 NSEEDS=512 diff --git a/depends/Makefile b/depends/Makefile index 005d9696fb..016c8f64ad 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -233,7 +233,7 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@RANLIB@|$(host_RANLIB)|' \ -e 's|@NM@|$(host_NM)|' \ -e 's|@STRIP@|$(host_STRIP)|' \ - -e 's|@OTOOL@|$(host_OTOOL)|' \ + -e 's|@OBJDUMP@|$(host_OBJDUMP)|' \ -e 's|@DSYMUTIL@|$(host_DSYMUTIL)|' \ -e 's|@build_os@|$(build_os)|' \ -e 's|@host_os@|$(host_os)|' \ diff --git a/depends/builders/darwin.mk b/depends/builders/darwin.mk index 554bfd2c3e..be04e1d8f3 100644 --- a/depends/builders/darwin.mk +++ b/depends/builders/darwin.mk @@ -3,7 +3,7 @@ build_darwin_CXX:=$(shell xcrun -f clang++) -isysroot$(shell xcrun --show-sdk-pa build_darwin_AR:=$(shell xcrun -f ar) build_darwin_RANLIB:=$(shell xcrun -f ranlib) build_darwin_STRIP:=$(shell xcrun -f strip) -build_darwin_OTOOL:=$(shell xcrun -f otool) +build_darwin_OBJDUMP:=$(shell xcrun -f objdump) build_darwin_NM:=$(shell xcrun -f nm) build_darwin_DSYMUTIL:=$(shell xcrun -f dsymutil) build_darwin_SHA256SUM=shasum -a 256 @@ -15,7 +15,7 @@ darwin_CXX:=$(shell xcrun -f clang++) -stdlib=libc++ -isysroot$(shell xcrun --sh darwin_AR:=$(shell xcrun -f ar) darwin_RANLIB:=$(shell xcrun -f ranlib) darwin_STRIP:=$(shell xcrun -f strip) -darwin_OTOOL:=$(shell xcrun -f otool) +darwin_OBJDUMP:=$(shell xcrun -f objdump) darwin_NM:=$(shell xcrun -f nm) darwin_DSYMUTIL:=$(shell xcrun -f dsymutil) darwin_native_binutils= diff --git a/depends/builders/default.mk b/depends/builders/default.mk index 50869cd8a2..2a1709d98a 100644 --- a/depends/builders/default.mk +++ b/depends/builders/default.mk @@ -1,6 +1,7 @@ default_build_CC = gcc default_build_CXX = g++ default_build_AR = ar +default_build_OBJDUMP = objdump default_build_TAR = tar default_build_RANLIB = ranlib default_build_STRIP = strip @@ -12,7 +13,7 @@ build_$(build_os)_$1 ?= $$(default_build_$1) build_$(build_arch)_$(build_os)_$1 ?= $$(build_$(build_os)_$1) build_$1=$$(build_$(build_arch)_$(build_os)_$1) endef -$(foreach var,CC CXX AR TAR RANLIB NM STRIP SHA256SUM DOWNLOAD OTOOL DSYMUTIL TOUCH,$(eval $(call add_build_tool_func,$(var)))) +$(foreach var,CC CXX AR TAR RANLIB NM STRIP SHA256SUM DOWNLOAD OBJDUMP DSYMUTIL TOUCH,$(eval $(call add_build_tool_func,$(var)))) define add_build_flags_func build_$(build_arch)_$(build_os)_$1 += $(build_$(build_os)_$1) build_$1=$$(build_$(build_arch)_$(build_os)_$1) diff --git a/depends/config.site.in b/depends/config.site.in index 81975f02b9..f6bed6a9d4 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -118,9 +118,9 @@ if test -n "@STRIP@"; then fi if test "@host_os@" = darwin; then - if test -n "@OTOOL@"; then - OTOOL="@OTOOL@" - ac_cv_path_OTOOL="${OTOOL}" + if test -n "@OBJDUMP@"; then + OBJDUMP="@OBJDUMP@" + ac_cv_path_OBJDUMP="${OBJDUMP}" fi if test -n "@DSYMUTIL@"; then diff --git a/depends/funcs.mk b/depends/funcs.mk index d8a72421b4..7f79478cbd 100644 --- a/depends/funcs.mk +++ b/depends/funcs.mk @@ -180,6 +180,9 @@ $(1)_cmake=env CC="$$($(1)_cc)" \ CXXFLAGS="$$($(1)_cppflags) $$($(1)_cxxflags)" \ LDFLAGS="$$($(1)_ldflags)" \ cmake -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)" \ + -DCMAKE_AR=`which $$($(1)_ar)` \ + -DCMAKE_NM=`which $$($(1)_nm)` \ + -DCMAKE_RANLIB=`which $$($(1)_ranlib)` \ -DCMAKE_INSTALL_LIBDIR=lib/ \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ -DCMAKE_VERBOSE_MAKEFILE:BOOL=$(V) \ diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk index 639259ace3..2c4ef0644d 100644 --- a/depends/hosts/darwin.mk +++ b/depends/hosts/darwin.mk @@ -19,6 +19,8 @@ clang_prog=$(build_prefix)/bin/clang clangxx_prog=$(clang_prog)++ llvm_config_prog=$(build_prefix)/bin/llvm-config +darwin_OBJDUMP=$(build_prefix)/bin/$(host)-objdump + else # FORCE_USE_SYSTEM_CLANG is non-empty, so we use the clang from the user's # system @@ -37,9 +39,11 @@ clangxx_prog=$(shell $(SHELL) $(.SHELLFLAGS) "command -v clang++") llvm_config_prog=$(shell $(SHELL) $(.SHELLFLAGS) "command -v llvm-config") llvm_lib_dir=$(shell $(llvm_config_prog) --libdir) + +darwin_OBJDUMP=$(shell $(SHELL) $(.SHELLFLAGS) "command -v llvm-objdump") endif -cctools_TOOLS=AR RANLIB STRIP NM OTOOL DSYMUTIL +cctools_TOOLS=AR RANLIB STRIP NM DSYMUTIL # Make-only lowercase function lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) diff --git a/depends/hosts/default.mk b/depends/hosts/default.mk index 6a6cab6cc6..d82c33f29c 100644 --- a/depends/hosts/default.mk +++ b/depends/hosts/default.mk @@ -38,5 +38,5 @@ host_$1 = $$($(host_arch)_$(host_os)_$1) host_$(release_type)_$1 = $$($(host_arch)_$(host_os)_$(release_type)_$1) endef -$(foreach tool,CC CXX AR RANLIB STRIP NM OBJCOPY OTOOL DSYMUTIL,$(eval $(call add_host_tool_func,$(tool)))) +$(foreach tool,CC CXX AR RANLIB STRIP NM OBJCOPY OBJDUMP DSYMUTIL,$(eval $(call add_host_tool_func,$(tool)))) $(foreach flags,CFLAGS CXXFLAGS CPPFLAGS LDFLAGS, $(eval $(call add_host_flags_func,$(flags)))) diff --git a/depends/packages/native_llvm.mk b/depends/packages/native_llvm.mk index 09994eb012..1953c91bf4 100644 --- a/depends/packages/native_llvm.mk +++ b/depends/packages/native_llvm.mk @@ -18,6 +18,7 @@ define $(package)_stage_cmds cp -P bin/clang++ $($(package)_staging_prefix_dir)/bin/ && \ cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil && \ cp bin/llvm-config $($(package)_staging_prefix_dir)/bin/ && \ + cp bin/llvm-objdump $($(package)_staging_prefix_dir)/bin/$(host)-objdump && \ cp include/llvm-c/ExternC.h $($(package)_staging_prefix_dir)/include/llvm-c && \ cp include/llvm-c/lto.h $($(package)_staging_prefix_dir)/include/llvm-c && \ cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ diff --git a/src/Makefile.am b/src/Makefile.am index 226ee9321f..b749651b72 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -945,7 +945,6 @@ libbitcoinkernel_la_SOURCES = \ kernel/disconnected_transactions.cpp \ kernel/mempool_persist.cpp \ kernel/mempool_removal_reason.cpp \ - key.cpp \ logging.cpp \ node/blockstorage.cpp \ node/chainstate.cpp \ diff --git a/src/bench/bip324_ecdh.cpp b/src/bench/bip324_ecdh.cpp index fb10c2957e..88f3932ad8 100644 --- a/src/bench/bip324_ecdh.cpp +++ b/src/bench/bip324_ecdh.cpp @@ -14,7 +14,7 @@ static void BIP324_ECDH(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; FastRandomContext rng; std::array<std::byte, 32> key_data; @@ -44,8 +44,6 @@ static void BIP324_ECDH(benchmark::Bench& bench) // - Copy 16 bytes from the resulting shared secret into the middle of their ellswift key. std::copy(ret.begin() + 16, ret.end(), their_ellswift_data.begin() + 24); }); - - ECC_Stop(); } BENCHMARK(BIP324_ECDH, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 4a3ec67c2b..05b2f5435c 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -18,7 +18,7 @@ // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CCoinsCaching(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; FillableSigningProvider keystore; CCoinsView coinsDummy; @@ -47,7 +47,6 @@ static void CCoinsCaching(benchmark::Bench& bench) bool success{AreInputsStandard(tx_1, coins)}; assert(success); }); - ECC_Stop(); } BENCHMARK(CCoinsCaching, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 114dd9d39c..c973fe9f71 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -25,7 +25,7 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) // We shouldn't ever be running with the checkqueue on a single core machine. if (GetNumCores() <= 1) return; - ECC_Start(); + ECC_Context ecc_context{}; struct PrevectorJob { prevector<PREVECTOR_SIZE, uint8_t> p; @@ -62,6 +62,5 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) // it is done explicitly here for clarity control.Wait(); }); - ECC_Stop(); } BENCHMARK(CCheckQueueSpeedPrevectorJob, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/descriptors.cpp b/src/bench/descriptors.cpp index fbef1395fb..5d6bcb8ce8 100644 --- a/src/bench/descriptors.cpp +++ b/src/bench/descriptors.cpp @@ -12,7 +12,7 @@ static void ExpandDescriptor(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; const auto desc_str = "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))"; const std::pair<int64_t, int64_t> range = {0, 1000}; @@ -27,8 +27,6 @@ static void ExpandDescriptor(benchmark::Bench& bench) assert(success); } }); - - ECC_Stop(); } BENCHMARK(ExpandDescriptor, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ellswift.cpp b/src/bench/ellswift.cpp index 9441b4863e..4780db8e1c 100644 --- a/src/bench/ellswift.cpp +++ b/src/bench/ellswift.cpp @@ -9,7 +9,7 @@ static void EllSwiftCreate(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; CKey key = GenerateRandomKey(); uint256 entropy = GetRandHash(); @@ -22,8 +22,6 @@ static void EllSwiftCreate(benchmark::Bench& bench) /* Use the last 32 bytes of the ellswift encoded public key as next entropy. */ std::copy(ret.begin() + 32, ret.begin() + 64, MakeWritableByteSpan(entropy).begin()); }); - - ECC_Stop(); } BENCHMARK(EllSwiftCreate, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index ee750bc1f8..f38aa49a23 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -15,7 +15,7 @@ // modified to measure performance of other types of scripts. static void VerifyScriptBench(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; const uint32_t flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; const int witnessversion = 0; @@ -57,7 +57,6 @@ static void VerifyScriptBench(benchmark::Bench& bench) assert(err == SCRIPT_ERR_OK); assert(success); }); - ECC_Stop(); } static void VerifyNestedIfScript(benchmark::Bench& bench) diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index 642af06e82..4927634233 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -26,6 +26,7 @@ #include <script/sigcache.h> #include <util/chaintype.h> #include <util/fs.h> +#include <util/signalinterrupt.h> #include <util/task_runner.h> #include <validation.h> #include <validationinterface.h> diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 1c5b0c074c..cfac50e090 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -692,21 +692,10 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) tx = mergedTx; } -class Secp256k1Init -{ -public: - Secp256k1Init() { - ECC_Start(); - } - ~Secp256k1Init() { - ECC_Stop(); - } -}; - static void MutateTx(CMutableTransaction& tx, const std::string& command, const std::string& commandVal) { - std::unique_ptr<Secp256k1Init> ecc; + std::unique_ptr<ECC_Context> ecc; if (command == "nversion") MutateTxVersion(tx, commandVal); @@ -726,10 +715,10 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, else if (command == "outaddr") MutateTxAddOutAddr(tx, commandVal); else if (command == "outpubkey") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxAddOutPubKey(tx, commandVal); } else if (command == "outmultisig") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxAddOutMultiSig(tx, commandVal); } else if (command == "outscript") MutateTxAddOutScript(tx, commandVal); @@ -737,7 +726,7 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, MutateTxAddOutData(tx, commandVal); else if (command == "sign") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxSign(tx, commandVal); } diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index bee052bc46..e6d20b55c2 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -128,10 +128,9 @@ MAIN_FUNCTION return EXIT_FAILURE; } - ECC_Start(); + ECC_Context ecc_context{}; if (!wallet::WalletTool::ExecuteWalletToolFunc(args, command->command)) { return EXIT_FAILURE; } - ECC_Stop(); return EXIT_SUCCESS; } diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 7685bdea79..0b89aa42af 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -14,11 +14,13 @@ #include <init.h> #include <interfaces/chain.h> #include <interfaces/init.h> +#include <kernel/context.h> #include <node/context.h> #include <node/interface_ui.h> #include <noui.h> #include <util/check.h> #include <util/exception.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/syserror.h> #include <util/threadnames.h> @@ -180,6 +182,7 @@ static bool AppInit(NodeContext& node) } node.kernel = std::make_unique<kernel::Context>(); + node.ecc_context = std::make_unique<ECC_Context>(); if (!AppInitSanityChecks(*node.kernel)) { // InitError will have been called with detailed error, which ends up on console diff --git a/src/init.cpp b/src/init.cpp index d2e0a80780..51ade4de93 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -32,6 +32,8 @@ #include <interfaces/chain.h> #include <interfaces/init.h> #include <interfaces/node.h> +#include <kernel/context.h> +#include <key.h> #include <logging.h> #include <mapport.h> #include <net.h> @@ -76,6 +78,7 @@ #include <util/fs_helpers.h> #include <util/moneystr.h> #include <util/result.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/syserror.h> @@ -372,6 +375,7 @@ void Shutdown(NodeContext& node) node.chainman.reset(); node.validation_signals.reset(); node.scheduler.reset(); + node.ecc_context.reset(); node.kernel.reset(); RemovePidFile(*node.args); @@ -534,9 +538,7 @@ void SetupServerArgs(ArgsManager& argsman) 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("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); - // TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from - // https://github.com/bitcoin/bitcoin/pull/23542 have become widespread. - 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("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); #if HAVE_SOCKADDR_UN argsman.AddArg("-proxy=<ip:port|path>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled). May be a local file path prefixed with 'unix:' if the proxy supports it.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); #else @@ -1085,6 +1087,10 @@ bool AppInitSanityChecks(const kernel::Context& kernel) return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } + if (!ECC_InitSanityCheck()) { + return InitError(strprintf(_("Elliptic curve cryptography sanity check failure. %s is shutting down."), PACKAGE_NAME)); + } + // Probe the data directory lock to give an early error message, if possible // We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened, // and a fork will cause weird behavior to it. diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp index 45a5e25093..e4a13ee4cc 100644 --- a/src/kernel/checks.cpp +++ b/src/kernel/checks.cpp @@ -4,8 +4,8 @@ #include <kernel/checks.h> -#include <key.h> #include <random.h> +#include <util/result.h> #include <util/translation.h> #include <memory> @@ -14,10 +14,6 @@ namespace kernel { util::Result<void> SanityChecks(const Context&) { - if (!ECC_InitSanityCheck()) { - return util::Error{Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")}; - } - if (!Random_SanityCheck()) { return util::Error{Untranslated("OS cryptographic RNG sanity check failure. Aborting.")}; } diff --git a/src/kernel/context.cpp b/src/kernel/context.cpp index c60f1638d1..bfb17915fd 100644 --- a/src/kernel/context.cpp +++ b/src/kernel/context.cpp @@ -5,9 +5,7 @@ #include <kernel/context.h> #include <crypto/sha256.h> -#include <key.h> #include <logging.h> -#include <pubkey.h> #include <random.h> #include <string> @@ -19,12 +17,7 @@ Context::Context() std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); RandomInit(); - ECC_Start(); } -Context::~Context() -{ - ECC_Stop(); -} } // namespace kernel diff --git a/src/kernel/context.h b/src/kernel/context.h index ff4df20473..159af12689 100644 --- a/src/kernel/context.h +++ b/src/kernel/context.h @@ -5,10 +5,6 @@ #ifndef BITCOIN_KERNEL_CONTEXT_H #define BITCOIN_KERNEL_CONTEXT_H -#include <util/signalinterrupt.h> - -#include <memory> - namespace kernel { //! Context struct holding the kernel library's logically global state, and //! passed to external libbitcoin_kernel functions which need access to this @@ -19,7 +15,6 @@ namespace kernel { //! should be stored to std::unique_ptr members pointing to opaque types. struct Context { Context(); - ~Context(); }; } // namespace kernel diff --git a/src/key.cpp b/src/key.cpp index 2bd6396298..e8458f2e3b 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -432,7 +432,8 @@ bool ECC_InitSanityCheck() { return key.VerifyPubKey(pubkey); } -void ECC_Start() { +/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ +static void ECC_Start() { assert(secp256k1_context_sign == nullptr); secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -449,7 +450,8 @@ void ECC_Start() { secp256k1_context_sign = ctx; } -void ECC_Stop() { +/** Deinitialize the elliptic curve support. No-op if ECC_Start wasn't called first. */ +static void ECC_Stop() { secp256k1_context *ctx = secp256k1_context_sign; secp256k1_context_sign = nullptr; @@ -457,3 +459,13 @@ void ECC_Stop() { secp256k1_context_destroy(ctx); } } + +ECC_Context::ECC_Context() +{ + ECC_Start(); +} + +ECC_Context::~ECC_Context() +{ + ECC_Stop(); +} @@ -236,13 +236,21 @@ struct CExtKey { void SetSeed(Span<const std::byte> seed); }; -/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ -void ECC_Start(); - -/** Deinitialize the elliptic curve support. No-op if ECC_Start wasn't called first. */ -void ECC_Stop(); - /** Check that required EC support is available at runtime. */ bool ECC_InitSanityCheck(); +/** + * RAII class initializing and deinitializing global state for elliptic curve support. + * Only one instance may be initialized at a time. + * + * In the future global ECC state could be removed, and this class could contain + * state and be passed as an argument to ECC key functions. + */ +class ECC_Context +{ +public: + ECC_Context(); + ~ECC_Context(); +}; + #endif // BITCOIN_KEY_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 57e2f7409b..5d2949af33 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -5441,16 +5441,19 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seco // unless it's invalid, in which case we should find that out and // disconnect from them elsewhere). if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork) { + // The outbound peer has sent us a block with at least as much work as our current tip, so reset the timeout if it was set if (state.m_chain_sync.m_timeout != 0s) { state.m_chain_sync.m_timeout = 0s; state.m_chain_sync.m_work_header = nullptr; state.m_chain_sync.m_sent_getheaders = false; } } else if (state.m_chain_sync.m_timeout == 0s || (state.m_chain_sync.m_work_header != nullptr && state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= state.m_chain_sync.m_work_header->nChainWork)) { - // Our best block known by this peer is behind our tip, and we're either noticing - // that for the first time, OR this peer was able to catch up to some earlier point - // where we checked against our tip. - // Either way, set a new timeout based on current tip. + // At this point we know that the outbound peer has either never sent us a block/header or they have, but its tip is behind ours + // AND + // we are noticing this for the first time (m_timeout is 0) + // OR we noticed this at some point within the last CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds and set a timeout + // for them, they caught up to our tip at the time of setting the timer but not to our current one (we've also advanced). + // Either way, set a new timeout based on our current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; state.m_chain_sync.m_work_header = m_chainman.ActiveChain().Tip(); state.m_chain_sync.m_sent_getheaders = false; diff --git a/src/node/context.cpp b/src/node/context.cpp index ca56fa0b86..e32d21b383 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -8,6 +8,7 @@ #include <banman.h> #include <interfaces/chain.h> #include <kernel/context.h> +#include <key.h> #include <net.h> #include <net_processing.h> #include <netgroup.h> @@ -16,6 +17,7 @@ #include <scheduler.h> #include <txmempool.h> #include <validation.h> +#include <validationinterface.h> namespace node { NodeContext::NodeContext() = default; diff --git a/src/node/context.h b/src/node/context.h index 245f230aec..a7d92989dd 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -5,10 +5,7 @@ #ifndef BITCOIN_NODE_CONTEXT_H #define BITCOIN_NODE_CONTEXT_H -#include <kernel/context.h> - #include <atomic> -#include <cassert> #include <cstdlib> #include <functional> #include <memory> @@ -24,6 +21,7 @@ class ValidationSignals; class CScheduler; class CTxMemPool; class ChainstateManager; +class ECC_Context; class NetGroupManager; class PeerManager; namespace interfaces { @@ -32,6 +30,12 @@ class ChainClient; class Init; class WalletLoader; } // namespace interfaces +namespace kernel { +struct Context; +} +namespace util { +class SignalInterrupt; +} namespace node { class KernelNotifications; @@ -49,6 +53,7 @@ class KernelNotifications; struct NodeContext { //! libbitcoin_kernel context std::unique_ptr<kernel::Context> kernel; + std::unique_ptr<ECC_Context> ecc_context; //! Init interface for initializing current process and connecting to other processes. interfaces::Init* init{nullptr}; //! Interrupt object used to track whether node shutdown was requested. diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 84fc92e69d..c886357a34 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -17,6 +17,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <kernel/chain.h> +#include <kernel/context.h> #include <kernel/mempool_entry.h> #include <logging.h> #include <mapport.h> @@ -99,6 +100,7 @@ public: if (!AppInitParameterInteraction(args())) return false; m_context->kernel = std::make_unique<kernel::Context>(); + m_context->ecc_context = std::make_unique<ECC_Context>(); if (!AppInitSanityChecks(*m_context->kernel)) return false; if (!AppInitLockDataDirectory()) return false; diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp index 6578b383f7..e326d4a1f2 100644 --- a/src/node/kernel_notifications.cpp +++ b/src/node/kernel_notifications.cpp @@ -14,6 +14,7 @@ #include <node/abort.h> #include <node/interface_ui.h> #include <util/check.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 60e9bcde33..eeea53864a 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1113,7 +1113,7 @@ <item row="3" column="0"> <widget class="QLabel" name="peerSessionIdLabel"> <property name="toolTip"> - <string>The BIP324 session ID string in hex, if any.</string> + <string>The BIP324 session ID string in hex.</string> </property> <property name="text"> <string>Session ID</string> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 15103baf59..ee53a59bb5 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -18,6 +18,7 @@ #include <node/chainstatemanager_args.h> #include <netbase.h> #include <txdb.h> +#include <util/strencodings.h> #include <chrono> @@ -480,7 +481,10 @@ QValidator(parent) QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); - // Validate the proxy + uint16_t port{0}; + std::string hostname; + if (!SplitHostPort(input.toStdString(), port, hostname) || port != 0) return QValidator::Invalid; + CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT)); Proxy addrProxy = Proxy(serv, true); if (addrProxy.IsValid()) diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index b55b4cbe64..f261c6409d 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -115,7 +115,7 @@ HelpMessageDialog::~HelpMessageDialog() void HelpMessageDialog::printToConsole() { // On other operating systems, the expected action is to print the message to the console. - tfm::format(std::cout, "%s\n", qPrintable(text)); + tfm::format(std::cout, "%s", qPrintable(text)); } void HelpMessageDialog::showOrPrint() diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index fe000bcbb8..87ad98a4cc 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -529,12 +529,6 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) return false; } - WalletModel::UnlockContext ctx(requestUnlock()); - if(!ctx.isValid()) - { - return false; - } - // Short-circuit if we are returning a bumped transaction PSBT to clipboard if (retval == QMessageBox::Save) { // "Create Unsigned" clicked @@ -549,10 +543,15 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) DataStream ssTx{}; ssTx << psbtx; GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); - Q_EMIT message(tr("PSBT copied"), tr("Copied to clipboard", "Fee-bump PSBT saved"), CClientUIInterface::MSG_INFORMATION); + Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL); return true; } + WalletModel::UnlockContext ctx(requestUnlock()); + if (!ctx.isValid()) { + return false; + } + assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner()); // sign bumped transaction diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c9997ae063..1abaeafb2a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -188,12 +188,12 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr; UniValue objTx(UniValue::VOBJ); TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, txundo, verbosity); - txs.push_back(objTx); + txs.push_back(std::move(objTx)); } break; } - result.pushKV("tx", txs); + result.pushKV("tx", std::move(txs)); return result; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 73987669a5..2391392bd7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -31,6 +31,7 @@ #include <script/signingprovider.h> #include <txmempool.h> #include <univalue.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index 37c41f3895..8210e75cee 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -17,7 +17,7 @@ namespace { void Initialize() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index 6ea315d4e2..b9a5560ffb 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -55,7 +55,7 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov void initialize_descriptor_parse() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index 9e1e318e02..d389a29575 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -32,7 +32,7 @@ void initialize_key() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/key_io.cpp b/src/test/fuzz/key_io.cpp index 5f98f2b7f1..aefdefe233 100644 --- a/src/test/fuzz/key_io.cpp +++ b/src/test/fuzz/key_io.cpp @@ -14,7 +14,7 @@ void initialize_key_io() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/message.cpp b/src/test/fuzz/message.cpp index b5c95441f8..75baaa2754 100644 --- a/src/test/fuzz/message.cpp +++ b/src/test/fuzz/message.cpp @@ -19,7 +19,7 @@ void initialize_message() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp index 947424c4ac..f10007222c 100644 --- a/src/test/fuzz/miniscript.cpp +++ b/src/test/fuzz/miniscript.cpp @@ -1201,7 +1201,7 @@ void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& p void FuzzInit() { - ECC_Start(); + static ECC_Context ecc_context{}; TEST_DATA.Init(); } diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 1b7a732260..f6d82c3001 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -25,7 +25,7 @@ std::vector<std::string> g_all_messages; void initialize_p2p_transport_serialization() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); g_all_messages = getAllNetMessageTypes(); std::sort(g_all_messages.begin(), g_all_messages.end()); diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 9ae150e553..4695bc611b 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -26,7 +26,7 @@ void initialize_script_sign() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp index 74ef6bfd4e..ae0c8479cb 100644 --- a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp +++ b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp @@ -24,8 +24,7 @@ FUZZ_TARGET(secp256k1_ecdsa_signature_parse_der_lax) secp256k1_ecdsa_signature sig_der_lax; const bool parsed_der_lax = ecdsa_signature_parse_der_lax(&sig_der_lax, signature_bytes.data(), signature_bytes.size()) == 1; if (parsed_der_lax) { - ECC_Start(); + ECC_Context ecc_context{}; (void)SigHasLowR(&sig_der_lax); - ECC_Stop(); } } diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index a44f47b00d..037c7af77f 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -37,13 +37,12 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) SetMockTime(ConsumeTime(fuzzed_data_provider)); TxOrphanage orphanage; - std::vector<COutPoint> outpoints; + std::set<COutPoint> outpoints; + // initial outpoints used to construct transactions later for (uint8_t i = 0; i < 4; i++) { - outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); + outpoints.emplace(Txid::FromUint256(uint256{i}), 0); } - // if true, allow duplicate input when constructing tx - const bool duplicate_input = fuzzed_data_provider.ConsumeBool(); CTransactionRef ptx_potential_parent = nullptr; @@ -53,29 +52,22 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) const CTransactionRef tx = [&] { CMutableTransaction tx_mut; const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); - const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); - // pick unique outpoints from outpoints as input + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256); + // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not + // running any transaction validation logic before adding transactions to the orphanage for (uint32_t i = 0; i < num_in; i++) { auto& prevout = PickValue(fuzzed_data_provider, outpoints); - tx_mut.vin.emplace_back(prevout); - // pop the picked outpoint if duplicate input is not allowed - if (!duplicate_input) { - std::swap(prevout, outpoints.back()); - outpoints.pop_back(); - } + // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen + tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); } // output amount will not affect txorphanage for (uint32_t i = 0; i < num_out; i++) { tx_mut.vout.emplace_back(CAmount{0}, CScript{}); } - // restore previously popped outpoints - for (auto& in : tx_mut.vin) { - outpoints.push_back(in.prevout); - } auto new_tx = MakeTransactionRef(tx_mut); - // add newly constructed transaction to outpoints + // add newly constructed outpoints to the coin pool for (uint32_t i = 0; i < num_out; i++) { - outpoints.emplace_back(new_tx->GetHash(), i); + outpoints.emplace(new_tx->GetHash(), i); } return new_tx; }(); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 0fb5e4eb45..3b890c09db 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -183,6 +183,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto AppInitParameterInteraction(*m_node.args); LogInstance().StartLogging(); m_node.kernel = std::make_unique<kernel::Context>(); + m_node.ecc_context = std::make_unique<ECC_Context>(); SetupEnvironment(); ValidationCacheSizes validation_cache_sizes{}; @@ -200,6 +201,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto BasicTestingSetup::~BasicTestingSetup() { + m_node.ecc_context.reset(); m_node.kernel.reset(); SetMockTime(0s); // Reset mocktime for following tests LogInstance().DisconnectTestLogger(); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 8ccf9b571c..dbd66e3585 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -6,6 +6,7 @@ #define BITCOIN_TEST_UTIL_SETUP_COMMON_H #include <common/args.h> // IWYU pragma: export +#include <kernel/context.h> #include <key.h> #include <node/caches.h> #include <node/context.h> // IWYU pragma: export @@ -15,6 +16,7 @@ #include <util/chaintype.h> // IWYU pragma: export #include <util/check.h> #include <util/fs.h> +#include <util/signalinterrupt.h> #include <util/string.h> #include <util/vector.h> diff --git a/src/util/subprocess.h b/src/util/subprocess.h index af408b31d4..e76ced687c 100644 --- a/src/util/subprocess.h +++ b/src/util/subprocess.h @@ -902,17 +902,9 @@ private: * Command provided in a single string. * wait() - Wait for the child to exit. * retcode() - The return code of the exited child. - * pid() - PID of the spawned child. - * poll() - Check the status of the running child. * send(...) - Send input to the input channel of the child. * communicate(...) - Get the output/error from the child and close the channels * from the parent side. - * input() - Get the input channel/File pointer. Can be used for - * customizing the way of sending input to child. - * output() - Get the output channel/File pointer. Usually used - in case of redirection. See piping examples. - * error() - Get the error channel/File pointer. Usually used - in case of redirection. */ class Popen { @@ -956,14 +948,10 @@ public: execute_process(); } - int pid() const noexcept { return child_pid_; } - int retcode() const noexcept { return retcode_; } int wait() noexcept(false); - int poll() noexcept(false); - void set_out_buf_cap(size_t cap) { stream_.set_out_buf_cap(cap); } void set_err_buf_cap(size_t cap) { stream_.set_err_buf_cap(cap); } @@ -1001,15 +989,6 @@ public: return communicate(nullptr, 0); } - FILE* input() { return stream_.input(); } - FILE* output() { return stream_.output();} - FILE* error() { return stream_.error(); } - - /// Stream close APIs - void close_input() { stream_.input_.reset(); } - void close_output() { stream_.output_.reset(); } - void close_error() { stream_.error_.reset(); } - private: template <typename F, typename... Args> void init_args(F&& farg, Args&&... args); @@ -1033,7 +1012,6 @@ private: std::vector<std::string> vargs_; std::vector<char*> cargv_; - bool child_created_ = false; // Pid of the child process int child_pid_ = -1; @@ -1068,7 +1046,7 @@ inline int Popen::wait() noexcept(false) return 0; #else int ret, status; - std::tie(ret, status) = util::wait_for_child_exit(pid()); + std::tie(ret, status) = util::wait_for_child_exit(child_pid_); if (ret == -1) { if (errno != ECHILD) throw OSError("waitpid failed", errno); return 0; @@ -1081,56 +1059,6 @@ inline int Popen::wait() noexcept(false) #endif } -inline int Popen::poll() noexcept(false) -{ -#ifdef __USING_WINDOWS__ - int ret = WaitForSingleObject(process_handle_, 0); - if (ret != WAIT_OBJECT_0) return -1; - - DWORD dretcode_; - if (FALSE == GetExitCodeProcess(process_handle_, &dretcode_)) - throw OSError("GetExitCodeProcess", 0); - - retcode_ = (int)dretcode_; - CloseHandle(process_handle_); - - return retcode_; -#else - if (!child_created_) return -1; // TODO: ?? - - int status; - - // Returns zero if child is still running - int ret = waitpid(child_pid_, &status, WNOHANG); - if (ret == 0) return -1; - - if (ret == child_pid_) { - if (WIFSIGNALED(status)) { - retcode_ = WTERMSIG(status); - } else if (WIFEXITED(status)) { - retcode_ = WEXITSTATUS(status); - } else { - retcode_ = 255; - } - return retcode_; - } - - if (ret == -1) { - // From subprocess.py - // This happens if SIGCHLD is set to be ignored - // or waiting for child process has otherwise been disabled - // for our process. This child is dead, we cannot get the - // status. - if (errno == ECHILD) retcode_ = 0; - else throw OSError("waitpid failed", errno); - } else { - retcode_ = ret; - } - - return retcode_; -#endif -} - inline void Popen::execute_process() noexcept(false) { #ifdef __USING_WINDOWS__ @@ -1233,8 +1161,6 @@ inline void Popen::execute_process() noexcept(false) throw OSError("fork failed", errno); } - child_created_ = true; - if (child_pid_ == 0) { // Close descriptors belonging to parent diff --git a/src/validation.cpp b/src/validation.cpp index c2b56555a2..609ccec153 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2748,8 +2748,10 @@ bool Chainstate::FlushStateToDisk( return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!")); } // Flush the chainstate (which may refer to block index entries). - if (!CoinsTip().Flush()) + const auto empty_cache{(mode == FlushStateMode::ALWAYS) || fCacheLarge || fCacheCritical || fFlushForPrune}; + if (empty_cache ? !CoinsTip().Flush() : !CoinsTip().Sync()) { return FatalError(m_chainman.GetNotifications(), state, _("Failed to write to coin database.")); + } m_last_flush = nNow; full_flush_completed = true; TRACE5(utxocache, flush, diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index 58a5442f4e..0d6c92c9fa 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -405,6 +405,10 @@ class AssumeutxoTest(BitcoinTestFramework): assert_equal(snapshot['snapshot_blockhash'], dump_output['base_hash']) assert_equal(snapshot['validated'], False) + self.log.info("Check that loading the snapshot again will fail because there is already an active snapshot.") + with n2.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot-based chainstate more than once"]): + assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", n2.loadtxoutset, dump_output['path']) + self.connect_nodes(0, 2) self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) self.sync_blocks() diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 268009b0f4..22ae0c307b 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -85,7 +85,7 @@ class InitStressTest(BitcoinTestFramework): for terminate_line in lines_to_terminate_after: self.log.info(f"Starting node and will exit after line {terminate_line}") - with node.wait_for_debug_log([terminate_line]): + with node.busy_wait_for_debug_log([terminate_line]): node.start(extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1']) self.log.debug("Terminating node after terminate line was found") sigterm_node() diff --git a/test/functional/feature_reindex_readonly.py b/test/functional/feature_reindex_readonly.py index 25cff87a3b..52c0bb26a6 100755 --- a/test/functional/feature_reindex_readonly.py +++ b/test/functional/feature_reindex_readonly.py @@ -75,7 +75,7 @@ class BlockstoreReindexTest(BitcoinTestFramework): if undo_immutable: self.log.debug("Attempt to restart and reindex the node with the unwritable block file") - with self.nodes[0].wait_for_debug_log([b"Reindexing finished"]): + with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60): self.start_node(0, extra_args=['-reindex', '-fastprune']) assert block_count == self.nodes[0].getblockcount() undo_immutable() diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index e8a568f7ab..d46924f4ce 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -6,6 +6,9 @@ from decimal import Decimal +from test_framework.mempool_util import ( + fill_mempool, +) from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -13,7 +16,6 @@ from test_framework.util import ( assert_fee_amount, assert_greater_than, assert_raises_rpc_error, - fill_mempool, ) from test_framework.wallet import ( COIN, @@ -93,7 +95,7 @@ class MempoolLimitTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) - fill_mempool(self, node, self.wallet) + fill_mempool(self, node) current_info = node.getmempoolinfo() mempoolmin_feerate = current_info["mempoolminfee"] @@ -183,7 +185,7 @@ class MempoolLimitTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) - fill_mempool(self, node, self.wallet) + fill_mempool(self, node) current_info = node.getmempoolinfo() mempoolmin_feerate = current_info["mempoolminfee"] @@ -257,7 +259,7 @@ class MempoolLimitTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) - fill_mempool(self, node, self.wallet) + fill_mempool(self, node) # Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool self.log.info('Create a mempool tx that will not pass mempoolminfee') diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index dcb66b2ca1..e83c62915e 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -199,13 +199,13 @@ class MempoolPackagesTest(BitcoinTestFramework): assert set(mempool1).issubset(set(mempool0)) for tx in chain[:CUSTOM_ANCESTOR_LIMIT]: assert tx in mempool1 - # TODO: more detailed check of node1's mempool (fees etc.) - # check transaction unbroadcast info (should be false if in both mempools) - mempool = self.nodes[0].getrawmempool(True) - for tx in mempool: - assert_equal(mempool[tx]['unbroadcast'], False) - - # TODO: test ancestor size limits + entry0 = self.nodes[0].getmempoolentry(tx) + entry1 = self.nodes[1].getmempoolentry(tx) + assert not entry0['unbroadcast'] + assert not entry1['unbroadcast'] + assert_equal(entry1['fees']['base'], entry0['fees']['base']) + assert_equal(entry1['vsize'], entry0['vsize']) + assert_equal(entry1['depends'], entry0['depends']) # Now test descendant chain limits @@ -251,10 +251,14 @@ class MempoolPackagesTest(BitcoinTestFramework): assert tx in mempool1 for tx in chain[CUSTOM_DESCENDANT_LIMIT:]: assert tx not in mempool1 - # TODO: more detailed check of node1's mempool (fees etc.) - - # TODO: test descendant size limits - + for tx in mempool1: + entry0 = self.nodes[0].getmempoolentry(tx) + entry1 = self.nodes[1].getmempoolentry(tx) + assert not entry0['unbroadcast'] + assert not entry1['unbroadcast'] + assert_equal(entry1['fees']['base'], entry0['fees']['base']) + assert_equal(entry1['vsize'], entry0['vsize']) + assert_equal(entry1['depends'], entry0['depends']) # Test reorg handling # First, the basics: self.generate(self.nodes[0], 1) diff --git a/test/functional/p2p_1p1c_network.py b/test/functional/p2p_1p1c_network.py index e88c826962..ea59248506 100755 --- a/test/functional/p2p_1p1c_network.py +++ b/test/functional/p2p_1p1c_network.py @@ -12,6 +12,9 @@ too-low-feerate transactions). The packages should be received and accepted by a from decimal import Decimal from math import ceil +from test_framework.mempool_util import ( + fill_mempool, +) from test_framework.messages import ( msg_tx, ) @@ -22,7 +25,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, - fill_mempool, ) from test_framework.wallet import ( MiniWallet, @@ -45,8 +47,7 @@ class PackageRelayTest(BitcoinTestFramework): self.supports_cli = False def raise_network_minfee(self): - filler_wallet = MiniWallet(self.nodes[0]) - fill_mempool(self, self.nodes[0], filler_wallet) + fill_mempool(self, self.nodes[0]) self.log.debug("Wait for the network to sync mempools") self.sync_mempools() diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py index e07acd5481..aec6e95fbc 100755 --- a/test/functional/p2p_opportunistic_1p1c.py +++ b/test/functional/p2p_opportunistic_1p1c.py @@ -8,6 +8,9 @@ Test opportunistic 1p1c package submission logic. from decimal import Decimal import time +from test_framework.mempool_util import ( + fill_mempool, +) from test_framework.messages import ( CInv, CTxInWitness, @@ -24,7 +27,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, - fill_mempool, ) from test_framework.wallet import ( MiniWallet, @@ -386,8 +388,7 @@ class PackageRelayTest(BitcoinTestFramework): self.generate(self.wallet_nonsegwit, 10) self.generate(self.wallet, 20) - filler_wallet = MiniWallet(node) - fill_mempool(self, node, filler_wallet) + fill_mempool(self, node) self.log.info("Check opportunistic 1p1c logic when parent (txid != wtxid) is received before child") self.test_basic_parent_then_child(self.wallet) diff --git a/test/functional/p2p_outbound_eviction.py b/test/functional/p2p_outbound_eviction.py new file mode 100755 index 0000000000..8d89688999 --- /dev/null +++ b/test/functional/p2p_outbound_eviction.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019-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 node outbound peer eviction logic + +A subset of our outbound peers are subject to eviction logic if they cannot keep up +with our vision of the best chain. This criteria applies only to non-protected peers, +and can be triggered by either not learning about any blocks from an outbound peer after +a certain deadline, or by them not being able to catch up fast enough (under the same deadline). + +This tests the different eviction paths based on the peer's behavior and on whether they are protected +or not. +""" +import time + +from test_framework.messages import ( + from_hex, + msg_headers, + CBlockHeader, +) +from test_framework.p2p import P2PInterface +from test_framework.test_framework import BitcoinTestFramework + +# Timeouts (in seconds) +CHAIN_SYNC_TIMEOUT = 20 * 60 +HEADERS_RESPONSE_TIME = 2 * 60 + + +class P2POutEvict(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def test_outbound_eviction_unprotected(self): + # This tests the eviction logic for **unprotected** outbound peers (that is, PeerManagerImpl::ConsiderEviction) + node = self.nodes[0] + cur_mock_time = node.mocktime + + # Get our tip header and its parent + tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False)) + prev_header = from_hex(CBlockHeader(), node.getblockheader(f"{tip_header.hashPrevBlock:064x}", False)) + + self.log.info("Create an outbound connection and don't send any headers") + # Test disconnect due to no block being announced in 22+ minutes (headers are not even exchanged) + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay") + # Wait for over 20 min to trigger the first eviction timeout. This sets the last call past 2 min in the future. + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + # Wait for over 2 more min to trigger the disconnection + peer.wait_for_getheaders(block_hash=tip_header.hashPrevBlock) + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Test that the peer gets evicted") + peer.wait_for_disconnect() + + self.log.info("Create an outbound connection and send header but never catch up") + # Mimic a node that just falls behind for long enough + # This should also apply for a node doing IBD that does not catch up in time + # Connect a peer and make it send us headers ending in our tip's parent + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay") + peer.send_and_ping(msg_headers([prev_header])) + + # Trigger the timeouts + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + peer.wait_for_getheaders(block_hash=tip_header.hashPrevBlock) + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Test that the peer gets evicted") + peer.wait_for_disconnect() + + self.log.info("Create an outbound connection and keep lagging behind, but not too much") + # Test that if the peer never catches up with our current tip, but it does with the + # expected work that we set when setting the timer (that is, our tip at the time) + # we do not disconnect the peer + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay") + + self.log.info("Mine a block so our peer starts lagging") + prev_prev_hash = tip_header.hashPrevBlock + best_block_hash = self.generateblock(node, output="raw(42)", transactions=[])["hash"] + peer.sync_with_ping() + + self.log.info("Keep catching up with the old tip and check that we are not evicted") + for i in range(10): + # Generate an additional block so the peers is 2 blocks behind + prev_header = from_hex(CBlockHeader(), node.getblockheader(best_block_hash, False)) + best_block_hash = self.generateblock(node, output="raw(42)", transactions=[])["hash"] + peer.sync_with_ping() + + # Advance time but not enough to evict the peer + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + + # Wait until we get out last call (by receiving a getheaders) + peer.wait_for_getheaders(block_hash=prev_prev_hash) + + # Send a header with the previous tip (so we go back to 1 block behind) + peer.send_and_ping(msg_headers([prev_header])) + prev_prev_hash = tip_header.hash + + self.log.info("Create an outbound connection and take some time to catch up, but do it in time") + # Check that if the peer manages to catch up within time, the timeouts are removed (and the peer is not disconnected) + # We are reusing the peer from the previous case which already sent us a valid (but old) block and whose timer is ticking + + # Send an updated headers message matching our tip + peer.send_and_ping(msg_headers([from_hex(CBlockHeader(), node.getblockheader(best_block_hash, False))])) + + # Wait for long enough for the timeouts to have triggered and check that we are still connected + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Test that the peer does not get evicted") + peer.sync_with_ping() + + node.disconnect_p2ps() + + def test_outbound_eviction_protected(self): + # This tests the eviction logic for **protected** outbound peers (that is, PeerManagerImpl::ConsiderEviction) + # Outbound connections are flagged as protected as long as they have sent us a connecting block with at least as + # much work as our current tip and we have enough empty protected_peers slots. + node = self.nodes[0] + cur_mock_time = node.mocktime + tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False)) + + self.log.info("Create an outbound connection to a peer that shares our tip so it gets granted protection") + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay") + peer.send_and_ping(msg_headers([tip_header])) + + self.log.info("Mine a new block and sync with our peer") + self.generateblock(node, output="raw(42)", transactions=[]) + peer.sync_with_ping() + + self.log.info("Let enough time pass for the timeouts to go off") + # Trigger the timeouts and check how we are still connected + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + peer.wait_for_getheaders(block_hash=tip_header.hashPrevBlock) + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Test that the node does not get evicted") + peer.sync_with_ping() + + node.disconnect_p2ps() + + def test_outbound_eviction_mixed(self): + # This tests the outbound eviction logic for a mix of protected and unprotected peers. + node = self.nodes[0] + cur_mock_time = node.mocktime + + self.log.info("Create a mix of protected and unprotected outbound connections to check against eviction") + + # Let's try this logic having multiple peers, some protected and some unprotected + # We protect up to 4 peers as long as they have provided a block with the same amount of work as our tip + self.log.info("The first 4 peers are protected by sending us a valid block with enough work") + tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False)) + headers_message = msg_headers([tip_header]) + protected_peers = [] + for i in range(4): + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=i, connection_type="outbound-full-relay") + peer.send_and_ping(headers_message) + protected_peers.append(peer) + + # We can create 4 additional outbound connections to peers that are unprotected. 2 of them will be well behaved, + # whereas the other 2 will misbehave (1 sending no headers, 1 sending old ones) + self.log.info("The remaining 4 peers will be mixed between honest (2) and misbehaving peers (2)") + prev_header = from_hex(CBlockHeader(), node.getblockheader(f"{tip_header.hashPrevBlock:064x}", False)) + headers_message = msg_headers([prev_header]) + honest_unprotected_peers = [] + for i in range(2): + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=4+i, connection_type="outbound-full-relay") + peer.send_and_ping(headers_message) + honest_unprotected_peers.append(peer) + + misbehaving_unprotected_peers = [] + for i in range(2): + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=6+i, connection_type="outbound-full-relay") + if i%2==0: + peer.send_and_ping(headers_message) + misbehaving_unprotected_peers.append(peer) + + self.log.info("Mine a new block and keep the unprotected honest peer on sync, all the rest off-sync") + # Mine a block so all peers become outdated + target_hash = prev_header.rehash() + tip_hash = self.generateblock(node, output="raw(42)", transactions=[])["hash"] + tip_header = from_hex(CBlockHeader(), node.getblockheader(tip_hash, False)) + tip_headers_message = msg_headers([tip_header]) + + # Let the timeouts hit and check back + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + for peer in protected_peers + misbehaving_unprotected_peers: + peer.sync_with_ping() + peer.wait_for_getheaders(block_hash=target_hash) + for peer in honest_unprotected_peers: + peer.send_and_ping(tip_headers_message) + peer.wait_for_getheaders(block_hash=target_hash) + + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Check how none of the honest nor protected peers was evicted but all the misbehaving unprotected were") + for peer in protected_peers + honest_unprotected_peers: + peer.sync_with_ping() + for peer in misbehaving_unprotected_peers: + peer.wait_for_disconnect() + + node.disconnect_p2ps() + + def test_outbound_eviction_blocks_relay_only(self): + # The logic for outbound eviction protection only applies to outbound-full-relay peers + # This tests that other types of peers (blocks-relay-only for instance) are not granted protection + node = self.nodes[0] + cur_mock_time = node.mocktime + tip_header = from_hex(CBlockHeader(), node.getblockheader(node.getbestblockhash(), False)) + + self.log.info("Create an blocks-only outbound connection to a peer that shares our tip. This would usually grant protection") + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only") + peer.send_and_ping(msg_headers([tip_header])) + + self.log.info("Mine a new block and sync with our peer") + self.generateblock(node, output="raw(42)", transactions=[]) + peer.sync_with_ping() + + self.log.info("Let enough time pass for the timeouts to go off") + # Trigger the timeouts and check how the peer gets evicted, since protection is only given to outbound-full-relay peers + cur_mock_time += (CHAIN_SYNC_TIMEOUT + 1) + node.setmocktime(cur_mock_time) + peer.sync_with_ping() + peer.wait_for_getheaders(block_hash=tip_header.hash) + cur_mock_time += (HEADERS_RESPONSE_TIME + 1) + node.setmocktime(cur_mock_time) + self.log.info("Test that the peer gets evicted") + peer.wait_for_disconnect() + + node.disconnect_p2ps() + + + def run_test(self): + self.nodes[0].setmocktime(int(time.time())) + self.test_outbound_eviction_unprotected() + self.test_outbound_eviction_protected() + self.test_outbound_eviction_mixed() + self.test_outbound_eviction_blocks_relay_only() + + +if __name__ == '__main__': + P2POutEvict().main() diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index 7a50f1e605..0af6b1d2c9 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -8,6 +8,9 @@ Test transaction download behavior from decimal import Decimal import time +from test_framework.mempool_util import ( + fill_mempool, +) from test_framework.messages import ( CInv, MSG_TX, @@ -24,7 +27,6 @@ from test_framework.p2p import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - fill_mempool, ) from test_framework.wallet import MiniWallet @@ -248,7 +250,7 @@ class TxDownloadTest(BitcoinTestFramework): def test_rejects_filter_reset(self): self.log.info('Check that rejected tx is not requested again') node = self.nodes[0] - fill_mempool(self, node, self.wallet) + fill_mempool(self, node) self.wallet.rescan_utxos() mempoolminfee = node.getmempoolinfo()['mempoolminfee'] peer = node.add_p2p_connection(TestP2PConn()) diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index baf47be7ee..8ac0afdaaa 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -8,6 +8,9 @@ from decimal import Decimal import random from test_framework.blocktools import COINBASE_MATURITY +from test_framework.mempool_util import ( + fill_mempool, +) from test_framework.messages import ( MAX_BIP125_RBF_SEQUENCE, tx_from_hex, @@ -18,7 +21,6 @@ from test_framework.util import ( assert_equal, assert_fee_amount, assert_raises_rpc_error, - fill_mempool, ) from test_framework.wallet import ( DEFAULT_FEE, @@ -398,7 +400,7 @@ class RPCPackagesTest(BitcoinTestFramework): ]) self.wallet.rescan_utxos() - fill_mempool(self, node, self.wallet) + fill_mempool(self, node) minrelay = node.getmempoolinfo()["minrelaytxfee"] parent = self.wallet.create_self_transfer( diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 5b2e3289a9..bcb38b21cd 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -47,7 +47,7 @@ class AddressType(enum.Enum): b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -def create_deterministic_address_bcrt1_p2tr_op_true(): +def create_deterministic_address_bcrt1_p2tr_op_true(explicit_internal_key=None): """ Generates a deterministic bech32m address (segwit v1 output) that can be spent with a witness stack of OP_TRUE and the control block @@ -55,9 +55,10 @@ def create_deterministic_address_bcrt1_p2tr_op_true(): Returns a tuple with the generated address and the internal key. """ - internal_key = (1).to_bytes(32, 'big') + internal_key = explicit_internal_key or (1).to_bytes(32, 'big') address = output_key_to_p2tr(taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).output_pubkey) - assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') + if explicit_internal_key is None: + assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') return (address, internal_key) diff --git a/test/functional/test_framework/mempool_util.py b/test/functional/test_framework/mempool_util.py new file mode 100644 index 0000000000..148cc935ed --- /dev/null +++ b/test/functional/test_framework/mempool_util.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Helpful routines for mempool testing.""" +from decimal import Decimal + +from .blocktools import ( + COINBASE_MATURITY, +) +from .util import ( + assert_equal, + assert_greater_than, + create_lots_of_big_transactions, + gen_return_txouts, +) +from .wallet import ( + MiniWallet, +) + + +def fill_mempool(test_framework, node): + """Fill mempool until eviction. + + Allows for simpler testing of scenarios with floating mempoolminfee > minrelay + Requires -datacarriersize=100000 and + -maxmempool=5. + It will not ensure mempools become synced as it + is based on a single node and assumes -minrelaytxfee + is 1 sat/vbyte. + To avoid unintentional tx dependencies, the mempool filling txs are created with a + tagged ephemeral miniwallet instance. + """ + test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises") + txouts = gen_return_txouts() + relayfee = node.getnetworkinfo()['relayfee'] + + assert_equal(relayfee, Decimal('0.00001000')) + + tx_batch_size = 1 + num_of_batches = 75 + # Generate UTXOs to flood the mempool + # 1 to create a tx initially that will be evicted from the mempool later + # 75 transactions each with a fee rate higher than the previous one + ephemeral_miniwallet = MiniWallet(node, tag_name="fill_mempool_ephemeral_wallet") + test_framework.generate(ephemeral_miniwallet, 1 + num_of_batches * tx_batch_size) + + # Mine enough blocks so that the UTXOs are allowed to be spent + test_framework.generate(node, COINBASE_MATURITY - 1) + + # Get all UTXOs up front to ensure none of the transactions spend from each other, as that may + # change their effective feerate and thus the order in which they are selected for eviction. + confirmed_utxos = [ephemeral_miniwallet.get_utxo(confirmed_only=True) for _ in range(num_of_batches * tx_batch_size + 1)] + assert_equal(len(confirmed_utxos), num_of_batches * tx_batch_size + 1) + + test_framework.log.debug("Create a mempool tx that will be evicted") + tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer( + from_node=node, utxo_to_spend=confirmed_utxos.pop(0), fee_rate=relayfee)["txid"] + + # Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool + # The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB) + # by 130 should result in a fee that corresponds to 2x of that fee rate + base_fee = relayfee * 130 + + test_framework.log.debug("Fill up the mempool with txs with higher fee rate") + with node.assert_debug_log(["rolling minimum fee bumped"]): + for batch_of_txid in range(num_of_batches): + fee = (batch_of_txid + 1) * base_fee + utxos = confirmed_utxos[:tx_batch_size] + create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos) + del confirmed_utxos[:tx_batch_size] + + test_framework.log.debug("The tx should be evicted by now") + # The number of transactions created should be greater than the ones present in the mempool + assert_greater_than(tx_batch_size * num_of_batches, len(node.getrawmempool())) + # Initial tx created should not be present in the mempool anymore as it had a lower fee rate + assert tx_to_be_evicted_id not in node.getrawmempool() + + test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee") + assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) + assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 67e0be5280..d228bd8991 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -490,7 +490,7 @@ class TestNode(): self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager - def wait_for_debug_log(self, expected_msgs, timeout=60): + def busy_wait_for_debug_log(self, expected_msgs, timeout=60): """ Block until we see a particular debug log message fragment or until we exceed the timeout. Return: diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 0de09b6440..c5b69a3954 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -496,65 +496,6 @@ def check_node_connections(*, node, num_in, num_out): assert_equal(info["connections_in"], num_in) assert_equal(info["connections_out"], num_out) -def fill_mempool(test_framework, node, miniwallet): - """Fill mempool until eviction. - - Allows for simpler testing of scenarios with floating mempoolminfee > minrelay - Requires -datacarriersize=100000 and - -maxmempool=5. - It will not ensure mempools become synced as it - is based on a single node and assumes -minrelaytxfee - is 1 sat/vbyte. - To avoid unintentional tx dependencies, it is recommended to use separate miniwallets for - mempool filling vs transactions in tests. - """ - test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises") - txouts = gen_return_txouts() - relayfee = node.getnetworkinfo()['relayfee'] - - assert_equal(relayfee, Decimal('0.00001000')) - - tx_batch_size = 1 - num_of_batches = 75 - # Generate UTXOs to flood the mempool - # 1 to create a tx initially that will be evicted from the mempool later - # 75 transactions each with a fee rate higher than the previous one - test_framework.generate(miniwallet, 1 + (num_of_batches * tx_batch_size)) - - # Mine COINBASE_MATURITY - 1 blocks so that the UTXOs are allowed to be spent - test_framework.generate(node, 100 - 1) - - # Get all UTXOs up front to ensure none of the transactions spend from each other, as that may - # change their effective feerate and thus the order in which they are selected for eviction. - confirmed_utxos = [miniwallet.get_utxo(confirmed_only=True) for _ in range(num_of_batches * tx_batch_size + 1)] - assert_equal(len(confirmed_utxos), num_of_batches * tx_batch_size + 1) - - test_framework.log.debug("Create a mempool tx that will be evicted") - tx_to_be_evicted_id = miniwallet.send_self_transfer(from_node=node, utxo_to_spend=confirmed_utxos[0], fee_rate=relayfee)["txid"] - del confirmed_utxos[0] - - # Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool - # The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB) - # by 130 should result in a fee that corresponds to 2x of that fee rate - base_fee = relayfee * 130 - - test_framework.log.debug("Fill up the mempool with txs with higher fee rate") - with node.assert_debug_log(["rolling minimum fee bumped"]): - for batch_of_txid in range(num_of_batches): - fee = (batch_of_txid + 1) * base_fee - utxos = confirmed_utxos[:tx_batch_size] - create_lots_of_big_transactions(miniwallet, node, fee, tx_batch_size, txouts, utxos) - del confirmed_utxos[:tx_batch_size] - - test_framework.log.debug("The tx should be evicted by now") - # The number of transactions created should be greater than the ones present in the mempool - assert_greater_than(tx_batch_size * num_of_batches, len(node.getrawmempool())) - # Initial tx created should not be present in the mempool anymore as it had a lower fee rate - assert tx_to_be_evicted_id not in node.getrawmempool() - - test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee") - assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) - assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) # Transaction/Block functions ############################# diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 470ed08ed4..4433cbcc55 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -32,6 +32,7 @@ from test_framework.messages import ( CTxIn, CTxInWitness, CTxOut, + hash256, ) from test_framework.script import ( CScript, @@ -65,7 +66,10 @@ class MiniWalletMode(Enum): However, if the transactions need to be modified by the user (e.g. prepending scriptSig for testing opcodes that are activated by a soft-fork), or the txs should contain an actual signature, the raw modes RAW_OP_TRUE and RAW_P2PK - can be useful. Summary of modes: + can be useful. In order to avoid mixing of UTXOs between different MiniWallet + instances, a tag name can be passed to the default mode, to create different + output scripts. Note that the UTXOs from the pre-generated test chain can + only be spent if no tag is passed. Summary of modes: | output | | tx is | can modify | needs mode | description | address | standard | scriptSig | signing @@ -80,22 +84,25 @@ class MiniWalletMode(Enum): class MiniWallet: - def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE): + def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE, tag_name=None): self._test_node = test_node self._utxos = [] self._mode = mode assert isinstance(mode, MiniWalletMode) if mode == MiniWalletMode.RAW_OP_TRUE: + assert tag_name is None self._scriptPubKey = bytes(CScript([OP_TRUE])) elif mode == MiniWalletMode.RAW_P2PK: # use simple deterministic private key (k=1) + assert tag_name is None self._priv_key = ECKey() self._priv_key.set((1).to_bytes(32, 'big'), True) pub_key = self._priv_key.get_pubkey() self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes()) elif mode == MiniWalletMode.ADDRESS_OP_TRUE: - self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true() + internal_key = None if tag_name is None else hash256(tag_name.encode()) + self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true(internal_key) self._scriptPubKey = address_to_scriptpubkey(self._address) # When the pre-mined test framework chain is used, it contains coinbase diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 5ccb9d9496..690ab64c83 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -285,6 +285,7 @@ BASE_SCRIPTS = [ 'p2p_leak_tx.py --v1transport', 'p2p_leak_tx.py --v2transport', 'p2p_eviction.py', + 'p2p_outbound_eviction.py', 'p2p_ibd_stalling.py --v1transport', 'p2p_ibd_stalling.py --v2transport', 'p2p_net_deadlock.py --v1transport', |