diff options
Diffstat (limited to 'src')
168 files changed, 3369 insertions, 2719 deletions
diff --git a/src/.bear-tidy-config b/src/.bear-tidy-config new file mode 100644 index 0000000000..111ef6ee44 --- /dev/null +++ b/src/.bear-tidy-config @@ -0,0 +1,15 @@ +{ + "output": { + "content": { + "include_only_existing_source": true, + "paths_to_include": [], + "paths_to_exclude": [ + "src/leveldb" + ] + }, + "format": { + "command_as_array": true, + "drop_output_field": false + } + } +} diff --git a/src/.clang-tidy b/src/.clang-tidy index 27616ad072..b1f543f610 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -1,2 +1,11 @@ -Checks: '-*,bugprone-argument-comment' -WarningsAsErrors: bugprone-argument-comment +Checks: ' +-*, +bugprone-argument-comment, +modernize-use-nullptr, +readability-redundant-declaration, +' +WarningsAsErrors: ' +bugprone-argument-comment, +modernize-use-nullptr, +readability-redundant-declaration, +' diff --git a/src/Makefile.am b/src/Makefile.am index 476ff0a6c5..357e562c69 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,14 +15,23 @@ AM_LIBTOOLFLAGS = --preserve-dup-deps PTHREAD_FLAGS = $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) EXTRA_LIBRARIES = +lib_LTLIBRARIES = +noinst_LTLIBRARIES = + +bin_PROGRAMS = +noinst_PROGRAMS = +TESTS = +BENCHMARKS = + BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) LIBBITCOIN_NODE=libbitcoin_node.a LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a +LIBBITCOIN_KERNEL=libbitcoin_kernel.a LIBBITCOIN_UTIL=libbitcoin_util.a -LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.a +LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.la LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la @@ -32,28 +41,32 @@ endif if BUILD_BITCOIN_LIBS LIBBITCOINCONSENSUS=libbitcoinconsensus.la endif +if BUILD_BITCOIN_KERNEL_LIB +LIBBITCOINKERNEL=libbitcoinkernel.la +endif if ENABLE_WALLET LIBBITCOIN_WALLET=libbitcoin_wallet.a LIBBITCOIN_WALLET_TOOL=libbitcoin_wallet_tool.a endif -LIBBITCOIN_CRYPTO= $(LIBBITCOIN_CRYPTO_BASE) +LIBBITCOIN_CRYPTO = $(LIBBITCOIN_CRYPTO_BASE) if ENABLE_SSE41 -LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.a +LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SSE41) endif if ENABLE_AVX2 -LIBBITCOIN_CRYPTO_AVX2 = crypto/libbitcoin_crypto_avx2.a +LIBBITCOIN_CRYPTO_AVX2 = crypto/libbitcoin_crypto_avx2.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_AVX2) endif if ENABLE_X86_SHANI -LIBBITCOIN_CRYPTO_X86_SHANI = crypto/libbitcoin_crypto_x86_shani.a +LIBBITCOIN_CRYPTO_X86_SHANI = crypto/libbitcoin_crypto_x86_shani.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_X86_SHANI) endif if ENABLE_ARM_SHANI -LIBBITCOIN_CRYPTO_ARM_SHANI = crypto/libbitcoin_crypto_arm_shani.a +LIBBITCOIN_CRYPTO_ARM_SHANI = crypto/libbitcoin_crypto_arm_shani.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_ARM_SHANI) endif +noinst_LTLIBRARIES += $(LIBBITCOIN_CRYPTO) $(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) @@ -61,7 +74,6 @@ $(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(w # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: EXTRA_LIBRARIES += \ - $(LIBBITCOIN_CRYPTO) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_CONSENSUS) \ @@ -72,14 +84,6 @@ EXTRA_LIBRARIES += \ $(LIBBITCOIN_WALLET_TOOL) \ $(LIBBITCOIN_ZMQ) -lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS) -noinst_LTLIBRARIES = - -bin_PROGRAMS = -noinst_PROGRAMS = -TESTS = -BENCHMARKS = - if BUILD_BITCOIND bin_PROGRAMS += bitcoind endif @@ -271,6 +275,7 @@ BITCOIN_CORE_H = \ util/spanparsing.h \ util/string.h \ util/syscall_sandbox.h \ + util/syserror.h \ util/system.h \ util/thread.h \ util/threadnames.h \ @@ -375,13 +380,16 @@ libbitcoin_node_a_SOURCES = \ pow.cpp \ rest.cpp \ rpc/blockchain.cpp \ + rpc/fees.cpp \ rpc/mempool.cpp \ rpc/mining.cpp \ - rpc/misc.cpp \ + rpc/node.cpp \ rpc/net.cpp \ + rpc/output_script.cpp \ rpc/rawtransaction.cpp \ rpc/server.cpp \ rpc/server_util.cpp \ + rpc/signmessage.cpp \ rpc/txoutproof.cpp \ script/sigcache.cpp \ shutdown.cpp \ @@ -464,9 +472,16 @@ libbitcoin_wallet_tool_a_SOURCES = \ $(BITCOIN_CORE_H) # crypto primitives library -crypto_libbitcoin_crypto_base_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_base_a_SOURCES = \ +crypto_libbitcoin_crypto_base_la_CPPFLAGS = $(AM_CPPFLAGS) + +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +crypto_libbitcoin_crypto_base_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_base_la_LDFLAGS = $(AM_LDFLAGS) -static + +crypto_libbitcoin_crypto_base_la_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ crypto/chacha_poly_aead.h \ @@ -498,32 +513,44 @@ crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/siphash.h if USE_ASM -crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp +crypto_libbitcoin_crypto_base_la_SOURCES += crypto/sha256_sse4.cpp endif -crypto_libbitcoin_crypto_sse41_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_sse41_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_sse41_a_CXXFLAGS += $(SSE41_CXXFLAGS) -crypto_libbitcoin_crypto_sse41_a_CPPFLAGS += -DENABLE_SSE41 -crypto_libbitcoin_crypto_sse41_a_SOURCES = crypto/sha256_sse41.cpp - -crypto_libbitcoin_crypto_avx2_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_avx2_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_avx2_a_CXXFLAGS += $(AVX2_CXXFLAGS) -crypto_libbitcoin_crypto_avx2_a_CPPFLAGS += -DENABLE_AVX2 -crypto_libbitcoin_crypto_avx2_a_SOURCES = crypto/sha256_avx2.cpp - -crypto_libbitcoin_crypto_x86_shani_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CXXFLAGS += $(X86_SHANI_CXXFLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CPPFLAGS += -DENABLE_X86_SHANI -crypto_libbitcoin_crypto_x86_shani_a_SOURCES = crypto/sha256_x86_shani.cpp - -crypto_libbitcoin_crypto_arm_shani_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CXXFLAGS += $(ARM_SHANI_CXXFLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CPPFLAGS += -DENABLE_ARM_SHANI -crypto_libbitcoin_crypto_arm_shani_a_SOURCES = crypto/sha256_arm_shani.cpp +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_sse41_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_sse41_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_sse41_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_sse41_la_CXXFLAGS += $(SSE41_CXXFLAGS) +crypto_libbitcoin_crypto_sse41_la_CPPFLAGS += -DENABLE_SSE41 +crypto_libbitcoin_crypto_sse41_la_SOURCES = crypto/sha256_sse41.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_avx2_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_avx2_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_avx2_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_avx2_la_CXXFLAGS += $(AVX2_CXXFLAGS) +crypto_libbitcoin_crypto_avx2_la_CPPFLAGS += -DENABLE_AVX2 +crypto_libbitcoin_crypto_avx2_la_SOURCES = crypto/sha256_avx2.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_x86_shani_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_x86_shani_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_x86_shani_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_x86_shani_la_CXXFLAGS += $(X86_SHANI_CXXFLAGS) +crypto_libbitcoin_crypto_x86_shani_la_CPPFLAGS += -DENABLE_X86_SHANI +crypto_libbitcoin_crypto_x86_shani_la_SOURCES = crypto/sha256_x86_shani.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_arm_shani_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_arm_shani_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_arm_shani_la_CXXFLAGS += $(ARM_SHANI_CXXFLAGS) +crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS += -DENABLE_ARM_SHANI +crypto_libbitcoin_crypto_arm_shani_la_SOURCES = crypto/sha256_arm_shani.cpp # consensus: shared between all executables that validate any consensus rules. libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) @@ -631,6 +658,7 @@ libbitcoin_util_a_SOURCES = \ util/getuniquepath.cpp \ util/hasher.cpp \ util/sock.cpp \ + util/syserror.cpp \ util/system.cpp \ util/message.cpp \ util/moneystr.cpp \ @@ -683,7 +711,6 @@ bitcoin_bin_ldadd = \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) @@ -782,8 +809,48 @@ bitcoin_util_LDADD = \ # # bitcoin-chainstate binary # -bitcoin_chainstate_SOURCES = \ - bitcoin-chainstate.cpp \ +bitcoin_chainstate_SOURCES = bitcoin-chainstate.cpp +bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) + +# $(LIBTOOL_APP_LDFLAGS) deliberately omitted here so that we can test linking +# bitcoin-chainstate against libbitcoinkernel as a shared or static library by +# setting --{en,dis}able-shared. +bitcoin_chainstate_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(PTHREAD_FLAGS) +bitcoin_chainstate_LDADD = $(LIBBITCOINKERNEL) +# + +# bitcoinkernel library # +if BUILD_BITCOIN_KERNEL_LIB +lib_LTLIBRARIES += $(LIBBITCOINKERNEL) + +libbitcoinkernel_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) $(PTHREAD_FLAGS) +libbitcoinkernel_la_LIBADD = $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) +libbitcoinkernel_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) + +# libbitcoinkernel requires default symbol visibility, explicitly specify that +# here so that things still work even when user configures with +# --enable-reduce-exports +# +# Note this is a quick hack that will be removed as we incrementally define what +# to export from the library. +libbitcoinkernel_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -fvisibility=default + +# TODO: For now, Specify -static in both CXXFLAGS and LDFLAGS when building for +# windows targets so libtool will only build a static version of this +# library. There are unresolved problems when building dll's for mingw-w64 +# and attempting to statically embed libstdc++, libpthread, etc. +if TARGET_WINDOWS +libbitcoinkernel_la_LDFLAGS += -static +libbitcoinkernel_la_CXXFLAGS += -static +endif + +# TODO: libbitcoinkernel is a work in progress consensus engine library, as more +# and more modules are decoupled from the consensus engine, this list will +# shrink to only those which are absolutely necessary. For example, things +# like index/*.cpp will be removed. +libbitcoinkernel_la_SOURCES = \ + kernel/bitcoinkernel.cpp \ arith_uint256.cpp \ blockfilter.cpp \ chain.cpp \ @@ -852,7 +919,9 @@ bitcoin_chainstate_SOURCES = \ util/serfloat.cpp \ util/settings.cpp \ util/strencodings.cpp \ + util/string.cpp \ util/syscall_sandbox.cpp \ + util/syserror.cpp \ util/system.cpp \ util/thread.cpp \ util/threadnames.cpp \ @@ -862,26 +931,19 @@ bitcoin_chainstate_SOURCES = \ validationinterface.cpp \ versionbits.cpp \ warnings.cpp -bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -bitcoin_chainstate_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) -bitcoin_chainstate_LDADD = \ - $(LIBBITCOIN_CRYPTO) \ - $(LIBUNIVALUE) \ - $(LIBSECP256K1) \ - $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ - $(LIBMEMENV) # Required for obj/build.h to be generated first. # More details: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html -bitcoin_chainstate-clientversion.$(OBJEXT): obj/build.h +libbitcoinkernel_la-clientversion.l$(OBJEXT): obj/build.h +endif # BUILD_BITCOIN_KERNEL_LIB # # bitcoinconsensus library # if BUILD_BITCOIN_LIBS +lib_LTLIBRARIES += $(LIBBITCOINCONSENSUS) + include_HEADERS = script/bitcoinconsensus.h -libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) +libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_la_SOURCES) $(libbitcoin_consensus_a_SOURCES) libbitcoinconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) libbitcoinconsensus_la_LIBADD = $(LIBSECP256K1) diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 58a09cd4a4..532f668668 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -44,6 +44,7 @@ bench_bench_bitcoin_SOURCES = \ bench/rollingbloom.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ + bench/strencodings.cpp \ bench/util_time.cpp \ bench/verify_script.cpp @@ -52,15 +53,14 @@ nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES) bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ bench_bench_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bench_bench_bitcoin_LDADD = \ + $(LIBTEST_UTIL) \ $(LIBBITCOIN_NODE) \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ - $(LIBTEST_UTIL) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) \ $(LIBUNIVALUE) \ diff --git a/src/Makefile.crc32c.include b/src/Makefile.crc32c.include index 3cbe71792c..c4dd84991d 100644 --- a/src/Makefile.crc32c.include +++ b/src/Makefile.crc32c.include @@ -2,10 +2,9 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -LIBCRC32C_INT = crc32c/libcrc32c.a -LIBLEVELDB_SSE42_INT = leveldb/libleveldb_sse42.a +LIBCRC32C_INT = crc32c/libcrc32c.la -EXTRA_LIBRARIES += $(LIBCRC32C_INT) +noinst_LTLIBRARIES += $(LIBCRC32C_INT) LIBCRC32C = $(LIBCRC32C_INT) @@ -34,41 +33,49 @@ else CRC32C_CPPFLAGS_INT += -DBYTE_ORDER_BIG_ENDIAN=0 endif -crc32c_libcrc32c_a_CPPFLAGS = $(AM_CPPFLAGS) $(CRC32C_CPPFLAGS_INT) $(CRC32C_CPPFLAGS) -crc32c_libcrc32c_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) - -crc32c_libcrc32c_a_SOURCES = -crc32c_libcrc32c_a_SOURCES += crc32c/include/crc32c/crc32c.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_arm64.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_arm64_check.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_internal.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_prefetch.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_read_le.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_round_up.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_sse42_check.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_sse42.h - -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c.cc -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_portable.cc +crc32c_libcrc32c_la_CPPFLAGS = $(AM_CPPFLAGS) $(CRC32C_CPPFLAGS_INT) $(CRC32C_CPPFLAGS) + +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +crc32c_libcrc32c_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crc32c_libcrc32c_la_LDFLAGS = $(AM_LDFLAGS) -static + +crc32c_libcrc32c_la_SOURCES = +crc32c_libcrc32c_la_SOURCES += crc32c/include/crc32c/crc32c.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_arm64.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_arm64_check.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_internal.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_prefetch.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_read_le.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_round_up.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_sse42_check.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_sse42.h + +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c.cc +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_portable.cc if ENABLE_SSE42 -LIBCRC32C_SSE42_INT = crc32c/libcrc32c_sse42.a -EXTRA_LIBRARIES += $(LIBCRC32C_SSE42_INT) +LIBCRC32C_SSE42_INT = crc32c/libcrc32c_sse42.la +noinst_LTLIBRARIES += $(LIBCRC32C_SSE42_INT) LIBCRC32C += $(LIBCRC32C_SSE42_INT) -crc32c_libcrc32c_sse42_a_CPPFLAGS = $(crc32c_libcrc32c_a_CPPFLAGS) -crc32c_libcrc32c_sse42_a_CXXFLAGS = $(crc32c_libcrc32c_a_CXXFLAGS) $(SSE42_CXXFLAGS) +crc32c_libcrc32c_sse42_la_CPPFLAGS = $(crc32c_libcrc32c_la_CPPFLAGS) +crc32c_libcrc32c_sse42_la_CXXFLAGS = $(crc32c_libcrc32c_la_CXXFLAGS) $(SSE42_CXXFLAGS) +crc32c_libcrc32c_sse42_la_LDFLAGS = $(crc32c_libcrc32c_la_LDFLAGS) -crc32c_libcrc32c_sse42_a_SOURCES = crc32c/src/crc32c_sse42.cc +crc32c_libcrc32c_sse42_la_SOURCES = crc32c/src/crc32c_sse42.cc endif if ENABLE_ARM_CRC -LIBCRC32C_ARM_CRC_INT = crc32c/libcrc32c_arm_crc.a -EXTRA_LIBRARIES += $(LIBCRC32C_ARM_CRC_INT) +LIBCRC32C_ARM_CRC_INT = crc32c/libcrc32c_arm_crc.la +noinst_LTLIBRARIES += $(LIBCRC32C_ARM_CRC_INT) LIBCRC32C += $(LIBCRC32C_ARM_CRC_INT) -crc32c_libcrc32c_arm_crc_a_CPPFLAGS = $(crc32c_libcrc32c_a_CPPFLAGS) -crc32c_libcrc32c_arm_crc_a_CXXFLAGS = $(crc32c_libcrc32c_a_CXXFLAGS) $(ARM_CRC_CXXFLAGS) +crc32c_libcrc32c_arm_crc_la_CPPFLAGS = $(crc32c_libcrc32c_la_CPPFLAGS) +crc32c_libcrc32c_arm_crc_la_CXXFLAGS = $(crc32c_libcrc32c_la_CXXFLAGS) $(ARM_CRC_CXXFLAGS) +crc32c_libcrc32c_arm_crc_la_LDFLAGS = $(crc32c_libcrc32c_la_LDFLAGS) -crc32c_libcrc32c_arm_crc_a_SOURCES = crc32c/src/crc32c_arm64.cc +crc32c_libcrc32c_arm_crc_la_SOURCES = crc32c/src/crc32c_arm64.cc endif diff --git a/src/Makefile.leveldb.include b/src/Makefile.leveldb.include index 3bec92482a..066f8940c5 100644 --- a/src/Makefile.leveldb.include +++ b/src/Makefile.leveldb.include @@ -2,11 +2,11 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -LIBLEVELDB_INT = leveldb/libleveldb.a -LIBMEMENV_INT = leveldb/libmemenv.a +LIBLEVELDB_INT = leveldb/libleveldb.la +LIBMEMENV_INT = leveldb/libmemenv.la -EXTRA_LIBRARIES += $(LIBLEVELDB_INT) -EXTRA_LIBRARIES += $(LIBMEMENV_INT) +noinst_LTLIBRARIES += $(LIBLEVELDB_INT) +noinst_LTLIBRARIES += $(LIBMEMENV_INT) LIBLEVELDB = $(LIBLEVELDB_INT) $(LIBCRC32C) LIBMEMENV = $(LIBMEMENV_INT) @@ -37,111 +37,118 @@ else LEVELDB_CPPFLAGS_INT += -DLEVELDB_PLATFORM_POSIX endif -leveldb_libleveldb_a_CPPFLAGS = $(AM_CPPFLAGS) $(LEVELDB_CPPFLAGS_INT) $(LEVELDB_CPPFLAGS) -leveldb_libleveldb_a_CXXFLAGS = $(filter-out -Wconditional-uninitialized -Werror=conditional-uninitialized -Wsuggest-override -Werror=suggest-override, $(AM_CXXFLAGS)) $(PIE_FLAGS) +leveldb_libleveldb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LEVELDB_CPPFLAGS_INT) $(LEVELDB_CPPFLAGS) -leveldb_libleveldb_a_SOURCES= -leveldb_libleveldb_a_SOURCES += leveldb/port/port_stdcxx.h -leveldb_libleveldb_a_SOURCES += leveldb/port/port.h -leveldb_libleveldb_a_SOURCES += leveldb/port/thread_annotations.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/db.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/options.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/comparator.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/filter_policy.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/slice.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/table_builder.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/env.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/export.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/c.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/iterator.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/cache.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/dumpfile.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/table.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/write_batch.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/status.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_format.h -leveldb_libleveldb_a_SOURCES += leveldb/db/memtable.h -leveldb_libleveldb_a_SOURCES += leveldb/db/version_set.h -leveldb_libleveldb_a_SOURCES += leveldb/db/write_batch_internal.h -leveldb_libleveldb_a_SOURCES += leveldb/db/filename.h -leveldb_libleveldb_a_SOURCES += leveldb/db/version_edit.h -leveldb_libleveldb_a_SOURCES += leveldb/db/dbformat.h -leveldb_libleveldb_a_SOURCES += leveldb/db/builder.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_writer.h -leveldb_libleveldb_a_SOURCES += leveldb/db/db_iter.h -leveldb_libleveldb_a_SOURCES += leveldb/db/skiplist.h -leveldb_libleveldb_a_SOURCES += leveldb/db/db_impl.h -leveldb_libleveldb_a_SOURCES += leveldb/db/table_cache.h -leveldb_libleveldb_a_SOURCES += leveldb/db/snapshot.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_reader.h -leveldb_libleveldb_a_SOURCES += leveldb/table/filter_block.h -leveldb_libleveldb_a_SOURCES += leveldb/table/block_builder.h -leveldb_libleveldb_a_SOURCES += leveldb/table/block.h -leveldb_libleveldb_a_SOURCES += leveldb/table/two_level_iterator.h -leveldb_libleveldb_a_SOURCES += leveldb/table/merger.h -leveldb_libleveldb_a_SOURCES += leveldb/table/format.h -leveldb_libleveldb_a_SOURCES += leveldb/table/iterator_wrapper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/crc32c.h -leveldb_libleveldb_a_SOURCES += leveldb/util/env_posix_test_helper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/env_windows_test_helper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/arena.h -leveldb_libleveldb_a_SOURCES += leveldb/util/random.h -leveldb_libleveldb_a_SOURCES += leveldb/util/posix_logger.h -leveldb_libleveldb_a_SOURCES += leveldb/util/hash.h -leveldb_libleveldb_a_SOURCES += leveldb/util/histogram.h -leveldb_libleveldb_a_SOURCES += leveldb/util/coding.h -leveldb_libleveldb_a_SOURCES += leveldb/util/testutil.h -leveldb_libleveldb_a_SOURCES += leveldb/util/mutexlock.h -leveldb_libleveldb_a_SOURCES += leveldb/util/logging.h -leveldb_libleveldb_a_SOURCES += leveldb/util/no_destructor.h -leveldb_libleveldb_a_SOURCES += leveldb/util/testharness.h -leveldb_libleveldb_a_SOURCES += leveldb/util/windows_logger.h +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +leveldb_libleveldb_la_CXXFLAGS = $(filter-out -Wconditional-uninitialized -Werror=conditional-uninitialized -Wsuggest-override -Werror=suggest-override, $(AM_CXXFLAGS)) $(PIE_FLAGS) -static +leveldb_libleveldb_la_LDFLAGS = $(AM_LDFLAGS) -static -leveldb_libleveldb_a_SOURCES += leveldb/db/builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/c.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/dbformat.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/db_impl.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/db_iter.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/dumpfile.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/filename.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/log_reader.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/log_writer.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/memtable.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/repair.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/table_cache.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/version_edit.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/version_set.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/write_batch.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/block_builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/block.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/filter_block.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/format.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/iterator.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/merger.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/table_builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/table.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/two_level_iterator.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/arena.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/bloom.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/cache.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/coding.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/comparator.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/crc32c.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/env.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/filter_policy.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/hash.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/histogram.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/logging.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/options.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/status.cc +leveldb_libleveldb_la_SOURCES= +leveldb_libleveldb_la_SOURCES += leveldb/port/port_stdcxx.h +leveldb_libleveldb_la_SOURCES += leveldb/port/port.h +leveldb_libleveldb_la_SOURCES += leveldb/port/thread_annotations.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/db.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/options.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/comparator.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/filter_policy.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/slice.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/table_builder.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/env.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/export.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/c.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/iterator.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/cache.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/dumpfile.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/table.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/write_batch.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/status.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_format.h +leveldb_libleveldb_la_SOURCES += leveldb/db/memtable.h +leveldb_libleveldb_la_SOURCES += leveldb/db/version_set.h +leveldb_libleveldb_la_SOURCES += leveldb/db/write_batch_internal.h +leveldb_libleveldb_la_SOURCES += leveldb/db/filename.h +leveldb_libleveldb_la_SOURCES += leveldb/db/version_edit.h +leveldb_libleveldb_la_SOURCES += leveldb/db/dbformat.h +leveldb_libleveldb_la_SOURCES += leveldb/db/builder.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_writer.h +leveldb_libleveldb_la_SOURCES += leveldb/db/db_iter.h +leveldb_libleveldb_la_SOURCES += leveldb/db/skiplist.h +leveldb_libleveldb_la_SOURCES += leveldb/db/db_impl.h +leveldb_libleveldb_la_SOURCES += leveldb/db/table_cache.h +leveldb_libleveldb_la_SOURCES += leveldb/db/snapshot.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_reader.h +leveldb_libleveldb_la_SOURCES += leveldb/table/filter_block.h +leveldb_libleveldb_la_SOURCES += leveldb/table/block_builder.h +leveldb_libleveldb_la_SOURCES += leveldb/table/block.h +leveldb_libleveldb_la_SOURCES += leveldb/table/two_level_iterator.h +leveldb_libleveldb_la_SOURCES += leveldb/table/merger.h +leveldb_libleveldb_la_SOURCES += leveldb/table/format.h +leveldb_libleveldb_la_SOURCES += leveldb/table/iterator_wrapper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/crc32c.h +leveldb_libleveldb_la_SOURCES += leveldb/util/env_posix_test_helper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/env_windows_test_helper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/arena.h +leveldb_libleveldb_la_SOURCES += leveldb/util/random.h +leveldb_libleveldb_la_SOURCES += leveldb/util/posix_logger.h +leveldb_libleveldb_la_SOURCES += leveldb/util/hash.h +leveldb_libleveldb_la_SOURCES += leveldb/util/histogram.h +leveldb_libleveldb_la_SOURCES += leveldb/util/coding.h +leveldb_libleveldb_la_SOURCES += leveldb/util/testutil.h +leveldb_libleveldb_la_SOURCES += leveldb/util/mutexlock.h +leveldb_libleveldb_la_SOURCES += leveldb/util/logging.h +leveldb_libleveldb_la_SOURCES += leveldb/util/no_destructor.h +leveldb_libleveldb_la_SOURCES += leveldb/util/testharness.h +leveldb_libleveldb_la_SOURCES += leveldb/util/windows_logger.h + +leveldb_libleveldb_la_SOURCES += leveldb/db/builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/c.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/dbformat.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/db_impl.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/db_iter.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/dumpfile.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/filename.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/log_reader.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/log_writer.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/memtable.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/repair.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/table_cache.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/version_edit.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/version_set.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/write_batch.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/block_builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/block.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/filter_block.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/format.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/iterator.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/merger.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/table_builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/table.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/two_level_iterator.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/arena.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/bloom.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/cache.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/coding.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/comparator.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/crc32c.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/filter_policy.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/hash.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/histogram.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/logging.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/options.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/status.cc if TARGET_WINDOWS -leveldb_libleveldb_a_SOURCES += leveldb/util/env_windows.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env_windows.cc else -leveldb_libleveldb_a_SOURCES += leveldb/util/env_posix.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env_posix.cc endif -leveldb_libmemenv_a_CPPFLAGS = $(leveldb_libleveldb_a_CPPFLAGS) -leveldb_libmemenv_a_CXXFLAGS = $(leveldb_libleveldb_a_CXXFLAGS) -leveldb_libmemenv_a_SOURCES = leveldb/helpers/memenv/memenv.cc -leveldb_libmemenv_a_SOURCES += leveldb/helpers/memenv/memenv.h +leveldb_libmemenv_la_CPPFLAGS = $(leveldb_libleveldb_la_CPPFLAGS) +leveldb_libmemenv_la_CXXFLAGS = $(leveldb_libleveldb_la_CXXFLAGS) +leveldb_libmemenv_la_LDFLAGS = $(leveldb_libleveldb_la_LDFLAGS) +leveldb_libmemenv_la_SOURCES = leveldb/helpers/memenv/memenv.cc +leveldb_libmemenv_la_SOURCES += leveldb/helpers/memenv/memenv.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3491f07ee0..72037b3db2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -330,7 +330,7 @@ endif if ENABLE_ZMQ bitcoin_qt_ldadd += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif -bitcoin_qt_ldadd += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ +bitcoin_qt_ldadd += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) bitcoin_qt_ldflags = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 29c322fbc2..fa822f2954 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -55,7 +55,7 @@ if ENABLE_ZMQ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(QT_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) \ + $(LIBMEMENV) $(QT_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) \ $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 96a9a74802..02a3f9ae7d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -47,7 +47,6 @@ FUZZ_SUITE_LD_COMMON = \ $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) \ $(MINISKETCH_LIBS) \ @@ -110,6 +109,7 @@ BITCOIN_TESTS =\ test/net_peer_eviction_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/orphanage_tests.cpp \ test/pmt_tests.cpp \ test/policy_fee_tests.cpp \ test/policyestimator_tests.cpp \ @@ -204,7 +204,7 @@ test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif test_test_bitcoin_LDADD += $(LIBBITCOIN_NODE) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS) + $(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS) test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_bitcoin_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index 9574454fd2..8922dda3ad 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -18,8 +18,3 @@ libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/fuzz/util.cpp \ $(TEST_FUZZ_H) - -LIBTEST_FUZZ += $(LIBBITCOIN_NODE) -LIBTEST_FUZZ += $(LIBBITCOIN_COMMON) -LIBTEST_FUZZ += $(LIBBITCOIN_UTIL) -LIBTEST_FUZZ += $(LIBBITCOIN_CRYPTO_BASE) diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 92cb8a5ce6..9306bb6fcc 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -34,8 +34,3 @@ libtest_util_a_SOURCES = \ test/util/validation.cpp \ test/util/wallet.cpp \ $(TEST_UTIL_H) - -LIBTEST_UTIL += $(LIBBITCOIN_NODE) -LIBTEST_UTIL += $(LIBBITCOIN_COMMON) -LIBTEST_UTIL += $(LIBBITCOIN_UTIL) -LIBTEST_UTIL += $(LIBBITCOIN_CRYPTO_BASE) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 299cbdcf6a..31f8eadf98 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -53,7 +53,7 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data std::string tmpfn = strprintf("%s.%04x", prefix, randv); // open temp output file, and associate with CAutoFile - fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn; + fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn); FILE *file = fsbridge::fopen(pathTmp, "wb"); CAutoFile fileout(file, SER_DISK, version); if (fileout.IsNull()) { diff --git a/src/base58.cpp b/src/base58.cpp index dfa2e8db55..11c1ce7397 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -126,7 +126,7 @@ std::string EncodeBase58(Span<const unsigned char> input) bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len) { - if (!ValidAsCString(str)) { + if (!ContainsNoNUL(str)) { return false; } return DecodeBase58(str.c_str(), vchRet, max_ret_len); @@ -160,7 +160,7 @@ std::string EncodeBase58Check(Span<const unsigned char> input) bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret) { - if (!ValidAsCString(str)) { + if (!ContainsNoNUL(str)) { return false; } return DecodeBase58Check(str.c_str(), vchRet, max_ret); diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index d7b8c1badc..53591f8905 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -39,7 +39,10 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) { return true; } - void swap(PrevectorJob& x){p.swap(x.p);}; + void swap(PrevectorJob& x) noexcept + { + p.swap(x.p); + }; }; CCheckQueue<PrevectorJob> queue {QUEUE_BATCH_SIZE}; // The main thread should be counted to prevent thread oversubscription, and diff --git a/src/bench/strencodings.cpp b/src/bench/strencodings.cpp new file mode 100644 index 0000000000..69b3a83cbf --- /dev/null +++ b/src/bench/strencodings.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2022 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 <bench/data.h> +#include <util/strencodings.h> + +static void HexStrBench(benchmark::Bench& bench) +{ + auto const& data = benchmark::data::block413567; + bench.batch(data.size()).unit("byte").run([&] { + auto hex = HexStr(data); + ankerl::nanobench::doNotOptimizeAway(hex); + }); +} + +BENCHMARK(HexStrBench); diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index fcbb6aacce..e5b6875a36 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -28,8 +28,6 @@ #include <functional> #include <iosfwd> -const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; - int main(int argc, char* argv[]) { // SETUP: Argument parsing and handling @@ -256,7 +254,5 @@ epilogue: } GetMainSignals().UnregisterBackgroundSignalScheduler(); - WITH_LOCK(::cs_main, UnloadBlockIndex(nullptr, chainman)); - init::UnsetGlobals(); } diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 0b40626595..580cc5839a 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -31,8 +31,6 @@ #include <memory> #include <stdio.h> -#include <boost/algorithm/string.hpp> - static bool fCreateBlank; static std::map<std::string,UniValue> registers; static const int CONTINUE_EXECUTION=-1; @@ -242,7 +240,7 @@ static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInId template <typename T> static T TrimAndParse(const std::string& int_str, const std::string& err) { - const auto parsed{ToIntegral<T>(TrimString(int_str))}; + const auto parsed{ToIntegral<T>(TrimStringView(int_str))}; if (!parsed.has_value()) { throw std::runtime_error(err + " '" + int_str + "'"); } @@ -251,8 +249,7 @@ static T TrimAndParse(const std::string& int_str, const std::string& err) static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInput) { - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); // separate TXID:VOUT in string if (vStrInputParts.size()<2) @@ -287,8 +284,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:ADDRESS - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() != 2) throw std::runtime_error("TX output missing or too many separators"); @@ -312,8 +308,7 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:PUBKEY[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() < 2 || vStrInputParts.size() > 3) throw std::runtime_error("TX output missing or too many separators"); @@ -356,8 +351,7 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:REQUIRED:NUMKEYS:PUBKEY1:PUBKEY2:....[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); // Check that there are enough parameters if (vStrInputParts.size()<3) @@ -460,8 +454,7 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& strInput) { // separate VALUE:SCRIPT[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() < 2) throw std::runtime_error("TX output missing separator"); @@ -674,7 +667,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) - ProduceSignature(keystore, MutableTransactionSignatureCreator(&mergedTx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(keystore, MutableTransactionSignatureCreator(mergedTx, i, amount, nHashType), prevPubKey, sigdata); if (amount == MAX_MONEY && !sigdata.scriptWitness.IsNull()) { throw std::runtime_error(strprintf("Missing amount for CTxOut with scriptPubKey=%s", HexStr(prevPubKey))); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 9843382682..bc063faed1 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -20,6 +20,7 @@ #include <util/check.h> #include <util/strencodings.h> #include <util/syscall_sandbox.h> +#include <util/syserror.h> #include <util/system.h> #include <util/threadnames.h> #include <util/tokenpipe.h> @@ -206,7 +207,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) } break; case -1: // Error happened. - return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno)))); + return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", SysErrorString(errno)))); default: { // Parent: wait and exit. int token = daemon_ep.TokenRead(); if (token) { // Success diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c65a816a5f..7a7c72ea25 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -10,13 +10,11 @@ #include <deploymentinfo.h> #include <hash.h> // for signet block challenge hash #include <script/interpreter.h> +#include <util/string.h> #include <util/system.h> #include <assert.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) { CMutableTransaction txNew; @@ -528,8 +526,7 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) if (!args.IsArgSet("-vbparams")) return; for (const std::string& strDeployment : args.GetArgs("-vbparams")) { - std::vector<std::string> vDeploymentParams; - boost::split(vDeploymentParams, strDeployment, boost::is_any_of(":")); + std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':'); if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) { throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height]"); } diff --git a/src/compat/byteswap.h b/src/compat/byteswap.h index 27ef1a18df..2f4232fa5c 100644 --- a/src/compat/byteswap.h +++ b/src/compat/byteswap.h @@ -9,7 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <stdint.h> +#include <cstdint> #if defined(HAVE_BYTESWAP_H) #include <byteswap.h> diff --git a/src/compat/cpuid.h b/src/compat/cpuid.h index 0877ad47d3..e78c1ce6d1 100644 --- a/src/compat/cpuid.h +++ b/src/compat/cpuid.h @@ -10,6 +10,8 @@ #include <cpuid.h> +#include <cstdint> + // We can't use cpuid.h's __get_cpuid as it does not support subleafs. void static inline GetCPUID(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) { diff --git a/src/compat/endian.h b/src/compat/endian.h index c5cf7a46cc..bdd8b84c1b 100644 --- a/src/compat/endian.h +++ b/src/compat/endian.h @@ -11,7 +11,7 @@ #include <compat/byteswap.h> -#include <stdint.h> +#include <cstdint> #if defined(HAVE_ENDIAN_H) #include <endian.h> diff --git a/src/compat/glibcxx_sanity.cpp b/src/compat/glibcxx_sanity.cpp index e6e6208e40..f2ceeeeb9c 100644 --- a/src/compat/glibcxx_sanity.cpp +++ b/src/compat/glibcxx_sanity.cpp @@ -5,6 +5,7 @@ #include <list> #include <locale> #include <stdexcept> +#include <string> namespace { diff --git a/src/compat/stdin.cpp b/src/compat/stdin.cpp index 0fc4e0fcf2..61d66e39f2 100644 --- a/src/compat/stdin.cpp +++ b/src/compat/stdin.cpp @@ -2,23 +2,19 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <compat/stdin.h> -#include <cstdio> // for fileno(), stdin +#include <cstdio> #ifdef WIN32 -#include <windows.h> // for SetStdinEcho() -#include <io.h> // for isatty() +#include <windows.h> +#include <io.h> #else -#include <termios.h> // for SetStdinEcho() -#include <unistd.h> // for SetStdinEcho(), isatty() -#include <poll.h> // for StdinReady() +#include <termios.h> +#include <unistd.h> +#include <poll.h> #endif -#include <compat/stdin.h> - // https://stackoverflow.com/questions/1413445/reading-a-password-from-stdcin void SetStdinEcho(bool enable) { diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 5738c333ce..154146f08d 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -11,6 +11,7 @@ #include <consensus/validation.h> #include <primitives/transaction.h> #include <script/interpreter.h> +#include <util/check.h> #include <util/moneystr.h> bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) @@ -74,7 +75,7 @@ std::pair<int, int64_t> CalculateSequenceLocks(const CTransaction &tx, int flags int nCoinHeight = prevHeights[txinIndex]; if (txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) { - int64_t nCoinTime = block.GetAncestor(std::max(nCoinHeight-1, 0))->GetMedianTimePast(); + const int64_t nCoinTime{Assert(block.GetAncestor(std::max(nCoinHeight - 1, 0)))->GetMedianTimePast()}; // NOTE: Subtract 1 to maintain nLockTime semantics // BIP 68 relative lock times have the semantics of calculating // the first block or time at which the transaction would be diff --git a/src/core_read.cpp b/src/core_read.cpp index 3bab5b5d98..77c516427a 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -14,9 +14,6 @@ #include <util/strencodings.h> #include <version.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - #include <algorithm> #include <string> @@ -66,12 +63,11 @@ CScript ParseScript(const std::string& s) { CScript result; - std::vector<std::string> words; - boost::algorithm::split(words, s, boost::algorithm::is_any_of(" \t\n"), boost::algorithm::token_compress_on); + std::vector<std::string> words = SplitString(s, " \t\n"); for (const std::string& w : words) { if (w.empty()) { - // Empty string, ignore. (boost::split given '' will return one word) + // Empty string, ignore. (SplitString doesn't combine multiple separators) } else if (std::all_of(w.begin(), w.end(), ::IsDigit) || (w.front() == '-' && w.size() > 1 && std::all_of(w.begin() + 1, w.end(), ::IsDigit))) { diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index f3ff4268ee..c7e12b0612 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -18,6 +18,8 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( a += b; d = rotl32(d ^ a, 8); \ c += d; b = rotl32(b ^ c, 7); +#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) + static const unsigned char sigma[] = "expand 32-byte k"; static const unsigned char tau[] = "expand 16-byte k"; @@ -119,16 +121,19 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) x13 = j13; x14 = j14; x15 = j15; - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } + + // The 20 inner ChaCha20 rounds are unrolled here for performance. + REPEAT10( + QUARTERROUND( x0, x4, x8,x12); + QUARTERROUND( x1, x5, x9,x13); + QUARTERROUND( x2, x6,x10,x14); + QUARTERROUND( x3, x7,x11,x15); + QUARTERROUND( x0, x5,x10,x15); + QUARTERROUND( x1, x6,x11,x12); + QUARTERROUND( x2, x7, x8,x13); + QUARTERROUND( x3, x4, x9,x14); + ); + x0 += j0; x1 += j1; x2 += j2; @@ -231,16 +236,19 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) x13 = j13; x14 = j14; x15 = j15; - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } + + // The 20 inner ChaCha20 rounds are unrolled here for performance. + REPEAT10( + QUARTERROUND( x0, x4, x8,x12); + QUARTERROUND( x1, x5, x9,x13); + QUARTERROUND( x2, x6,x10,x14); + QUARTERROUND( x3, x7,x11,x15); + QUARTERROUND( x0, x5,x10,x15); + QUARTERROUND( x1, x6,x11,x12); + QUARTERROUND( x2, x7, x8,x13); + QUARTERROUND( x3, x4, x9,x14); + ); + x0 += j0; x1 += j1; x2 += j2; diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 2b94ed611b..028c6ebae1 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -50,6 +50,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const "-flushwallet", "-privdb", "-walletrejectlongchains", + "-walletcrosschain", "-unsafesqlitesync", }); } diff --git a/src/flatfile.cpp b/src/flatfile.cpp index d6cada0c46..0fecf4f504 100644 --- a/src/flatfile.cpp +++ b/src/flatfile.cpp @@ -27,7 +27,7 @@ std::string FlatFilePos::ToString() const fs::path FlatFileSeq::FileName(const FlatFilePos& pos) const { - return m_dir / strprintf("%s%05u.dat", m_prefix, pos.nFile); + return m_dir / fs::u8path(strprintf("%s%05u.dat", m_prefix, pos.nFile)); } FILE* FlatFileSeq::Open(const FlatFilePos& pos, bool read_only) diff --git a/src/fs.cpp b/src/fs.cpp index 219fdee959..b61115bf01 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <fs.h> +#include <util/syserror.h> #ifndef WIN32 #include <cstring> @@ -44,7 +45,7 @@ fs::path AbsPathJoin(const fs::path& base, const fs::path& path) static std::string GetErrorReason() { - return std::strerror(errno); + return SysErrorString(errno); } FileLock::FileLock(const fs::path& file) @@ -92,11 +92,30 @@ static inline auto quoted(const std::string& s) } // Allow safe path append operations. -static inline path operator+(path p1, path p2) +static inline path operator/(path p1, path p2) { - p1 += std::move(p2); + p1 /= std::move(p2); return p1; } +static inline path operator/(path p1, const char* p2) +{ + p1 /= p2; + return p1; +} +static inline path operator+(path p1, const char* p2) +{ + p1 += p2; + return p1; +} +static inline path operator+(path p1, path::value_type p2) +{ + p1 += p2; + return p1; +} + +// Disallow unsafe path append operations. +template<typename T> static inline path operator/(path p1, T p2) = delete; +template<typename T> static inline path operator+(path p1, T p2) = delete; // Disallow implicit std::string conversion for copy_file // to avoid locale-dependent encoding on Windows. diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 5d0b59f7cb..3bf165495c 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -4,7 +4,6 @@ #include <httprpc.h> -#include <chainparams.h> #include <crypto/hmac_sha256.h> #include <httpserver.h> #include <rpc/protocol.h> @@ -12,18 +11,15 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> -#include <util/translation.h> #include <walletinitinterface.h> #include <algorithm> #include <iterator> #include <map> #include <memory> -#include <stdio.h> #include <set> #include <string> - -#include <boost/algorithm/string.hpp> +#include <vector> /** WWW-Authenticate to present with 401 Unauthorized response */ static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\""; @@ -131,8 +127,11 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna return false; if (strAuth.substr(0, 6) != "Basic ") return false; - std::string strUserPass64 = TrimString(strAuth.substr(6)); - std::string strUserPass = DecodeBase64(strUserPass64); + std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6)); + auto userpass_data = DecodeBase64(strUserPass64); + std::string strUserPass; + if (!userpass_data) return false; + strUserPass.assign(userpass_data->begin(), userpass_data->end()); if (strUserPass.find(':') != std::string::npos) strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':')); @@ -251,13 +250,14 @@ static bool InitRPCAuthentication() LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n"); strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } - if (gArgs.GetArg("-rpcauth","") != "") - { + if (gArgs.GetArg("-rpcauth", "") != "") { LogPrintf("Using rpcauth authentication.\n"); for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) { - std::vector<std::string> fields; - boost::split(fields, rpcauth, boost::is_any_of(":$")); - if (fields.size() == 3) { + std::vector<std::string> fields{SplitString(rpcauth, ':')}; + const std::vector<std::string> salt_hmac{SplitString(fields.back(), '$')}; + if (fields.size() == 2 && salt_hmac.size() == 2) { + fields.pop_back(); + fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end()); g_rpcauth.push_back(fields); } else { LogPrintf("Invalid -rpcauth argument.\n"); @@ -274,8 +274,10 @@ static bool InitRPCAuthentication() std::set<std::string>& whitelist = g_rpc_whitelist[strUser]; if (pos != std::string::npos) { std::string strWhitelist = strRPCWhitelist.substr(pos + 1); - std::set<std::string> new_whitelist; - boost::split(new_whitelist, strWhitelist, boost::is_any_of(", ")); + std::vector<std::string> whitelist_split = SplitString(strWhitelist, ", "); + std::set<std::string> new_whitelist{ + std::make_move_iterator(whitelist_split.begin()), + std::make_move_iterator(whitelist_split.end())}; if (intersect) { std::set<std::string> tmp_whitelist; std::set_intersection(new_whitelist.begin(), new_whitelist.end(), diff --git a/src/i2p.cpp b/src/i2p.cpp index ccba14d63d..e08b5461fe 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -69,12 +69,11 @@ static std::string SwapBase64(const std::string& from) static Binary DecodeI2PBase64(const std::string& i2p_b64) { const std::string& std_b64 = SwapBase64(i2p_b64); - bool invalid; - Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); - if (invalid) { + auto decoded = DecodeBase64(std_b64); + if (!decoded) { throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); } - return decoded; + return std::move(*decoded); } /** diff --git a/src/index/base.cpp b/src/index/base.cpp index 8fe30f8960..a00ae13e5c 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -18,8 +18,8 @@ using node::ReadBlockFromDisk; constexpr uint8_t DB_BEST_BLOCK{'B'}; -constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds -constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds +constexpr auto SYNC_LOG_INTERVAL{30s}; +constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s}; template <typename... Args> static void FatalError(const char* fmt, const Args&... args) @@ -65,9 +65,9 @@ bool BaseIndex::Init() LOCK(cs_main); CChain& active_chain = m_chainstate->m_chain; if (locator.IsNull()) { - m_best_block_index = nullptr; + SetBestBlockIndex(nullptr); } else { - m_best_block_index = m_chainstate->FindForkInGlobalIndex(locator); + SetBestBlockIndex(m_chainstate->FindForkInGlobalIndex(locator)); } m_synced = m_best_block_index.load() == active_chain.Tip(); if (!m_synced) { @@ -75,11 +75,7 @@ bool BaseIndex::Init() if (!m_best_block_index) { // index is not built yet // make sure we have all block data back to the genesis - const CBlockIndex* block = active_chain.Tip(); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - prune_violation = block != active_chain.Genesis(); + prune_violation = m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis(); } // in case the index has a best block set and is not fully synced // check if we have the required blocks to continue building the index @@ -134,11 +130,11 @@ void BaseIndex::ThreadSync() if (!m_synced) { auto& consensus_params = Params().GetConsensus(); - int64_t last_log_time = 0; - int64_t last_locator_write_time = 0; + std::chrono::steady_clock::time_point last_log_time{0s}; + std::chrono::steady_clock::time_point last_locator_write_time{0s}; while (true) { if (m_interrupt) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); // No need to handle errors in Commit. If it fails, the error will be already be // logged. The best way to recover is to continue, as index cannot be corrupted by // a missed commit to disk for an advanced index state. @@ -150,7 +146,7 @@ void BaseIndex::ThreadSync() LOCK(cs_main); const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain); if (!pindex_next) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); m_synced = true; // No need to handle errors in Commit. See rationale above. Commit(); @@ -164,7 +160,7 @@ void BaseIndex::ThreadSync() pindex = pindex_next; } - int64_t current_time = GetTime(); + auto current_time{std::chrono::steady_clock::now()}; if (last_log_time + SYNC_LOG_INTERVAL < current_time) { LogPrintf("Syncing %s with block chain from height %d\n", GetName(), pindex->nHeight); @@ -172,7 +168,7 @@ void BaseIndex::ThreadSync() } if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); last_locator_write_time = current_time; // No need to handle errors in Commit. See rationale above. Commit(); @@ -230,10 +226,10 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti // out of sync may be possible but a users fault. // In case we reorg beyond the pruned depth, ReadBlockFromDisk would // throw and lead to a graceful shutdown - m_best_block_index = new_tip; + SetBestBlockIndex(new_tip); if (!Commit()) { // If commit fails, revert the best block index to avoid corruption. - m_best_block_index = current_tip; + SetBestBlockIndex(current_tip); return false; } @@ -274,7 +270,7 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const } if (WriteBlock(*block, pindex)) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); } else { FatalError("%s: Failed to write block %s to index", __func__, pindex->GetBlockHash().ToString()); @@ -381,3 +377,14 @@ IndexSummary BaseIndex::GetSummary() const summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0; return summary; } + +void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) { + assert(!node::fPruneMode || AllowPrune()); + + m_best_block_index = block; + if (AllowPrune() && block) { + node::PruneLockInfo prune_lock; + prune_lock.height_first = block->nHeight; + WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock)); + } +} diff --git a/src/index/base.h b/src/index/base.h index c4a8215bc4..a8f6a18c8d 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -75,6 +75,9 @@ private: /// to a chain reorganization), the index must halt until Commit succeeds or else it could end up /// getting corrupted. bool Commit(); + + virtual bool AllowPrune() const = 0; + protected: CChainState* m_chainstate{nullptr}; @@ -103,6 +106,9 @@ protected: /// Get the name of the index for display in logs. virtual const char* GetName() const = 0; + /// Update the internal best block index as well as the prune lock. + void SetBestBlockIndex(const CBlockIndex* block); + public: /// Destructor interrupts sync thread if running and blocks until it exits. virtual ~BaseIndex(); diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index 4f99eddfd7..c92b8c7e19 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -100,7 +100,7 @@ BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type, const std::string& filter_name = BlockFilterTypeName(filter_type); if (filter_name.empty()) throw std::invalid_argument("unknown filter_type"); - fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / filter_name; + fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name); fs::create_directories(path); m_name = filter_name + " block filter index"; diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h index a049019c02..b1836fe12f 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -38,6 +38,8 @@ private: /** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */ std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache); + bool AllowPrune() const override { return true; } + protected: bool Init() override; diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index 24190ac137..6f53bb74fb 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -36,6 +36,8 @@ private: bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex); + bool AllowPrune() const override { return true; } + protected: bool Init() override; diff --git a/src/index/txindex.h b/src/index/txindex.h index 2bbc602631..ec339abaa1 100644 --- a/src/index/txindex.h +++ b/src/index/txindex.h @@ -20,6 +20,8 @@ protected: private: const std::unique_ptr<DB> m_db; + bool AllowPrune() const override { return false; } + protected: bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override; diff --git a/src/init.cpp b/src/init.cpp index dc99e78555..feb4ad52a6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -65,6 +65,7 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/syscall_sandbox.h> +#include <util/syserror.h> #include <util/system.h> #include <util/thread.h> #include <util/threadnames.h> @@ -73,6 +74,7 @@ #include <validationinterface.h> #include <walletinitinterface.h> +#include <algorithm> #include <condition_variable> #include <cstdint> #include <cstdio> @@ -90,7 +92,6 @@ #include <sys/stat.h> #endif -#include <boost/algorithm/string/replace.hpp> #include <boost/signals2/signal.hpp> #if ENABLE_ZMQ @@ -149,7 +150,7 @@ static fs::path GetPidFile(const ArgsManager& args) #endif return true; } else { - return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), std::strerror(errno))); + return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno))); } } @@ -407,7 +408,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -421,11 +422,11 @@ void SetupServerArgs(ArgsManager& argsman) -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -coinstatsindex. " + argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk. This will also rebuild active optional indexes.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead. Deactivate all optional indexes before running this.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-startupnotify=<cmd>", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -858,8 +859,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb if (args.GetIntArg("-prune", 0)) { if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) return InitError(_("Prune mode is incompatible with -txindex.")); - if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) - return InitError(_("Prune mode is incompatible with -coinstatsindex.")); if (args.GetBoolArg("-reindex-chainstate", false)) { return InitError(_("Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.")); } @@ -1033,6 +1032,19 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb return InitError(_("No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.")); } + if (args.GetBoolArg("-reindex-chainstate", false)) { + // indexes that must be deactivated to prevent index corruption, see #24630 + if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { + return InitError(_("-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.")); + } + if (g_enabled_filter_types.count(BlockFilterType::BASIC)) { + return InitError(_("-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.")); + } + if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { + return InitError(_("-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.")); + } + } + #if defined(USE_SYSCALL_SANDBOX) if (args.IsArgSet("-sandbox") && !args.IsArgNegated("-sandbox")) { const std::string sandbox_arg{args.GetArg("-sandbox", "")}; @@ -1277,19 +1289,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // as they would never get updated. if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); - assert(!node.mempool); - int check_ratio = std::min<int>(std::max<int>(args.GetIntArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); - node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio); - - 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(), - chainman, *node.mempool, ignores_incoming_txs); - RegisterValidationInterface(node.peerman.get()); - // sanitize comments per BIP-0014, format user agent and check total size std::vector<std::string> uacomments; for (const std::string& cmt : args.GetArgs("-uacomment")) { @@ -1418,8 +1417,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("* Using %.1f MiB for chain state database\n", cache_sizes.coins_db * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); - bool fLoaded = false; - while (!fLoaded && !ShutdownRequested()) { + assert(!node.mempool); + assert(!node.chainman); + const int mempool_check_ratio = std::clamp<int>(args.GetIntArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0, 1000000); + + for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { + node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), mempool_check_ratio); + + node.chainman = std::make_unique<ChainstateManager>(); + ChainstateManager& chainman = *node.chainman; + const bool fReset = fReindex; bilingual_str strLoadError; @@ -1551,6 +1558,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } + ChainstateManager& chainman = *Assert(node.chainman); + + assert(!node.peerman); + node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), + chainman, *node.mempool, ignores_incoming_txs); + RegisterValidationInterface(node.peerman.get()); + // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { if (const auto error{WITH_LOCK(cs_main, return CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db)))}) { @@ -1626,7 +1640,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) uiInterface.NotifyBlockTip_connect([block_notify](SynchronizationState sync_state, const CBlockIndex* pBlockIndex) { if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) return; std::string command = block_notify; - boost::replace_all(command, "%s", pBlockIndex->GetBlockHash().GetHex()); + ReplaceAll(command, "%s", pBlockIndex->GetBlockHash().GetHex()); std::thread t(runCommand, command); t.detach(); // thread runs free }); diff --git a/src/init/bitcoin-gui.cpp b/src/init/bitcoin-gui.cpp index e297b48718..2fa4add4e5 100644 --- a/src/init/bitcoin-gui.cpp +++ b/src/init/bitcoin-gui.cpp @@ -9,6 +9,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 511a872bc0..78bc3e5980 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -9,6 +9,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-qt.cpp b/src/init/bitcoin-qt.cpp index 37d4e426c5..bb3bb945d0 100644 --- a/src/init/bitcoin-qt.cpp +++ b/src/init/bitcoin-qt.cpp @@ -8,6 +8,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-wallet.cpp b/src/init/bitcoin-wallet.cpp index e9dcde72fe..c8d499da10 100644 --- a/src/init/bitcoin-wallet.cpp +++ b/src/init/bitcoin-wallet.cpp @@ -4,6 +4,8 @@ #include <interfaces/init.h> +#include <memory> + namespace interfaces { std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status) { diff --git a/src/init/bitcoind.cpp b/src/init/bitcoind.cpp index 2addff07c1..b473ebb805 100644 --- a/src/init/bitcoind.cpp +++ b/src/init/bitcoind.cpp @@ -8,6 +8,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/common.cpp b/src/init/common.cpp index 688471b35d..eac6732968 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -9,16 +9,21 @@ #include <clientversion.h> #include <compat/sanity.h> #include <crypto/sha256.h> +#include <fs.h> #include <key.h> #include <logging.h> #include <node/ui_interface.h> #include <pubkey.h> #include <random.h> +#include <tinyformat.h> #include <util/system.h> #include <util/time.h> #include <util/translation.h> +#include <algorithm> #include <memory> +#include <string> +#include <vector> static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp new file mode 100644 index 0000000000..bb101ba186 --- /dev/null +++ b/src/kernel/bitcoinkernel.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2022 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 <functional> +#include <string> + +// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the +// library aren't required to export this symbol +extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; diff --git a/src/mapport.cpp b/src/mapport.cpp index 42ca366089..235e6f904c 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -193,7 +193,7 @@ static bool ProcessUpnp() std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", nullptr, "0"); if (r != UPNPCOMMAND_SUCCESS) { ret = false; @@ -206,7 +206,7 @@ static bool ProcessUpnp() } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); g_mapport_interrupt.reset(); - r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); + r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", nullptr); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); freeUPNPDevlist(devlist); devlist = nullptr; FreeUPNPUrls(&urls); diff --git a/src/net.cpp b/src/net.cpp index 586f7d671b..41ac31445d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -103,7 +103,7 @@ enum BindFlags { // The sleep time needs to be small to avoid new sockets stalling static const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50; -const std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; +const std::string NET_MESSAGE_TYPE_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8] @@ -643,12 +643,12 @@ void CNode::CopyStats(CNodeStats& stats) X(m_bip152_highbandwidth_from); { LOCK(cs_vSend); - X(mapSendBytesPerMsgCmd); + X(mapSendBytesPerMsgType); X(nSendBytes); } { LOCK(cs_vRecv); - X(mapRecvBytesPerMsgCmd); + X(mapRecvBytesPerMsgType); X(nRecvBytes); } X(m_permissionFlags); @@ -684,19 +684,19 @@ bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete) bool reject_message{false}; CNetMessage msg = m_deserializer->GetMessage(time, reject_message); if (reject_message) { - // Message deserialization failed. Drop the message but don't disconnect the peer. + // Message deserialization failed. Drop the message but don't disconnect the peer. // store the size of the corrupt message - mapRecvBytesPerMsgCmd.at(NET_MESSAGE_COMMAND_OTHER) += msg.m_raw_message_size; + mapRecvBytesPerMsgType.at(NET_MESSAGE_TYPE_OTHER) += msg.m_raw_message_size; continue; } - // Store received bytes per message command - // to prevent a memory DOS, only allow valid commands - auto i = mapRecvBytesPerMsgCmd.find(msg.m_type); - if (i == mapRecvBytesPerMsgCmd.end()) { - i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER); + // Store received bytes per message type. + // To prevent a memory DOS, only allow known message types. + auto i = mapRecvBytesPerMsgType.find(msg.m_type); + if (i == mapRecvBytesPerMsgType.end()) { + i = mapRecvBytesPerMsgType.find(NET_MESSAGE_TYPE_OTHER); } - assert(i != mapRecvBytesPerMsgCmd.end()); + assert(i != mapRecvBytesPerMsgType.end()); i->second += msg.m_raw_message_size; // push the message to the process queue, @@ -781,7 +781,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds // decompose a single CNetMessage from the TransportDeserializer CNetMessage msg(std::move(vRecv)); - // store command string, time, and sizes + // store message type string, time, and sizes msg.m_type = hdr.GetCommand(); msg.m_time = time; msg.m_message_size = hdr.nMessageSize; @@ -792,7 +792,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds // We just received a message off the wire, harvest entropy from the time (and the message checksum) RandAddEvent(ReadLE32(hash.begin())); - // Check checksum and header command string + // Check checksum and header message type string if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { LogPrint(BCLog::NET, "Header error: Wrong checksum (%s, %u bytes), expected %s was %s, peer=%d\n", SanitizeString(msg.m_type), msg.m_message_size, @@ -1567,6 +1567,8 @@ void CConnman::SocketEvents(const std::vector<CNode*>& nodes, void CConnman::SocketHandler() { + AssertLockNotHeld(m_total_bytes_sent_mutex); + std::set<SOCKET> recv_set; std::set<SOCKET> send_set; std::set<SOCKET> error_set; @@ -1593,6 +1595,8 @@ void CConnman::SocketHandlerConnected(const std::vector<CNode*>& nodes, const std::set<SOCKET>& send_set, const std::set<SOCKET>& error_set) { + AssertLockNotHeld(m_total_bytes_sent_mutex); + for (CNode* pnode : nodes) { if (interruptNet) return; @@ -1694,6 +1698,8 @@ void CConnman::SocketHandlerListening(const std::set<SOCKET>& recv_set) void CConnman::ThreadSocketHandler() { + AssertLockNotHeld(m_total_bytes_sent_mutex); + SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET); while (!interruptNet) { @@ -2578,6 +2584,7 @@ bool CConnman::InitBinds(const Options& options) bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) { + AssertLockNotHeld(m_total_bytes_sent_mutex); Init(connOptions); if (fListen && !InitBinds(connOptions)) { @@ -2930,7 +2937,9 @@ void CConnman::RecordBytesRecv(uint64_t bytes) void CConnman::RecordBytesSent(uint64_t bytes) { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); + nTotalBytesSent += bytes; const auto now = GetTime<std::chrono::seconds>(); @@ -2946,7 +2955,8 @@ void CConnman::RecordBytesSent(uint64_t bytes) uint64_t CConnman::GetMaxOutboundTarget() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); return nMaxOutboundLimit; } @@ -2957,7 +2967,15 @@ std::chrono::seconds CConnman::GetMaxOutboundTimeframe() const std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); + return GetMaxOutboundTimeLeftInCycle_(); +} + +std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle_() const +{ + AssertLockHeld(m_total_bytes_sent_mutex); + if (nMaxOutboundLimit == 0) return 0s; @@ -2971,14 +2989,15 @@ std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() const bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); if (nMaxOutboundLimit == 0) return false; if (historicalBlockServingLimit) { // keep a large enough buffer to at least relay each block once - const std::chrono::seconds timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); + const std::chrono::seconds timeLeftInCycle = GetMaxOutboundTimeLeftInCycle_(); const uint64_t buffer = timeLeftInCycle / std::chrono::minutes{10} * MAX_BLOCK_SERIALIZED_SIZE; if (buffer >= nMaxOutboundLimit || nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) return true; @@ -2991,7 +3010,8 @@ bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) const uint64_t CConnman::GetOutboundTargetBytesLeft() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); if (nMaxOutboundLimit == 0) return 0; @@ -3005,7 +3025,8 @@ uint64_t CConnman::GetTotalBytesRecv() const uint64_t CConnman::GetTotalBytesSent() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); return nTotalBytesSent; } @@ -3032,8 +3053,8 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> s if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); for (const std::string &msg : getAllNetMessageTypes()) - mapRecvBytesPerMsgCmd[msg] = 0; - mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0; + mapRecvBytesPerMsgType[msg] = 0; + mapRecvBytesPerMsgType[NET_MESSAGE_TYPE_OTHER] = 0; if (fLogIPs) { LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", m_addr_name, id); @@ -3052,6 +3073,7 @@ bool CConnman::NodeFullyConnected(const CNode* pnode) void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) { + AssertLockNotHeld(m_total_bytes_sent_mutex); size_t nMessageSize = msg.data.size(); LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", msg.m_type, nMessageSize, pnode->GetId()); if (gArgs.GetBoolArg("-capturemessages", false)) { @@ -3078,7 +3100,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) bool optimisticSend(pnode->vSendMsg.empty()); //log total amount of bytes per message type - pnode->mapSendBytesPerMsgCmd[msg.m_type] += nTotalSize; + pnode->mapSendBytesPerMsgType[msg.m_type] += nTotalSize; pnode->nSendSize += nTotalSize; if (pnode->nSendSize > nSendBufferMaxSize) pnode->fPauseSend = true; @@ -3131,7 +3153,7 @@ void CaptureMessageToFile(const CAddress& addr, std::string clean_addr = addr.ToString(); std::replace(clean_addr.begin(), clean_addr.end(), ':', '_'); - fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / clean_addr; + fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / fs::u8path(clean_addr); fs::create_directories(base_path); fs::path path = base_path / (is_incoming ? "msgs_recv.dat" : "msgs_sent.dat"); @@ -252,8 +252,8 @@ struct LocalServiceInfo { extern Mutex g_maplocalhost_mutex; extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex); -extern const std::string NET_MESSAGE_COMMAND_OTHER; -typedef std::map<std::string, uint64_t> mapMsgCmdSize; //command, total bytes +extern const std::string NET_MESSAGE_TYPE_OTHER; +using mapMsgTypeSize = std::map</* message type */ std::string, /* total bytes */ uint64_t>; class CNodeStats { @@ -274,9 +274,9 @@ public: bool m_bip152_highbandwidth_from; int m_starting_height; uint64_t nSendBytes; - mapMsgCmdSize mapSendBytesPerMsgCmd; + mapMsgTypeSize mapSendBytesPerMsgType; uint64_t nRecvBytes; - mapMsgCmdSize mapRecvBytesPerMsgCmd; + mapMsgTypeSize mapRecvBytesPerMsgType; NetPermissionFlags m_permissionFlags; std::chrono::microseconds m_last_ping_time; std::chrono::microseconds m_min_ping_time; @@ -315,7 +315,7 @@ public: /** The TransportDeserializer takes care of holding and deserializing the * network receive buffer. It can deserialize the network buffer into a - * transport protocol agnostic CNetMessage (command & payload) + * transport protocol agnostic CNetMessage (message type & payload) */ class TransportDeserializer { public: @@ -686,8 +686,8 @@ private: CService addrLocal GUARDED_BY(m_addr_local_mutex); mutable Mutex m_addr_local_mutex; - mapMsgCmdSize mapSendBytesPerMsgCmd GUARDED_BY(cs_vSend); - mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); + mapMsgTypeSize mapSendBytesPerMsgType GUARDED_BY(cs_vSend); + mapMsgTypeSize mapRecvBytesPerMsgType GUARDED_BY(cs_vRecv); }; /** @@ -761,7 +761,10 @@ public: bool m_i2p_accept_incoming; }; - void Init(const Options& connOptions) { + void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex) + { + AssertLockNotHeld(m_total_bytes_sent_mutex); + nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); @@ -777,7 +780,7 @@ public: nReceiveFloodSize = connOptions.nReceiveFloodSize; m_peer_connect_timeout = std::chrono::seconds{connOptions.m_peer_connect_timeout}; { - LOCK(cs_totalBytesSent); + LOCK(m_total_bytes_sent_mutex); nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; @@ -792,7 +795,7 @@ public: bool network_active = true); ~CConnman(); - bool Start(CScheduler& scheduler, const Options& options); + bool Start(CScheduler& scheduler, const Options& options) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); void StopThreads(); void StopNodes(); @@ -811,7 +814,7 @@ public: bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); - void PushMessage(CNode* pnode, CSerializedNetMsg&& msg); + void PushMessage(CNode* pnode, CSerializedNetMsg&& msg) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); using NodeFn = std::function<void(CNode*)>; void ForEachNode(const NodeFn& func) @@ -902,24 +905,22 @@ public: //! that peer during `net_processing.cpp:PushNodeVersion()`. ServiceFlags GetLocalServices() const; - uint64_t GetMaxOutboundTarget() const; + uint64_t GetMaxOutboundTarget() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); std::chrono::seconds GetMaxOutboundTimeframe() const; //! check if the outbound target is reached //! if param historicalBlockServingLimit is set true, the function will //! response true if the limit for serving historical blocks has been reached - bool OutboundTargetReached(bool historicalBlockServingLimit) const; + bool OutboundTargetReached(bool historicalBlockServingLimit) const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); //! response the bytes left in the current max outbound cycle //! in case of no limit, it will always response 0 - uint64_t GetOutboundTargetBytesLeft() const; + uint64_t GetOutboundTargetBytesLeft() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); - //! returns the time left in the current max outbound cycle - //! in case of no limit, it will always return 0 - std::chrono::seconds GetMaxOutboundTimeLeftInCycle() const; + std::chrono::seconds GetMaxOutboundTimeLeftInCycle() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); uint64_t GetTotalBytesRecv() const; - uint64_t GetTotalBytesSent() const; + uint64_t GetTotalBytesSent() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** Get a unique deterministic randomizer. */ CSipHasher GetDeterministicRandomizer(uint64_t id) const; @@ -945,6 +946,10 @@ private: NetPermissionFlags m_permissions; }; + //! returns the time left in the current max outbound cycle + //! in case of no limit, it will always return 0 + std::chrono::seconds GetMaxOutboundTimeLeftInCycle_() const EXCLUSIVE_LOCKS_REQUIRED(m_total_bytes_sent_mutex); + bool BindListenPort(const CService& bindAddr, bilingual_str& strError, NetPermissionFlags permissions); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const Options& options); @@ -1004,7 +1009,7 @@ private: /** * Check connected and listening sockets for IO readiness and process them accordingly. */ - void SocketHandler(); + void SocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** * Do the read/write for connected sockets that are ready for IO. @@ -1017,7 +1022,8 @@ private: void SocketHandlerConnected(const std::vector<CNode*>& nodes, const std::set<SOCKET>& recv_set, const std::set<SOCKET>& send_set, - const std::set<SOCKET>& error_set); + const std::set<SOCKET>& error_set) + EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** * Accept incoming connections, one from each read-ready listening socket. @@ -1025,7 +1031,7 @@ private: */ void SocketHandlerListening(const std::set<SOCKET>& recv_set); - void ThreadSocketHandler(); + void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); void ThreadDNSAddressSeed(); uint64_t CalculateKeyedNetGroup(const CAddress& ad) const; @@ -1054,7 +1060,7 @@ private: // Network stats void RecordBytesRecv(uint64_t bytes); - void RecordBytesSent(uint64_t bytes); + void RecordBytesSent(uint64_t bytes) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** * Return vector of current BLOCK_RELAY peers. @@ -1065,14 +1071,14 @@ private: static bool NodeFullyConnected(const CNode* pnode); // Network usage totals - mutable RecursiveMutex cs_totalBytesSent; + mutable Mutex m_total_bytes_sent_mutex; std::atomic<uint64_t> nTotalBytesRecv{0}; - uint64_t nTotalBytesSent GUARDED_BY(cs_totalBytesSent) {0}; + uint64_t nTotalBytesSent GUARDED_BY(m_total_bytes_sent_mutex) {0}; // outbound limit & stats - uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(cs_totalBytesSent) {0}; - std::chrono::seconds nMaxOutboundCycleStartTime GUARDED_BY(cs_totalBytesSent) {0}; - uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent); + uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(m_total_bytes_sent_mutex) {0}; + std::chrono::seconds nMaxOutboundCycleStartTime GUARDED_BY(m_total_bytes_sent_mutex) {0}; + uint64_t nMaxOutboundLimit GUARDED_BY(m_total_bytes_sent_mutex); // P2P timeout in seconds std::chrono::seconds m_peer_connect_timeout; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index df422fa8e3..cb0e8c88f8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -135,6 +135,8 @@ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24h}; /** Average delay between peer address broadcasts */ static constexpr auto AVG_ADDRESS_BROADCAST_INTERVAL{30s}; +/** Delay between rotating the peers we relay a particular address to */ +static constexpr auto ROTATE_ADDR_RELAY_DEST_INTERVAL{24h}; /** Average delay between trickled inventory transmissions for inbound peers. * Blocks and peers with NetPermissionFlags::NoBan permission bypass this. */ static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL{5s}; @@ -332,6 +334,104 @@ struct Peer { using PeerRef = std::shared_ptr<Peer>; +/** + * Maintain validation-specific state about nodes, protected by cs_main, instead + * by CNode's own locks. This simplifies asynchronous operation, where + * processing of incoming data is done after the ProcessMessage call returns, + * and we're no longer holding the node's locks. + */ +struct CNodeState { + //! The best known block we know this peer has announced. + const CBlockIndex* pindexBestKnownBlock{nullptr}; + //! The hash of the last unknown block this peer has announced. + uint256 hashLastUnknownBlock{}; + //! The last full block we both have. + const CBlockIndex* pindexLastCommonBlock{nullptr}; + //! The best header we have sent our peer. + const CBlockIndex* pindexBestHeaderSent{nullptr}; + //! Length of current-streak of unconnecting headers announcements + int nUnconnectingHeaders{0}; + //! Whether we've started headers synchronization with this peer. + bool fSyncStarted{false}; + //! When to potentially disconnect peer for stalling headers download + std::chrono::microseconds m_headers_sync_timeout{0us}; + //! Since when we're stalling block download progress (in microseconds), or 0. + std::chrono::microseconds m_stalling_since{0us}; + std::list<QueuedBlock> vBlocksInFlight; + //! 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}; + //! Whether we consider this a preferred download peer. + bool fPreferredDownload{false}; + //! Whether this peer wants invs or headers (when possible) for block announcements. + bool fPreferHeaders{false}; + //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. + bool fPreferHeaderAndIDs{false}; + /** + * Whether this peer will send us cmpctblocks if we request them. + * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, + * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. + */ + bool fProvidesHeaderAndIDs{false}; + //! Whether this peer can give us witnesses + bool fHaveWitness{false}; + //! Whether this peer wants witnesses in cmpctblocks/blocktxns + bool fWantsCmpctWitness{false}; + /** + * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, + * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. + */ + bool fSupportsDesiredCmpctVersion{false}; + + /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. + * + * Both are only in effect for outbound, non-manual, non-protected connections. + * Any peer protected (m_protect = true) is not chosen for eviction. A peer is + * marked as protected if all of these are true: + * - its connection type is IsBlockOnlyConn() == false + * - it gave us a valid connecting header + * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet + * - its chain tip has at least as much work as ours + * + * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, + * set a timeout CHAIN_SYNC_TIMEOUT in the future: + * - If at timeout their best known block now has more work than our tip + * when the timeout was set, then either reset the timeout or clear it + * (after comparing against our current tip's work) + * - If at timeout their best known block still has less work than our + * tip did when the timeout was set, then send a getheaders message, + * and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future. + * If their best known block is still behind when that new timeout is + * reached, disconnect. + * + * EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers, + * drop the outbound one that least recently announced us a new block. + */ + struct ChainSyncTimeoutState { + //! A timeout used for checking whether our peer has sufficiently synced + std::chrono::seconds m_timeout{0s}; + //! A header with the work we require on our peer's chain + const CBlockIndex* m_work_header{nullptr}; + //! After timeout is reached, set to true after sending getheaders + bool m_sent_getheaders{false}; + //! Whether this peer is protected from disconnection due to a bad/slow chain + bool m_protect{false}; + }; + + ChainSyncTimeoutState m_chain_sync; + + //! Time of last new block announcement + int64_t m_last_block_announcement{0}; + + //! Whether this peer is an inbound connection + const bool m_is_inbound; + + //! A rolling bloom filter of all announced tx CInvs to this peer. + CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; + + CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} +}; + class PeerManagerImpl final : public PeerManager { public: @@ -364,6 +464,7 @@ public: void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override; + void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; private: /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ @@ -482,6 +583,16 @@ private: */ std::map<NodeId, PeerRef> m_peer_map GUARDED_BY(m_peer_mutex); + /** Map maintaining per-node state. */ + std::map<NodeId, CNodeState> m_node_states GUARDED_BY(cs_main); + + /** Get a pointer to a const CNodeState, used when not mutating the CNodeState object. */ + const CNodeState* State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Get a pointer to a mutable CNodeState. */ + CNodeState* State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + uint32_t GetFetchFlags(const CNode& pfrom) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::atomic<std::chrono::microseconds> m_next_inv_to_inbounds{0us}; /** Number of nodes with fSyncStarted. */ @@ -501,6 +612,9 @@ private: /** Number of outbound peers with m_chain_sync.m_protect. */ int m_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0; + /** Number of preferable block download peers. */ + int m_num_preferred_download_peers GUARDED_BY(cs_main){0}; + bool AlreadyHaveTx(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** @@ -567,6 +681,17 @@ private: std::chrono::microseconds NextInvToInbounds(std::chrono::microseconds now, std::chrono::seconds average_interval); + + // All of the following cache a recent block, and are protected by m_most_recent_block_mutex + RecursiveMutex m_most_recent_block_mutex; + std::shared_ptr<const CBlock> m_most_recent_block GUARDED_BY(m_most_recent_block_mutex); + std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex); + uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex); + bool m_most_recent_compact_block_has_witnesses GUARDED_BY(m_most_recent_block_mutex){false}; + + /** Height of the highest block announced using BIP 152 high-bandwidth mode. */ + int m_highest_fast_announce{0}; + /** Have we requested this block from a peer */ bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -708,122 +833,20 @@ private: */ bool SetupAddressRelay(const CNode& node, Peer& peer); }; -} // namespace - -namespace { - /** Number of preferable block download peers. */ - int nPreferredDownload GUARDED_BY(cs_main) = 0; -} // namespace - -namespace { -/** - * Maintain validation-specific state about nodes, protected by cs_main, instead - * by CNode's own locks. This simplifies asynchronous operation, where - * processing of incoming data is done after the ProcessMessage call returns, - * and we're no longer holding the node's locks. - */ -struct CNodeState { - //! The best known block we know this peer has announced. - const CBlockIndex* pindexBestKnownBlock{nullptr}; - //! The hash of the last unknown block this peer has announced. - uint256 hashLastUnknownBlock{}; - //! The last full block we both have. - const CBlockIndex* pindexLastCommonBlock{nullptr}; - //! The best header we have sent our peer. - const CBlockIndex* pindexBestHeaderSent{nullptr}; - //! Length of current-streak of unconnecting headers announcements - int nUnconnectingHeaders{0}; - //! Whether we've started headers synchronization with this peer. - bool fSyncStarted{false}; - //! When to potentially disconnect peer for stalling headers download - std::chrono::microseconds m_headers_sync_timeout{0us}; - //! Since when we're stalling block download progress (in microseconds), or 0. - std::chrono::microseconds m_stalling_since{0us}; - std::list<QueuedBlock> vBlocksInFlight; - //! 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}; - //! Whether we consider this a preferred download peer. - bool fPreferredDownload{false}; - //! Whether this peer wants invs or headers (when possible) for block announcements. - bool fPreferHeaders{false}; - //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. - bool fPreferHeaderAndIDs{false}; - /** - * Whether this peer will send us cmpctblocks if we request them. - * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, - * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. - */ - bool fProvidesHeaderAndIDs{false}; - //! Whether this peer can give us witnesses - bool fHaveWitness{false}; - //! Whether this peer wants witnesses in cmpctblocks/blocktxns - bool fWantsCmpctWitness{false}; - /** - * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, - * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. - */ - bool fSupportsDesiredCmpctVersion{false}; - - /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. - * - * Both are only in effect for outbound, non-manual, non-protected connections. - * Any peer protected (m_protect = true) is not chosen for eviction. A peer is - * marked as protected if all of these are true: - * - its connection type is IsBlockOnlyConn() == false - * - it gave us a valid connecting header - * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet - * - its chain tip has at least as much work as ours - * - * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, - * set a timeout CHAIN_SYNC_TIMEOUT in the future: - * - If at timeout their best known block now has more work than our tip - * when the timeout was set, then either reset the timeout or clear it - * (after comparing against our current tip's work) - * - If at timeout their best known block still has less work than our - * tip did when the timeout was set, then send a getheaders message, - * and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future. - * If their best known block is still behind when that new timeout is - * reached, disconnect. - * - * EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers, - * drop the outbound one that least recently announced us a new block. - */ - struct ChainSyncTimeoutState { - //! A timeout used for checking whether our peer has sufficiently synced - std::chrono::seconds m_timeout{0s}; - //! A header with the work we require on our peer's chain - const CBlockIndex* m_work_header{nullptr}; - //! After timeout is reached, set to true after sending getheaders - bool m_sent_getheaders{false}; - //! Whether this peer is protected from disconnection due to a bad/slow chain - bool m_protect{false}; - }; - - ChainSyncTimeoutState m_chain_sync; - - //! Time of last new block announcement - int64_t m_last_block_announcement{0}; - - //! Whether this peer is an inbound connection - const bool m_is_inbound; - - //! A rolling bloom filter of all announced tx CInvs to this peer. - CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; - - CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} -}; -/** Map maintaining per-node state. */ -static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); - -static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode); - if (it == mapNodeState.end()) +const CNodeState* PeerManagerImpl::State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + std::map<NodeId, CNodeState>::const_iterator it = m_node_states.find(pnode); + if (it == m_node_states.end()) return nullptr; return &it->second; } +CNodeState* PeerManagerImpl::State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + return const_cast<CNodeState*>(std::as_const(*this).State(pnode)); +} + /** * Whether the peer supports the address. For example, a peer that does not * implement BIP155 cannot receive Tor v3 addresses because it requires @@ -863,16 +886,6 @@ static void AddKnownTx(Peer& peer, const uint256& hash) } } -static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - nPreferredDownload -= state->fPreferredDownload; - - // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node.IsInboundConn() || node.HasPermission(NetPermissionFlags::NoBan)) && !node.IsAddrFetchConn() && !node.fClient; - - nPreferredDownload += state->fPreferredDownload; -} - std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::microseconds now, std::chrono::seconds average_interval) { @@ -1213,9 +1226,7 @@ void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, m_txrequest.ReceivedInv(nodeid, gtxid, preferred, current_time + delay); } -// This function is used for testing the stale tip eviction logic, see -// denialofservice_tests.cpp -void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) +void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); CNodeState *state = State(node); @@ -1227,7 +1238,7 @@ void PeerManagerImpl::InitializeNode(CNode *pnode) NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); + m_node_states.emplace_hint(m_node_states.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); assert(m_txrequest.Count(nodeid) == 0); } PeerRef peer = std::make_shared<Peer>(nodeid, /*tx_relay=*/ !pnode->IsBlockOnlyConn()); @@ -1289,18 +1300,18 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) } WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); - nPreferredDownload -= state->fPreferredDownload; + m_num_preferred_download_peers -= state->fPreferredDownload; 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); - mapNodeState.erase(nodeid); + m_node_states.erase(nodeid); - if (mapNodeState.empty()) { + if (m_node_states.empty()) { // Do a consistency check after the last peer is removed. assert(mapBlocksInFlight.empty()); - assert(nPreferredDownload == 0); + assert(m_num_preferred_download_peers == 0); assert(m_peers_downloading_from == 0); assert(m_outbound_peers_with_protect_from_disconnect == 0); assert(m_wtxid_relay_peers == 0); @@ -1341,7 +1352,7 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c { { LOCK(cs_main); - CNodeState* state = State(nodeid); + const CNodeState* state = State(nodeid); if (state == nullptr) return false; stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1; @@ -1615,13 +1626,6 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo m_recent_confirmed_transactions.reset(); } -// All of the following cache a recent block, and are protected by cs_most_recent_block -static RecursiveMutex cs_most_recent_block; -static std::shared_ptr<const CBlock> most_recent_block GUARDED_BY(cs_most_recent_block); -static std::shared_ptr<const CBlockHeaderAndShortTxIDs> most_recent_compact_block GUARDED_BY(cs_most_recent_block); -static uint256 most_recent_block_hash GUARDED_BY(cs_most_recent_block); -static bool fWitnessesPresentInMostRecentCompactBlock GUARDED_BY(cs_most_recent_block); - /** * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. @@ -1633,10 +1637,9 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha LOCK(cs_main); - static int nHighestFastAnnounce = 0; - if (pindex->nHeight <= nHighestFastAnnounce) + if (pindex->nHeight <= m_highest_fast_announce) return; - nHighestFastAnnounce = pindex->nHeight; + m_highest_fast_announce = pindex->nHeight; bool fWitnessEnabled = DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); uint256 hashBlock(pblock->GetHash()); @@ -1644,11 +1647,11 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })}; { - LOCK(cs_most_recent_block); - most_recent_block_hash = hashBlock; - most_recent_block = pblock; - most_recent_compact_block = pcmpctblock; - fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled; + LOCK(m_most_recent_block_mutex); + m_most_recent_block_hash = hashBlock; + m_most_recent_block = pblock; + m_most_recent_compact_block = pcmpctblock; + m_most_recent_compact_block_has_witnesses = fWitnessEnabled; } m_connman.ForEachNode([this, pindex, fWitnessEnabled, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { @@ -1818,9 +1821,12 @@ void PeerManagerImpl::RelayAddress(NodeId originator, // Use deterministic randomness to send to the same nodes for 24 hours // at a time so the m_addr_knowns of the chosen nodes prevent repeats const uint64_t hash_addr{CServiceHash(0, 0)(addr)}; + const auto current_time{GetTime<std::chrono::seconds>()}; + // Adding address hash makes exact rotation time different per address, while preserving periodicity. + const uint64_t time_addr{(static_cast<uint64_t>(count_seconds(current_time)) + hash_addr) / count_seconds(ROTATE_ADDR_RELAY_DEST_INTERVAL)}; const CSipHasher hasher{m_connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY) .Write(hash_addr) - .Write((GetTime() + hash_addr) / (24 * 60 * 60))}; + .Write(time_addr)}; FastRandomContext insecure_rand; // Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers. @@ -1855,10 +1861,10 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block; bool fWitnessesPresentInARecentCompactBlock; { - LOCK(cs_most_recent_block); - a_recent_block = most_recent_block; - a_recent_compact_block = most_recent_compact_block; - fWitnessesPresentInARecentCompactBlock = fWitnessesPresentInMostRecentCompactBlock; + LOCK(m_most_recent_block_mutex); + a_recent_block = m_most_recent_block; + a_recent_compact_block = m_most_recent_compact_block; + fWitnessesPresentInARecentCompactBlock = m_most_recent_compact_block_has_witnesses; } bool need_activate_chain = false; @@ -2121,7 +2127,8 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } } -static uint32_t GetFetchFlags(const CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { +uint32_t PeerManagerImpl::GetFetchFlags(const CNode& pfrom) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ uint32_t nFetchFlags = 0; if (State(pfrom.GetId())->fHaveWitness) { nFetchFlags |= MSG_WITNESS_FLAG; @@ -2730,8 +2737,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Potentially mark this peer as a preferred download peer. { - LOCK(cs_main); - UpdatePreferredDownload(pfrom, State(pfrom.GetId())); + LOCK(cs_main); + CNodeState* state = State(pfrom.GetId()); + state->fPreferredDownload = (!pfrom.IsInboundConn() || pfrom.HasPermission(NetPermissionFlags::NoBan)) && !pfrom.IsAddrFetchConn() && !pfrom.fClient; + m_num_preferred_download_peers += state->fPreferredDownload; } // Self advertisement & GETADDR logic @@ -3154,8 +3163,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { std::shared_ptr<const CBlock> a_recent_block; { - LOCK(cs_most_recent_block); - a_recent_block = most_recent_block; + LOCK(m_most_recent_block_mutex); + a_recent_block = m_most_recent_block; } BlockValidationState state; if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) { @@ -3206,10 +3215,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, std::shared_ptr<const CBlock> recent_block; { - LOCK(cs_most_recent_block); - if (most_recent_block_hash == req.blockhash) - recent_block = most_recent_block; - // Unlock cs_most_recent_block to avoid cs_main lock inversion + LOCK(m_most_recent_block_mutex); + if (m_most_recent_block_hash == req.blockhash) + recent_block = m_most_recent_block; + // Unlock m_most_recent_block_mutex to avoid cs_main lock inversion } if (recent_block) { SendBlockTransactions(pfrom, *recent_block, req); @@ -4673,7 +4682,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (m_chainman.m_best_header == nullptr) { m_chainman.m_best_header = m_chainman.ActiveChain().Tip(); } - bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. + bool fFetch = state.fPreferredDownload || (m_num_preferred_download_peers == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. if ((nSyncStarted == 0 && fFetch) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { @@ -4778,12 +4787,12 @@ bool PeerManagerImpl::SendMessages(CNode* pto) bool fGotBlockFromCache = false; { - LOCK(cs_most_recent_block); - if (most_recent_block_hash == pBestIndex->GetBlockHash()) { - if (state.fWantsCmpctWitness || !fWitnessesPresentInMostRecentCompactBlock) - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *most_recent_compact_block)); + LOCK(m_most_recent_block_mutex); + if (m_most_recent_block_hash == pBestIndex->GetBlockHash()) { + if (state.fWantsCmpctWitness || !m_most_recent_compact_block_has_witnesses) + m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *m_most_recent_compact_block)); else { - CBlockHeaderAndShortTxIDs cmpctblock(*most_recent_block, state.fWantsCmpctWitness); + CBlockHeaderAndShortTxIDs cmpctblock(*m_most_recent_block, state.fWantsCmpctWitness); m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); } fGotBlockFromCache = true; @@ -5018,7 +5027,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) { // Detect whether this is a stalling initial-headers-sync peer if (m_chainman.m_best_header->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { - if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { + if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (m_num_preferred_download_peers - state.fPreferredDownload >= 1)) { // Disconnect a peer (without NetPermissionFlags::NoBan permission) if it is our only sync peer, // and we have others we could be using instead. // Note: If all our peers are inbound, then we won't diff --git a/src/net_processing.h b/src/net_processing.h index 7dacaee5c1..c982b919a6 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -87,6 +87,9 @@ public: /** Process a single message from a peer. Public for fuzz testing */ virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) = 0; + + /** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */ + virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0; }; #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/netaddress.cpp b/src/netaddress.cpp index bc1915aad9..9717f7abce 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -20,9 +20,6 @@ #include <iterator> #include <tuple> -constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; -constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; - CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const { switch (m_net) { @@ -210,7 +207,7 @@ static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKS bool CNetAddr::SetSpecial(const std::string& addr) { - if (!ValidAsCString(addr)) { + if (!ContainsNoNUL(addr)) { return false; } @@ -234,17 +231,16 @@ bool CNetAddr::SetTor(const std::string& addr) return false; } - bool invalid; - const auto& input = DecodeBase32(addr.substr(0, addr.size() - suffix_len).c_str(), &invalid); + auto input = DecodeBase32(std::string_view{addr}.substr(0, addr.size() - suffix_len)); - if (invalid) { + if (!input) { return false; } - if (input.size() == torv3::TOTAL_LEN) { - Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE}; - Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN}; - Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)}; + if (input->size() == torv3::TOTAL_LEN) { + Span<const uint8_t> input_pubkey{input->data(), ADDR_TORV3_SIZE}; + Span<const uint8_t> input_checksum{input->data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN}; + Span<const uint8_t> input_version{input->data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)}; if (input_version != torv3::VERSION) { return false; @@ -280,15 +276,14 @@ bool CNetAddr::SetI2P(const std::string& addr) // can decode it. const std::string b32_padded = addr.substr(0, b32_len) + "===="; - bool invalid; - const auto& address_bytes = DecodeBase32(b32_padded.c_str(), &invalid); + auto address_bytes = DecodeBase32(b32_padded); - if (invalid || address_bytes.size() != ADDR_I2P_SIZE) { + if (!address_bytes || address_bytes->size() != ADDR_I2P_SIZE) { return false; } m_net = NET_I2P; - m_addr.assign(address_bytes.begin(), address_bytes.end()); + m_addr.assign(address_bytes->begin(), address_bytes->end()); return true; } diff --git a/src/netbase.cpp b/src/netbase.cpp index 9557297df1..8ff3b7a68c 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -136,7 +136,7 @@ static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un { vIP.clear(); - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } @@ -169,7 +169,7 @@ static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::string strHost = name; @@ -184,7 +184,7 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::vector<CNetAddr> vIP; @@ -197,7 +197,7 @@ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSL bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function) { - if (name.empty() || !ValidAsCString(name)) { + if (name.empty() || !ContainsNoNUL(name)) { return false; } uint16_t port{portDefault}; @@ -216,7 +216,7 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t port bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::vector<CService> vService; @@ -229,7 +229,7 @@ bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool CService LookupNumeric(const std::string& name, uint16_t portDefault, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return {}; } CService addr; @@ -675,7 +675,7 @@ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_ return false; } } else { - if (!Socks5(strDest, port, 0, sock)) { + if (!Socks5(strDest, port, nullptr, sock)) { return false; } } @@ -684,7 +684,7 @@ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out) { - if (!ValidAsCString(subnet_str)) { + if (!ContainsNoNUL(subnet_str)) { return false; } diff --git a/src/netgroup.cpp b/src/netgroup.cpp index 5f42d6c719..96b5e29684 100644 --- a/src/netgroup.cpp +++ b/src/netgroup.cpp @@ -71,7 +71,7 @@ std::vector<unsigned char> NetGroupManager::GetGroup(const CNetAddr& address) co // ...for the last byte, push nBits and for the rest of the byte push 1's if (nBits > 0) { assert(num_bytes < addr_bytes.size()); - vchRet.push_back(addr_bytes[num_bytes] | ((1 << (8 - nBits)) - 1)); + vchRet.push_back(addr_bytes[num_bytes + nStartByte] | ((1 << (8 - nBits)) - 1)); } return vchRet; diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 21cb0250d8..17ab226a30 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -21,6 +21,8 @@ #include <util/system.h> #include <validation.h> +#include <unordered_map> + namespace node { std::atomic_bool fImporting(false); std::atomic_bool fReindex(false); @@ -230,6 +232,11 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr nLastBlockWeCanPrune, count); } +void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) { + AssertLockHeld(::cs_main); + m_prune_locks[name] = lock_info; +} + CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash) { AssertLockHeld(cs_main); @@ -290,20 +297,6 @@ bool BlockManager::LoadBlockIndex(const Consensus::Params& consensus_params) return true; } -void BlockManager::Unload() -{ - m_blocks_unlinked.clear(); - - m_block_index.clear(); - - m_blockfile_info.clear(); - m_last_blockfile = 0; - m_dirty_blockindex.clear(); - m_dirty_fileinfo.clear(); - - m_have_pruned = false; -} - bool BlockManager::WriteBlockIndexDB() { AssertLockHeld(::cs_main); @@ -397,6 +390,16 @@ bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex) return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0); } +const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_block) +{ + AssertLockHeld(::cs_main); + const CBlockIndex* last_block = &start_block; + while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) { + last_block = last_block->pprev; + } + return last_block; +} + // If we're using -prune with -reindex, then delete block files that will be ignored by the // reindex. Since reindexing works by starting at block file 0 and looping until a blockfile // is missing, do the same here to delete any later block files after a gap. Also delete all diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 11445aa22e..488713dbd8 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -5,14 +5,16 @@ #ifndef BITCOIN_NODE_BLOCKSTORAGE_H #define BITCOIN_NODE_BLOCKSTORAGE_H +#include <attributes.h> #include <chain.h> #include <fs.h> -#include <protocol.h> // For CMessageHeader::MessageStartChars +#include <protocol.h> #include <sync.h> #include <txdb.h> #include <atomic> #include <cstdint> +#include <unordered_map> #include <vector> extern RecursiveMutex cs_main; @@ -65,6 +67,10 @@ struct CBlockIndexHeightOnlyComparator { bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const; }; +struct PruneLockInfo { + int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned +}; + /** * Maintains a tree of blocks (stored in `m_block_index`) which is consulted * to determine where the most-work tip is. @@ -78,6 +84,13 @@ class BlockManager friend ChainstateManager; private: + /** + * Load the blocktree off disk and into memory. Populate certain metadata + * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral + * collections like m_dirty_blockindex. + */ + bool LoadBlockIndex(const Consensus::Params& consensus_params) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false); void FlushUndoFile(int block_file, bool finalize = false); bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown); @@ -118,6 +131,14 @@ private: /** Dirty block file entries. */ std::set<int> m_dirty_fileinfo; + /** + * Map from external index name to oldest block that must not be pruned. + * + * @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and + * below will be pruned, but callers should avoid assuming any particular buffer size. + */ + std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main); + public: BlockMap m_block_index GUARDED_BY(cs_main); @@ -134,17 +155,6 @@ public: bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - /** - * Load the blocktree off disk and into memory. Populate certain metadata - * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral - * collections like m_dirty_blockindex. - */ - bool LoadBlockIndex(const Consensus::Params& consensus_params) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - /** Clear all data members. */ - void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CBlockIndex* AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Create a new block index entry for a given block hash */ CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -169,16 +179,17 @@ public: //! Returns last CBlockIndex* that is a checkpoint const CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + //! Find the first block that is not pruned + const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** True if any block files have ever been pruned. */ bool m_have_pruned = false; //! Check whether the block associated with this index entry is pruned or not. bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - ~BlockManager() - { - Unload(); - } + //! Create or update a prune lock identified by its name + void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); }; void CleanupBlockRevFiles(); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index e43211402c..99615dea69 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -32,8 +32,6 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; - UnloadBlockIndex(mempool, chainman); - auto& pblocktree{chainman.m_blockman.m_block_tree_db}; // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: diff --git a/src/node/miner.h b/src/node/miner.h index c8093ec883..678df815c0 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -116,7 +116,7 @@ struct update_for_parent_inclusion void operator() (CTxMemPoolModifiedEntry &e) { - e.nModFeesWithAncestors -= iter->GetFee(); + e.nModFeesWithAncestors -= iter->GetModifiedFee(); e.nSizeWithAncestors -= iter->GetTxSize(); e.nSigOpCostWithAncestors -= iter->GetSigOpCost(); } diff --git a/src/prevector.h b/src/prevector.h index aa20efaaa7..830b31e315 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -458,7 +458,8 @@ public: return *item_ptr(size() - 1); } - void swap(prevector<N, T, Size, Diff>& other) { + void swap(prevector<N, T, Size, Diff>& other) noexcept + { std::swap(_union, other._union); std::swap(_size, other._size); } diff --git a/src/psbt.cpp b/src/psbt.cpp index c8c73e130b..c1c8a385cc 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -234,7 +234,7 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio // Construct a would-be spend of this output, to update sigdata with. // Note that ProduceSignature is used to fill in metadata (not actual signatures), // so provider does not need to provide any private keys (it can be a HidingSigningProvider). - MutableTransactionSignatureCreator creator(&tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL); + MutableTransactionSignatureCreator creator(tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL); ProduceSignature(provider, creator, out.scriptPubKey, sigdata); // Put redeem_script, witness_script, key paths, into PSBTOutput. @@ -301,7 +301,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, 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. @@ -388,18 +388,17 @@ std::string PSBTRoleName(PSBTRole role) { bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) { - bool invalid; - std::string tx_data = DecodeBase64(base64_tx, &invalid); - if (invalid) { + auto tx_data = DecodeBase64(base64_tx); + if (!tx_data) { error = "invalid base64"; return false; } - return DecodeRawPSBT(psbt, tx_data, error); + return DecodeRawPSBT(psbt, MakeByteSpan(*tx_data), error); } -bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error) +bool DecodeRawPSBT(PartiallySignedTransaction& psbt, Span<const std::byte> tx_data, std::string& error) { - CDataStream ss_data(MakeByteSpan(tx_data), SER_NETWORK, PROTOCOL_VERSION); + CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION); try { ss_data >> psbt; if (!ss_data.empty()) { diff --git a/src/psbt.h b/src/psbt.h index f0ceb02481..8a9cbd33d2 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -988,6 +988,6 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti //! Decode a base64ed PSBT into a PartiallySignedTransaction [[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error); //! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction -[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error); +[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, Span<const std::byte> raw_psbt, std::string& error); #endif // BITCOIN_PSBT_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 6fb5fce5b3..706cf2f7db 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -508,7 +508,7 @@ fs::path static StartupShortcutPath() return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; if (chain == CBaseChainParams::TESTNET) // Remove this special case when CBaseChainParams::TESTNET = "testnet4" return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; - return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); + return GetSpecialFolderPath(CSIDL_STARTUP) / fs::u8path(strprintf("Bitcoin (%s).lnk", chain)); } bool GetStartOnSystemStartup() @@ -589,7 +589,7 @@ fs::path static GetAutostartFilePath() std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; - return GetAutostartDir() / strprintf("bitcoin-%s.desktop", chain); + return GetAutostartDir() / fs::u8path(strprintf("bitcoin-%s.desktop", chain)); } bool GetStartOnSystemStartup() diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 76e5207d81..e38ac6026a 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -361,18 +361,6 @@ namespace GUIUtil #endif } - /** - * Queue a function to run in an object's event loop. This can be - * replaced by a call to the QMetaObject::invokeMethod functor overload after Qt 5.10, but - * for now use a QObject::connect for compatibility with older Qt versions, based on - * https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style - */ - template <typename Fn> - void ObjectInvoke(QObject* object, Fn&& function, Qt::ConnectionType connection = Qt::QueuedConnection) - { - QObject source; - QObject::connect(&source, &QObject::destroyed, object, std::forward<Fn>(function), connection); - } /** * Replaces a plain text link with an HTML tagged one. diff --git a/src/qt/initexecutor.cpp b/src/qt/initexecutor.cpp index 24ae7ba73d..d269dfec71 100644 --- a/src/qt/initexecutor.cpp +++ b/src/qt/initexecutor.cpp @@ -5,13 +5,13 @@ #include <qt/initexecutor.h> #include <interfaces/node.h> -#include <qt/guiutil.h> #include <util/system.h> #include <util/threadnames.h> #include <exception> #include <QDebug> +#include <QMetaObject> #include <QObject> #include <QString> #include <QThread> @@ -39,7 +39,7 @@ void InitExecutor::handleRunawayException(const std::exception* e) void InitExecutor::initialize() { - GUIUtil::ObjectInvoke(&m_context, [this] { + QMetaObject::invokeMethod(&m_context, [this] { try { util::ThreadRename("qt-init"); qDebug() << "Running initialization in thread"; @@ -56,7 +56,7 @@ void InitExecutor::initialize() void InitExecutor::shutdown() { - GUIUtil::ObjectInvoke(&m_context, [this] { + QMetaObject::invokeMethod(&m_context, [this] { try { qDebug() << "Running Shutdown in thread"; m_node.appShutdown(); diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 63b4055092..f4928951fe 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -226,7 +226,7 @@ bool Intro::showIfNeeded(bool& did_show_intro, int64_t& prune_MiB) } /* If current default data directory does not exist, let the user choose one */ - Intro intro(0, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize()); + Intro intro(nullptr, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); did_show_intro = true; diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index eb69fabe89..4a51990f88 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1032,8 +1032,9 @@ void RPCConsole::on_lineEdit_returnPressed() ui->lineEdit->clear(); + WalletModel* wallet_model{nullptr}; #ifdef ENABLE_WALLET - WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); + wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); if (m_last_wallet_model != wallet_model) { if (wallet_model) { @@ -1049,7 +1050,10 @@ void RPCConsole::on_lineEdit_returnPressed() //: A console message indicating an entered command is currently being executed. message(CMD_REPLY, tr("Executing…")); m_is_executing = true; - Q_EMIT cmdRequest(cmd, m_last_wallet_model); + + QMetaObject::invokeMethod(m_executor, [this, cmd, wallet_model] { + m_executor->request(cmd, wallet_model); + }); cmd = QString::fromStdString(strFilteredCmd); @@ -1091,11 +1095,11 @@ void RPCConsole::browseHistory(int offset) void RPCConsole::startExecutor() { - RPCExecutor *executor = new RPCExecutor(m_node); - executor->moveToThread(&thread); + m_executor = new RPCExecutor(m_node); + m_executor->moveToThread(&thread); // Replies from executor object must go to this object - connect(executor, &RPCExecutor::reply, this, [this](int category, const QString& command) { + connect(m_executor, &RPCExecutor::reply, this, [this](int category, const QString& command) { // Remove "Executing…" message. ui->messagesWidget->undo(); message(category, command); @@ -1103,16 +1107,13 @@ void RPCConsole::startExecutor() m_is_executing = false; }); - // Requests from this object must go to executor - connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); - // Make sure executor object is deleted in its own thread - connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater); + connect(&thread, &QThread::finished, m_executor, &RPCExecutor::deleteLater); // Default implementation of QThread::run() simply spins up an event loop in the thread, // which is what we want. thread.start(); - QTimer::singleShot(0, executor, []() { + QTimer::singleShot(0, m_executor, []() { util::ThreadRename("qt-rpcconsole"); }); } diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 528e2bef7d..1a54fe0cad 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -5,6 +5,10 @@ #ifndef BITCOIN_QT_RPCCONSOLE_H #define BITCOIN_QT_RPCCONSOLE_H +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/guiutil.h> #include <qt/peertablemodel.h> @@ -17,6 +21,7 @@ class ClientModel; class PlatformStyle; +class RPCExecutor; class RPCTimerInterface; class WalletModel; @@ -49,8 +54,11 @@ public: } void setClientModel(ClientModel *model = nullptr, int bestblock_height = 0, int64_t bestblock_date = 0, double verification_progress = 0.0); - void addWallet(WalletModel * const walletModel); + +#ifdef ENABLE_WALLET + void addWallet(WalletModel* const walletModel); void removeWallet(WalletModel* const walletModel); +#endif // ENABLE_WALLET enum MessageClass { MC_ERROR, @@ -129,10 +137,6 @@ public Q_SLOTS: /** set which tab has the focus (is visible) */ void setTabFocus(enum TabTypes tabType); -Q_SIGNALS: - // For RPC command executor - void cmdRequest(const QString &command, const WalletModel* wallet_model); - private: struct TranslatedStrings { const QString yes{tr("Yes")}, no{tr("No")}, to{tr("To")}, from{tr("From")}, @@ -166,6 +170,7 @@ private: int consoleFontSize = 0; QCompleter *autoCompleter = nullptr; QThread thread; + RPCExecutor* m_executor{nullptr}; WalletModel* m_last_wallet_model{nullptr}; bool m_is_executing{false}; QByteArray m_peer_widget_header_state; diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 66637a5dcf..ba0e4c686f 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -8,6 +8,7 @@ #include <interfaces/chain.h> #include <interfaces/node.h> +#include <qt/addressbookpage.h> #include <qt/clientmodel.h> #include <qt/editaddressdialog.h> #include <qt/optionsmodel.h> @@ -23,8 +24,9 @@ #include <chrono> #include <QApplication> -#include <QTimer> #include <QMessageBox> +#include <QTableView> +#include <QTimer> using wallet::AddWallet; using wallet::CWallet; @@ -131,14 +133,19 @@ void TestAddAddressesToSendBook(interfaces::Node& node) EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); + AddressBookPage address_book{platformStyle.get(), AddressBookPage::ForEditing, AddressBookPage::SendingTab}; + address_book.setModel(walletModel.getAddressTableModel()); + auto table_view = address_book.findChild<QTableView*>("tableView"); + QCOMPARE(table_view->model()->rowCount(), 1); + EditAddressAndSubmit( &editAddressDialog, QString("uhoh"), preexisting_r_address, QString( "Address \"%1\" already exists as a receiving address with label " "\"%2\" and so cannot be added as a sending address." ).arg(preexisting_r_address).arg(r_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); EditAddressAndSubmit( &editAddressDialog, QString("uhoh, different"), preexisting_s_address, @@ -146,15 +153,15 @@ void TestAddAddressesToSendBook(interfaces::Node& node) "The entered address \"%1\" is already in the address book with " "label \"%2\"." ).arg(preexisting_s_address).arg(s_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); // Submit a new address which should add successfully - we expect the // warning message to be blank. EditAddressAndSubmit( &editAddressDialog, QString("new"), new_address, QString("")); - check_addbook_size(3); + QCOMPARE(table_view->model()->rowCount(), 2); } } // namespace diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index b025bb367c..d27ddf1aba 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -24,6 +24,7 @@ #include <QApplication> #include <QMessageBox> +#include <QMetaObject> #include <QMutexLocker> #include <QThread> #include <QTimer> @@ -135,7 +136,7 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal // handled on the GUI event loop. wallet_model->moveToThread(thread()); // setParent(parent) must be called in the thread which created the parent object. More details in #18948. - GUIUtil::ObjectInvoke(this, [wallet_model, this] { + QMetaObject::invokeMethod(this, [wallet_model, this] { wallet_model->setParent(this); }, GUIUtil::blockingGUIThreadConnection()); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 91ce420a33..dc4e25a02b 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -194,16 +194,16 @@ void WalletFrame::gotoVerifyMessageTab(QString addr) void WalletFrame::gotoLoadPSBT(bool from_clipboard) { - std::string data; + std::vector<unsigned char> data; if (from_clipboard) { std::string raw = QApplication::clipboard()->text().toStdString(); - bool invalid; - data = DecodeBase64(raw, &invalid); - if (invalid) { + auto result = DecodeBase64(raw); + if (!result) { Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR); return; } + data = std::move(*result); } else { QString filename = GUIUtil::getOpenFileName(this, tr("Load Transaction Data"), QString(), @@ -214,12 +214,12 @@ void WalletFrame::gotoLoadPSBT(bool from_clipboard) return; } std::ifstream in{filename.toLocal8Bit().data(), std::ios::binary}; - data = std::string(std::istreambuf_iterator<char>{in}, {}); + data.assign(std::istream_iterator<unsigned char>{in}, {}); } std::string error; PartiallySignedTransaction psbtx; - if (!DecodeRawPSBT(psbtx, data, error)) { + if (!DecodeRawPSBT(psbtx, MakeByteSpan(data), error)) { Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR); return; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 5ee32e79d5..1f6c90af4a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -145,7 +145,7 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } -bool WalletModel::validateAddress(const QString &address) +bool WalletModel::validateAddress(const QString& address) const { return IsValidDestinationString(address.toStdString()); } @@ -284,22 +284,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran return SendCoinsReturn(OK); } -OptionsModel *WalletModel::getOptionsModel() +OptionsModel* WalletModel::getOptionsModel() const { return optionsModel; } -AddressTableModel *WalletModel::getAddressTableModel() +AddressTableModel* WalletModel::getAddressTableModel() const { return addressTableModel; } -TransactionTableModel *WalletModel::getTransactionTableModel() +TransactionTableModel* WalletModel::getTransactionTableModel() const { return transactionTableModel; } -RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() +RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const { return recentRequestsTableModel; } @@ -369,7 +369,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), @@ -557,7 +557,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) return true; } -bool WalletModel::displayAddress(std::string sAddress) +bool WalletModel::displayAddress(std::string sAddress) const { CTxDestination dest = DecodeDestination(sAddress); bool res = false; @@ -585,7 +585,7 @@ QString WalletModel::getDisplayName() const return name.isEmpty() ? "["+tr("default wallet")+"]" : name; } -bool WalletModel::isMultiwallet() +bool WalletModel::isMultiwallet() const { return m_node.walletLoader().getWallets().size() > 1; } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ad1239ccdc..540fdaafe3 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -77,15 +77,15 @@ public: Unlocked // wallet->IsCrypted() && !wallet->IsLocked() }; - OptionsModel *getOptionsModel(); - AddressTableModel *getAddressTableModel(); - TransactionTableModel *getTransactionTableModel(); - RecentRequestsTableModel *getRecentRequestsTableModel(); + OptionsModel* getOptionsModel() const; + AddressTableModel* getAddressTableModel() const; + TransactionTableModel* getTransactionTableModel() const; + RecentRequestsTableModel* getRecentRequestsTableModel() const; EncryptionStatus getEncryptionStatus() const; // Check address for validity - bool validateAddress(const QString &address); + bool validateAddress(const QString& address) const; // Return status record for SendCoins, contains error id + information struct SendCoinsReturn @@ -137,7 +137,7 @@ public: UnlockContext requestUnlock(); bool bumpFee(uint256 hash, uint256& new_hash); - bool displayAddress(std::string sAddress); + bool displayAddress(std::string sAddress) const; static bool isWalletEnabled(); @@ -149,9 +149,7 @@ public: QString getWalletName() const; QString getDisplayName() const; - bool isMultiwallet(); - - AddressTableModel* getAddressTableModel() const { return addressTableModel; } + bool isMultiwallet() const; void refresh(bool pk_hash_only = false); diff --git a/src/random.h b/src/random.h index 40adf29010..b92c29f0be 100644 --- a/src/random.h +++ b/src/random.h @@ -232,6 +232,17 @@ public: /** Generate a random boolean. */ bool randbool() noexcept { return randbits(1); } + /** Return the time point advanced by a uniform random duration. */ + template <typename Tp> + Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) + { + using Dur = typename Tp::duration; + Dur dur{range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} : + range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} : + /* interval [0..0] */ Dur{0}}; + return time + dur; + } + // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } diff --git a/src/randomenv.cpp b/src/randomenv.cpp index 388841289a..c5dca346d6 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -57,8 +57,7 @@ #include <sys/auxv.h> #endif -//! Necessary on some platforms -extern char** environ; +extern char** environ; // NOLINT(readability-redundant-declaration): Necessary on some platforms namespace { @@ -363,10 +362,10 @@ void RandAddStaticEnv(CSHA512& hasher) #if HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS // Network interfaces - struct ifaddrs *ifad = NULL; + struct ifaddrs *ifad = nullptr; getifaddrs(&ifad); struct ifaddrs *ifit = ifad; - while (ifit != NULL) { + while (ifit != nullptr) { hasher.Write((const unsigned char*)&ifit, sizeof(ifit)); hasher.Write((const unsigned char*)ifit->ifa_name, strlen(ifit->ifa_name) + 1); hasher.Write((const unsigned char*)&ifit->ifa_flags, sizeof(ifit->ifa_flags)); diff --git a/src/rest.cpp b/src/rest.cpp index 2aeb9c68c3..22b5d2e074 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -32,8 +32,6 @@ #include <any> #include <string> -#include <boost/algorithm/string.hpp> - #include <univalue.h> using node::GetTransaction; @@ -191,8 +189,7 @@ static bool rest_headers(const std::any& context, return false; std::string param; const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); - std::vector<std::string> path; - boost::split(path, param, boost::is_any_of("/")); + std::vector<std::string> path = SplitString(param, '/'); std::string raw_count; std::string hashStr; @@ -362,8 +359,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const std::string param; const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); - std::vector<std::string> uri_parts; - boost::split(uri_parts, param, boost::is_any_of("/")); + std::vector<std::string> uri_parts = SplitString(param, '/'); std::string raw_count; std::string raw_blockhash; if (uri_parts.size() == 3) { @@ -483,8 +479,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash - std::vector<std::string> uri_parts; - boost::split(uri_parts, param, boost::is_any_of("/")); + std::vector<std::string> uri_parts = SplitString(param, '/'); if (uri_parts.size() != 2) { return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>"); } @@ -712,7 +707,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: if (param.length() > 1) { std::string strUriParams = param.substr(1); - boost::split(uriParts, strUriParams, boost::is_any_of("/")); + uriParts = SplitString(strUriParams, '/'); } // throw exception in case of an empty request diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f46e5e9fef..50bf764e53 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -37,6 +37,7 @@ #include <txmempool.h> #include <undo.h> #include <univalue.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/translation.h> #include <validation.h> @@ -759,8 +760,9 @@ static RPCHelpMan pruneblockchain() CChain& active_chain = active_chainstate.m_chain; int heightParam = request.params[0].get_int(); - if (heightParam < 0) + if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); + } // Height value more than a billion is too high to be a block height, and // too low to be a block time (corresponds to timestamp from Sep 2001). @@ -785,12 +787,10 @@ static RPCHelpMan pruneblockchain() } PruneBlockFilesManual(active_chainstate, height); - const CBlockIndex* block = active_chain.Tip(); - CHECK_NONFATAL(block); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - return uint64_t(block->nHeight); + const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())}; + const CBlockIndex* last_block{active_chainstate.m_blockman.GetFirstStoredBlock(block)}; + + return static_cast<uint64_t>(last_block->nHeight); }, }; } @@ -1200,30 +1200,23 @@ RPCHelpMan getblockchaininfo() LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); - const CBlockIndex* tip = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(tip); - const int height = tip->nHeight; + const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; + const int height{tip.nHeight}; UniValue obj(UniValue::VOBJ); obj.pushKV("chain", Params().NetworkIDString()); obj.pushKV("blocks", height); obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1); - obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); - obj.pushKV("difficulty", (double)GetDifficulty(tip)); - obj.pushKV("time", (int64_t)tip->nTime); - obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast()); - obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); + obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex()); + obj.pushKV("difficulty", GetDifficulty(&tip)); + obj.pushKV("time", tip.GetBlockTime()); + obj.pushKV("mediantime", tip.GetMedianTimePast()); + obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), &tip)); obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); - obj.pushKV("chainwork", tip->nChainWork.GetHex()); + obj.pushKV("chainwork", tip.nChainWork.GetHex()); obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage()); obj.pushKV("pruned", node::fPruneMode); if (node::fPruneMode) { - const CBlockIndex* block = tip; - CHECK_NONFATAL(block); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - - obj.pushKV("pruneheight", block->nHeight); + obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight); // if 0, execution bypasses the whole if block. bool automatic_pruning{args.GetIntArg("-prune", 0) != 1}; @@ -1235,7 +1228,7 @@ RPCHelpMan getblockchaininfo() if (IsDeprecatedRPCEnabled("softforks")) { const Consensus::Params& consensusParams = Params().GetConsensus(); - obj.pushKV("softforks", DeploymentInfo(tip, consensusParams)); + obj.pushKV("softforks", DeploymentInfo(&tip, consensusParams)); } obj.pushKV("warnings", GetWarnings(false).original); @@ -1309,8 +1302,7 @@ static RPCHelpMan getdeploymentinfo() const CBlockIndex* blockindex; if (request.params[0].isNull()) { - blockindex = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(blockindex); + blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); } else { const uint256 hash(ParseHashV(request.params[0], "blockhash")); blockindex = chainman.m_blockman.LookupBlockIndex(hash); @@ -1621,9 +1613,9 @@ static RPCHelpMan getchaintxstats() } } - const CBlockIndex* pindexPast = pindex->GetAncestor(pindex->nHeight - blockcount); - int nTimeDiff = pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast(); - int nTxDiff = pindex->nChainTx - pindexPast->nChainTx; + const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))}; + const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()}; + const int nTxDiff = pindex->nChainTx - past_block.nChainTx; UniValue ret(UniValue::VOBJ); ret.pushKV("time", (int64_t)pindex->nTime); @@ -1764,8 +1756,7 @@ static RPCHelpMan getblockstats() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - const CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)}; - CHECK_NONFATAL(pindex != nullptr); + const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))}; std::set<std::string> stats; if (!request.params[1].isNull()) { @@ -1776,8 +1767,8 @@ static RPCHelpMan getblockstats() } } - const CBlock block = GetBlockChecked(chainman.m_blockman, pindex); - const CBlockUndo blockUndo = GetUndoChecked(chainman.m_blockman, pindex); + const CBlock& block = GetBlockChecked(chainman.m_blockman, &pindex); + const CBlockUndo& blockUndo = GetUndoChecked(chainman.m_blockman, &pindex); const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default) const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; @@ -1895,25 +1886,25 @@ static RPCHelpMan getblockstats() ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0); ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); - ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); + ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex()); ret_all.pushKV("feerate_percentiles", feerates_res); - ret_all.pushKV("height", (int64_t)pindex->nHeight); + ret_all.pushKV("height", (int64_t)pindex.nHeight); ret_all.pushKV("ins", inputs); ret_all.pushKV("maxfee", maxfee); ret_all.pushKV("maxfeerate", maxfeerate); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); - ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); + ret_all.pushKV("mediantime", pindex.GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee); ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate); ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize); ret_all.pushKV("outs", outputs); - ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); + ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, Params().GetConsensus())); ret_all.pushKV("swtotal_size", swtotal_size); ret_all.pushKV("swtotal_weight", swtotal_weight); ret_all.pushKV("swtxs", swtxs); - ret_all.pushKV("time", pindex->GetBlockTime()); + ret_all.pushKV("time", pindex.GetBlockTime()); ret_all.pushKV("total_out", total_out); ret_all.pushKV("total_size", total_size); ret_all.pushKV("total_weight", total_weight); @@ -2131,10 +2122,8 @@ static RPCHelpMan scantxoutset() LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); - pcursor = active_chainstate.CoinsDB().Cursor(); - CHECK_NONFATAL(pcursor); - tip = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(tip); + pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor()); + tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point); result.pushKV("success", res); @@ -2292,6 +2281,12 @@ static RPCHelpMan dumptxoutset() FILE* file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; + if (afile.IsNull()) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Couldn't open file " + temppath.u8string() + " for writing."); + } + NodeContext& node = EnsureAnyNodeContext(request.context); UniValue result = CreateUTXOSnapshot( node, node.chainman->ActiveChainstate(), afile, path, temppath); @@ -2336,8 +2331,7 @@ UniValue CreateUTXOSnapshot( } pcursor = chainstate.CoinsDB().Cursor(); - tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock); - CHECK_NONFATAL(tip); + tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(stats.hashBlock)); } LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", @@ -2377,44 +2371,36 @@ UniValue CreateUTXOSnapshot( return result; } - -void RegisterBlockchainRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "blockchain", &getblockchaininfo, }, - { "blockchain", &getchaintxstats, }, - { "blockchain", &getblockstats, }, - { "blockchain", &getbestblockhash, }, - { "blockchain", &getblockcount, }, - { "blockchain", &getblock, }, - { "blockchain", &getblockfrompeer, }, - { "blockchain", &getblockhash, }, - { "blockchain", &getblockheader, }, - { "blockchain", &getchaintips, }, - { "blockchain", &getdifficulty, }, - { "blockchain", &getdeploymentinfo, }, - { "blockchain", &gettxout, }, - { "blockchain", &gettxoutsetinfo, }, - { "blockchain", &pruneblockchain, }, - { "blockchain", &verifychain, }, - - { "blockchain", &preciousblock, }, - { "blockchain", &scantxoutset, }, - { "blockchain", &getblockfilter, }, - - /* Not shown in help */ - { "hidden", &invalidateblock, }, - { "hidden", &reconsiderblock, }, - { "hidden", &waitfornewblock, }, - { "hidden", &waitforblock, }, - { "hidden", &waitforblockheight, }, - { "hidden", &syncwithvalidationinterfacequeue, }, - { "hidden", &dumptxoutset, }, -}; -// clang-format on +void RegisterBlockchainRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"blockchain", &getblockchaininfo}, + {"blockchain", &getchaintxstats}, + {"blockchain", &getblockstats}, + {"blockchain", &getbestblockhash}, + {"blockchain", &getblockcount}, + {"blockchain", &getblock}, + {"blockchain", &getblockfrompeer}, + {"blockchain", &getblockhash}, + {"blockchain", &getblockheader}, + {"blockchain", &getchaintips}, + {"blockchain", &getdifficulty}, + {"blockchain", &getdeploymentinfo}, + {"blockchain", &gettxout}, + {"blockchain", &gettxoutsetinfo}, + {"blockchain", &pruneblockchain}, + {"blockchain", &verifychain}, + {"blockchain", &preciousblock}, + {"blockchain", &scantxoutset}, + {"blockchain", &getblockfilter}, + {"hidden", &invalidateblock}, + {"hidden", &reconsiderblock}, + {"hidden", &waitfornewblock}, + {"hidden", &waitforblock}, + {"hidden", &waitforblockheight}, + {"hidden", &syncwithvalidationinterfacequeue}, + {"hidden", &dumptxoutset}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 82aa6f9516..4de7fc4205 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -62,15 +62,11 @@ static RPCHelpMan enumeratesigners() }; } -void RegisterSignerRPCCommands(CRPCTable &t) +void RegisterSignerRPCCommands(CRPCTable& t) { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "signer", &enumeratesigners, }, -}; -// clang-format on + static const CRPCCommand commands[]{ + {"signer", &enumeratesigners}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp new file mode 100644 index 0000000000..bfec0780aa --- /dev/null +++ b/src/rpc/fees.cpp @@ -0,0 +1,236 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <core_io.h> +#include <policy/feerate.h> +#include <policy/fees.h> +#include <policy/policy.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#include <txmempool.h> +#include <univalue.h> +#include <util/fees.h> +#include <util/system.h> +#include <validation.h> + +#include <algorithm> +#include <array> +#include <cmath> +#include <string> + +namespace node { +struct NodeContext; +} + +using node::NodeContext; + +static RPCHelpMan estimatesmartfee() +{ + return RPCHelpMan{"estimatesmartfee", + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible and return the number of blocks\n" + "for which the estimate is valid. Uses virtual transaction size as defined\n" + "in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n" + "Whether to return a more conservative estimate which also satisfies\n" + "a longer history. A conservative estimate potentially returns a\n" + "higher feerate and is more likely to be sufficient for the desired\n" + "target, but is not as responsive to short term drops in the\n" + "prevailing fee market. Must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", + { + {RPCResult::Type::STR, "", "error"}, + }}, + {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n" + "The request target will be clamped between 2 and the highest target\n" + "fee estimation is able to return based on how long it has been running.\n" + "An error is returned if not enough transactions and blocks\n" + "have been observed to make an estimate for any number of blocks."}, + }}, + RPCExamples{ + HelpExampleCli("estimatesmartfee", "6") + + HelpExampleRpc("estimatesmartfee", "6") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); + RPCTypeCheckArgument(request.params[0], UniValue::VNUM); + + CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); + const NodeContext& node = EnsureAnyNodeContext(request.context); + const CTxMemPool& mempool = EnsureMemPool(node); + + unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); + bool conservative = true; + if (!request.params[1].isNull()) { + FeeEstimateMode fee_mode; + if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); + } + if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; + } + + UniValue result(UniValue::VOBJ); + UniValue errors(UniValue::VARR); + FeeCalculation feeCalc; + CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; + if (feeRate != CFeeRate(0)) { + CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)}; + CFeeRate min_relay_feerate{::minRelayTxFee}; + feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); + result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); + } else { + errors.push_back("Insufficient data or no feerate found"); + result.pushKV("errors", errors); + } + result.pushKV("blocks", feeCalc.returnedTarget); + return result; + }, + }; +} + +static RPCHelpMan estimaterawfee() +{ + return RPCHelpMan{"estimaterawfee", + "\nWARNING: This interface is unstable and may disappear or change!\n" + "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" + "implementation of fee estimation. The parameters it can be called with\n" + "and the results it returns will change if the internal implementation changes.\n" + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" + "defined in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, + {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n" + "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" + "lower buckets."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", + { + {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", + { + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, + {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, + {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", + { + {RPCResult::Type::NUM, "startrange", "start of feerate range"}, + {RPCResult::Type::NUM, "endrange", "end of feerate range"}, + {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"}, + {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"}, + {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, + {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, + }}, + {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", + { + {RPCResult::Type::STR, "error", ""}, + }}, + }}, + {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + }}, + RPCExamples{ + HelpExampleCli("estimaterawfee", "6 0.9") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); + RPCTypeCheckArgument(request.params[0], UniValue::VNUM); + + CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); + + unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); + double threshold = 0.95; + if (!request.params[1].isNull()) { + threshold = request.params[1].get_real(); + } + if (threshold < 0 || threshold > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); + } + + UniValue result(UniValue::VOBJ); + + for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) { + CFeeRate feeRate; + EstimationResult buckets; + + // Only output results for horizons which track the target + if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue; + + feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets); + UniValue horizon_result(UniValue::VOBJ); + UniValue errors(UniValue::VARR); + UniValue passbucket(UniValue::VOBJ); + passbucket.pushKV("startrange", round(buckets.pass.start)); + passbucket.pushKV("endrange", round(buckets.pass.end)); + passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); + passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); + passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); + passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); + UniValue failbucket(UniValue::VOBJ); + failbucket.pushKV("startrange", round(buckets.fail.start)); + failbucket.pushKV("endrange", round(buckets.fail.end)); + failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); + failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); + failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); + failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); + + // CFeeRate(0) is used to indicate error as a return value from estimateRawFee + if (feeRate != CFeeRate(0)) { + horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); + horizon_result.pushKV("decay", buckets.decay); + horizon_result.pushKV("scale", (int)buckets.scale); + horizon_result.pushKV("pass", passbucket); + // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output + if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket); + } else { + // Output only information that is still meaningful in the event of error + horizon_result.pushKV("decay", buckets.decay); + horizon_result.pushKV("scale", (int)buckets.scale); + horizon_result.pushKV("fail", failbucket); + errors.push_back("Insufficient data or no feerate found which meets threshold"); + horizon_result.pushKV("errors", errors); + } + result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result); + } + return result; + }, + }; +} + +void RegisterFeeRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &estimatesmartfee}, + {"hidden", &estimaterawfee}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 1caf4ad96c..27080d3881 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -672,8 +672,6 @@ static RPCHelpMan savemempool() void RegisterMempoolRPCCommands(CRPCTable& t) { static const CRPCCommand commands[]{ - // category actor (function) - // -------- ---------------- {"rawtransactions", &sendrawtransaction}, {"rawtransactions", &testmempoolaccept}, {"blockchain", &getmempoolancestors}, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 211026c8d9..b552528951 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -17,7 +17,6 @@ #include <net.h> #include <node/context.h> #include <node/miner.h> -#include <policy/fees.h> #include <pow.h> #include <rpc/blockchain.h> #include <rpc/mining.h> @@ -30,7 +29,6 @@ #include <shutdown.h> #include <txmempool.h> #include <univalue.h> -#include <util/fees.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> @@ -1054,225 +1052,21 @@ static RPCHelpMan submitheader() }; } -static RPCHelpMan estimatesmartfee() +void RegisterMiningRPCCommands(CRPCTable& t) { - return RPCHelpMan{"estimatesmartfee", - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible and return the number of blocks\n" - "for which the estimate is valid. Uses virtual transaction size as defined\n" - "in BIP 141 (witness data is discounted).\n", - { - {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n" - " Whether to return a more conservative estimate which also satisfies\n" - " a longer history. A conservative estimate potentially returns a\n" - " higher feerate and is more likely to be sufficient for the desired\n" - " target, but is not as responsive to short term drops in the\n" - " prevailing fee market. Must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, - {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", - { - {RPCResult::Type::STR, "", "error"}, - }}, - {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n" - "The request target will be clamped between 2 and the highest target\n" - "fee estimation is able to return based on how long it has been running.\n" - "An error is returned if not enough transactions and blocks\n" - "have been observed to make an estimate for any number of blocks."}, - }}, - RPCExamples{ - HelpExampleCli("estimatesmartfee", "6") + - HelpExampleRpc("estimatesmartfee", "6") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); - - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); - const NodeContext& node = EnsureAnyNodeContext(request.context); - const CTxMemPool& mempool = EnsureMemPool(node); - - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); - unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); - bool conservative = true; - if (!request.params[1].isNull()) { - FeeEstimateMode fee_mode; - if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); - } - if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; - } - - UniValue result(UniValue::VOBJ); - UniValue errors(UniValue::VARR); - FeeCalculation feeCalc; - CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; - if (feeRate != CFeeRate(0)) { - CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)}; - CFeeRate min_relay_feerate{::minRelayTxFee}; - feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); - result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); - } else { - errors.push_back("Insufficient data or no feerate found"); - result.pushKV("errors", errors); - } - result.pushKV("blocks", feeCalc.returnedTarget); - return result; -}, + static const CRPCCommand commands[]{ + {"mining", &getnetworkhashps}, + {"mining", &getmininginfo}, + {"mining", &prioritisetransaction}, + {"mining", &getblocktemplate}, + {"mining", &submitblock}, + {"mining", &submitheader}, + + {"hidden", &generatetoaddress}, + {"hidden", &generatetodescriptor}, + {"hidden", &generateblock}, + {"hidden", &generate}, }; -} - -static RPCHelpMan estimaterawfee() -{ - return RPCHelpMan{"estimaterawfee", - "\nWARNING: This interface is unstable and may disappear or change!\n" - "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" - " implementation of fee estimation. The parameters it can be called with\n" - " and the results it returns will change if the internal implementation changes.\n" - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" - "defined in BIP 141 (witness data is discounted).\n", - { - {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, - {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n" - " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" - " lower buckets."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", - { - {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", - { - {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, - {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, - {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, - {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", - { - {RPCResult::Type::NUM, "startrange", "start of feerate range"}, - {RPCResult::Type::NUM, "endrange", "end of feerate range"}, - {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"}, - {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"}, - {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, - {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, - }}, - {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", - { - {RPCResult::Type::STR, "error", ""}, - }}, - }}, - {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - }}, - RPCExamples{ - HelpExampleCli("estimaterawfee", "6 0.9") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); - - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); - - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); - unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); - double threshold = 0.95; - if (!request.params[1].isNull()) { - threshold = request.params[1].get_real(); - } - if (threshold < 0 || threshold > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); - } - - UniValue result(UniValue::VOBJ); - - for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) { - CFeeRate feeRate; - EstimationResult buckets; - - // Only output results for horizons which track the target - if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue; - - feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets); - UniValue horizon_result(UniValue::VOBJ); - UniValue errors(UniValue::VARR); - UniValue passbucket(UniValue::VOBJ); - passbucket.pushKV("startrange", round(buckets.pass.start)); - passbucket.pushKV("endrange", round(buckets.pass.end)); - passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); - passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); - passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); - passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); - UniValue failbucket(UniValue::VOBJ); - failbucket.pushKV("startrange", round(buckets.fail.start)); - failbucket.pushKV("endrange", round(buckets.fail.end)); - failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); - failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); - failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); - failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); - - // CFeeRate(0) is used to indicate error as a return value from estimateRawFee - if (feeRate != CFeeRate(0)) { - horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); - horizon_result.pushKV("decay", buckets.decay); - horizon_result.pushKV("scale", (int)buckets.scale); - horizon_result.pushKV("pass", passbucket); - // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output - if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket); - } else { - // Output only information that is still meaningful in the event of error - horizon_result.pushKV("decay", buckets.decay); - horizon_result.pushKV("scale", (int)buckets.scale); - horizon_result.pushKV("fail", failbucket); - errors.push_back("Insufficient data or no feerate found which meets threshold"); - horizon_result.pushKV("errors",errors); - } - result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result); - } - return result; -}, - }; -} - -void RegisterMiningRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ----------------------- - { "mining", &getnetworkhashps, }, - { "mining", &getmininginfo, }, - { "mining", &prioritisetransaction, }, - { "mining", &getblocktemplate, }, - { "mining", &submitblock, }, - { "mining", &submitheader, }, - - - { "hidden", &generatetoaddress, }, - { "hidden", &generatetodescriptor, }, - { "hidden", &generateblock, }, - - { "util", &estimatesmartfee, }, - - { "hidden", &estimaterawfee, }, - { "hidden", &generate, }, -}; -// clang-format on for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp deleted file mode 100644 index 99671ee6ac..0000000000 --- a/src/rpc/misc.cpp +++ /dev/null @@ -1,824 +0,0 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-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 <httpserver.h> -#include <index/blockfilterindex.h> -#include <index/coinstatsindex.h> -#include <index/txindex.h> -#include <interfaces/chain.h> -#include <interfaces/echo.h> -#include <interfaces/init.h> -#include <interfaces/ipc.h> -#include <key_io.h> -#include <node/context.h> -#include <outputtype.h> -#include <rpc/blockchain.h> -#include <rpc/server.h> -#include <rpc/server_util.h> -#include <rpc/util.h> -#include <scheduler.h> -#include <script/descriptor.h> -#include <util/check.h> -#include <util/message.h> // For MessageSign(), MessageVerify() -#include <util/strencodings.h> -#include <util/syscall_sandbox.h> -#include <util/system.h> - -#include <optional> -#include <stdint.h> -#include <tuple> -#ifdef HAVE_MALLOC_INFO -#include <malloc.h> -#endif - -#include <univalue.h> - -using node::NodeContext; - -static RPCHelpMan validateaddress() -{ - return RPCHelpMan{ - "validateaddress", - "\nReturn information about the given bitcoin address.\n", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"}, - {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"}, - {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"}, - {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"}, - {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"}, - {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"}, - {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"}, - {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)", - { - {RPCResult::Type::NUM, "index", "index of a potential error"}, - }}, - } - }, - RPCExamples{ - HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + - HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string error_msg; - std::vector<int> error_locations; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); - const bool isValid = IsValidDestination(dest); - CHECK_NONFATAL(isValid == error_msg.empty()); - - UniValue ret(UniValue::VOBJ); - ret.pushKV("isvalid", isValid); - if (isValid) { - std::string currentAddress = EncodeDestination(dest); - ret.pushKV("address", currentAddress); - - CScript scriptPubKey = GetScriptForDestination(dest); - ret.pushKV("scriptPubKey", HexStr(scriptPubKey)); - - UniValue detail = DescribeAddress(dest); - ret.pushKVs(detail); - } else { - UniValue error_indices(UniValue::VARR); - for (int i : error_locations) error_indices.push_back(i); - ret.pushKV("error_locations", error_indices); - ret.pushKV("error", error_msg); - } - - return ret; -}, - }; -} - -static RPCHelpMan createmultisig() -{ - return RPCHelpMan{"createmultisig", - "\nCreates a multi-signature address with n signature of m keys required.\n" - "It returns a json object with the address and redeemScript.\n", - { - {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."}, - {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.", - { - {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"}, - }}, - {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "address", "The value of the new multisig address."}, - {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, - {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, - {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig", - { - {RPCResult::Type::STR, "", ""}, - }}, - } - }, - RPCExamples{ - "\nCreate a multisig address from 2 public keys\n" - + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - int required = request.params[0].get_int(); - - // Get the public keys - const UniValue& keys = request.params[1].get_array(); - std::vector<CPubKey> pubkeys; - for (unsigned int i = 0; i < keys.size(); ++i) { - if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { - pubkeys.push_back(HexToPubKey(keys[i].get_str())); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); - } - } - - // Get the output type - OutputType output_type = OutputType::LEGACY; - if (!request.params[2].isNull()) { - std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str()); - if (!parsed) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); - } else if (parsed.value() == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); - } - output_type = parsed.value(); - } - - // Construct using pay-to-script-hash: - FillableSigningProvider keystore; - CScript inner; - const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); - - // Make the descriptor - std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore); - - UniValue result(UniValue::VOBJ); - result.pushKV("address", EncodeDestination(dest)); - result.pushKV("redeemScript", HexStr(inner)); - result.pushKV("descriptor", descriptor->ToString()); - - UniValue warnings(UniValue::VARR); - if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { - // Only warns if the user has explicitly chosen an address type we cannot generate - warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); - } - if (warnings.size()) result.pushKV("warnings", warnings); - - return result; -}, - }; -} - -static RPCHelpMan getdescriptorinfo() -{ - const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; - - return RPCHelpMan{"getdescriptorinfo", - {"\nAnalyses a descriptor.\n"}, - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"}, - {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"}, - {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"}, - {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"}, - {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"}, - } - }, - RPCExamples{ - "Analyse a descriptor\n" + - HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + - HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VSTR}); - - FlatSigningProvider provider; - std::string error; - auto desc = Parse(request.params[0].get_str(), provider, error); - if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); - } - - UniValue result(UniValue::VOBJ); - result.pushKV("descriptor", desc->ToString()); - result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); - result.pushKV("isrange", desc->IsRange()); - result.pushKV("issolvable", desc->IsSolvable()); - result.pushKV("hasprivatekeys", provider.keys.size() > 0); - return result; -}, - }; -} - -static RPCHelpMan deriveaddresses() -{ - const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; - - return RPCHelpMan{"deriveaddresses", - {"\nDerives one or more addresses corresponding to an output descriptor.\n" - "Examples of output descriptors are:\n" - " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" - " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n" - " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" - " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" - "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" - "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n" - "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"}, - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, - {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR, "address", "the derived addresses"}, - } - }, - RPCExamples{ - "First three native segwit receive addresses\n" + - HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + - HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later - const std::string desc_str = request.params[0].get_str(); - - int64_t range_begin = 0; - int64_t range_end = 0; - - if (request.params.size() >= 2 && !request.params[1].isNull()) { - std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]); - } - - FlatSigningProvider key_provider; - std::string error; - auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); - if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); - } - - if (!desc->IsRange() && request.params.size() > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); - } - - if (desc->IsRange() && request.params.size() == 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor"); - } - - UniValue addresses(UniValue::VARR); - - for (int i = range_begin; i <= range_end; ++i) { - FlatSigningProvider provider; - std::vector<CScript> scripts; - if (!desc->Expand(i, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); - } - - for (const CScript &script : scripts) { - CTxDestination dest; - if (!ExtractDestination(script, dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); - } - - addresses.push_back(EncodeDestination(dest)); - } - } - - // This should not be possible, but an assert seems overkill: - if (addresses.empty()) { - throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); - } - - return addresses; -}, - }; -} - -static RPCHelpMan verifymessage() -{ - return RPCHelpMan{"verifymessage", - "Verify a signed message.", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, - {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, - {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."}, - }, - RPCResult{ - RPCResult::Type::BOOL, "", "If the signature is verified or not." - }, - RPCExamples{ - "\nUnlock the wallet for 30 seconds\n" - + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + - "\nCreate the signature\n" - + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + - "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - LOCK(cs_main); - - std::string strAddress = request.params[0].get_str(); - std::string strSign = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); - - switch (MessageVerify(strAddress, strSign, strMessage)) { - case MessageVerificationResult::ERR_INVALID_ADDRESS: - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); - case MessageVerificationResult::ERR_ADDRESS_NO_KEY: - throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); - case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: - throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); - case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: - case MessageVerificationResult::ERR_NOT_SIGNED: - return false; - case MessageVerificationResult::OK: - return true; - } - - return false; -}, - }; -} - -static RPCHelpMan signmessagewithprivkey() -{ - return RPCHelpMan{"signmessagewithprivkey", - "\nSign a message with the private key of an address\n", - { - {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."}, - {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, - }, - RPCResult{ - RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64" - }, - RPCExamples{ - "\nCreate the signature\n" - + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + - "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string strPrivkey = request.params[0].get_str(); - std::string strMessage = request.params[1].get_str(); - - CKey key = DecodeSecret(strPrivkey); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); - } - - std::string signature; - - if (!MessageSign(key, strMessage, signature)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); - } - - return signature; -}, - }; -} - -static RPCHelpMan setmocktime() -{ - return RPCHelpMan{"setmocktime", - "\nSet the local time to given timestamp (-regtest only)\n", - { - {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" - "Pass 0 to go back to using the system time."}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (!Params().IsMockableChain()) { - throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only"); - } - - // For now, don't change mocktime if we're in the middle of validation, as - // this could have an effect on mempool time-based eviction, as well as - // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). - // TODO: figure out the right way to synchronize around mocktime, and - // ensure all call sites of GetTime() are accessing this safely. - LOCK(cs_main); - - RPCTypeCheck(request.params, {UniValue::VNUM}); - const int64_t time{request.params[0].get_int64()}; - if (time < 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); - } - SetMockTime(time); - auto node_context = util::AnyPtr<NodeContext>(request.context); - if (node_context) { - for (const auto& chain_client : node_context->chain_clients) { - chain_client->setMockTime(time); - } - } - - return NullUniValue; -}, - }; -} - -#if defined(USE_SYSCALL_SANDBOX) -static RPCHelpMan invokedisallowedsyscall() -{ - return RPCHelpMan{ - "invokedisallowedsyscall", - "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n", - {}, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - if (!Params().IsTestChain()) { - throw std::runtime_error("invokedisallowedsyscall is used for testing only."); - } - TestDisallowedSandboxCall(); - return NullUniValue; - }, - }; -} -#endif // USE_SYSCALL_SANDBOX - -static RPCHelpMan mockscheduler() -{ - return RPCHelpMan{"mockscheduler", - "\nBump the scheduler into the future (-regtest only)\n", - { - {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (!Params().IsMockableChain()) { - throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); - } - - // check params are valid values - RPCTypeCheck(request.params, {UniValue::VNUM}); - int64_t delta_seconds = request.params[0].get_int64(); - if (delta_seconds <= 0 || delta_seconds > 3600) { - throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); - } - - auto node_context = util::AnyPtr<NodeContext>(request.context); - // protect against null pointer dereference - CHECK_NONFATAL(node_context); - CHECK_NONFATAL(node_context->scheduler); - node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); - - return NullUniValue; -}, - }; -} - -static UniValue RPCLockedMemoryInfo() -{ - LockedPool::Stats stats = LockedPoolManager::Instance().stats(); - UniValue obj(UniValue::VOBJ); - obj.pushKV("used", uint64_t(stats.used)); - obj.pushKV("free", uint64_t(stats.free)); - obj.pushKV("total", uint64_t(stats.total)); - obj.pushKV("locked", uint64_t(stats.locked)); - obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); - obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); - return obj; -} - -#ifdef HAVE_MALLOC_INFO -static std::string RPCMallocInfo() -{ - char *ptr = nullptr; - size_t size = 0; - FILE *f = open_memstream(&ptr, &size); - if (f) { - malloc_info(0, f); - fclose(f); - if (ptr) { - std::string rv(ptr, size); - free(ptr); - return rv; - } - } - return ""; -} -#endif - -static RPCHelpMan getmemoryinfo() -{ - /* Please, avoid using the word "pool" here in the RPC interface or help, - * as users will undoubtedly confuse it with the other "memory pool" - */ - return RPCHelpMan{"getmemoryinfo", - "Returns an object containing information about memory usage.\n", - { - {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n" - " - \"stats\" returns general statistics about memory usage in the daemon.\n" - " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, - }, - { - RPCResult{"mode \"stats\"", - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::OBJ, "locked", "Information about locked memory manager", - { - {RPCResult::Type::NUM, "used", "Number of bytes used"}, - {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"}, - {RPCResult::Type::NUM, "total", "Total number of bytes managed"}, - {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."}, - {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"}, - {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"}, - }}, - } - }, - RPCResult{"mode \"mallocinfo\"", - RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\"" - }, - }, - RPCExamples{ - HelpExampleCli("getmemoryinfo", "") - + HelpExampleRpc("getmemoryinfo", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); - if (mode == "stats") { - UniValue obj(UniValue::VOBJ); - obj.pushKV("locked", RPCLockedMemoryInfo()); - return obj; - } else if (mode == "mallocinfo") { -#ifdef HAVE_MALLOC_INFO - return RPCMallocInfo(); -#else - throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); -#endif - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); - } -}, - }; -} - -static void EnableOrDisableLogCategories(UniValue cats, bool enable) { - cats = cats.get_array(); - for (unsigned int i = 0; i < cats.size(); ++i) { - std::string cat = cats[i].get_str(); - - bool success; - if (enable) { - success = LogInstance().EnableCategory(cat); - } else { - success = LogInstance().DisableCategory(cat); - } - - if (!success) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); - } - } -} - -static RPCHelpMan logging() -{ - return RPCHelpMan{"logging", - "Gets and sets the logging configuration.\n" - "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" - "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" - "The arguments are evaluated in order \"include\", \"exclude\".\n" - "If an item is both included and excluded, it will thus end up being excluded.\n" - "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n" - "In addition, the following are available as category names with special meanings:\n" - " - \"all\", \"1\" : represent all logging categories.\n" - " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" - , - { - {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging", - { - {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, - }}, - {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging", - { - {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, - }}, - }, - RPCResult{ - RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status", - { - {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"}, - } - }, - RPCExamples{ - HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") - + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - uint32_t original_log_categories = LogInstance().GetCategoryMask(); - if (request.params[0].isArray()) { - EnableOrDisableLogCategories(request.params[0], true); - } - if (request.params[1].isArray()) { - EnableOrDisableLogCategories(request.params[1], false); - } - uint32_t updated_log_categories = LogInstance().GetCategoryMask(); - uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; - - // Update libevent logging if BCLog::LIBEVENT has changed. - if (changed_log_categories & BCLog::LIBEVENT) { - UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT)); - } - - UniValue result(UniValue::VOBJ); - for (const auto& logCatActive : LogInstance().LogCategoriesList()) { - result.pushKV(logCatActive.category, logCatActive.active); - } - - return result; -}, - }; -} - -static RPCHelpMan echo(const std::string& name) -{ - return RPCHelpMan{name, - "\nSimply echo back the input arguments. This command is for testing.\n" - "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" - "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " - "bitcoin-cli and the GUI. There is no server-side difference.", - { - {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - }, - RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (request.params[9].isStr()) { - CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug"); - } - - return request.params; -}, - }; -} - -static RPCHelpMan echo() { return echo("echo"); } -static RPCHelpMan echojson() { return echo("echojson"); } - -static RPCHelpMan echoipc() -{ - return RPCHelpMan{ - "echoipc", - "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" - "This command is for testing.\n", - {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, - RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, - RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + - HelpExampleRpc("echo", "\"Hello world\"")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; - std::unique_ptr<interfaces::Echo> echo; - if (interfaces::Ipc* ipc = local_init.ipc()) { - // Spawn a new bitcoin-node process and call makeEcho to get a - // client pointer to a interfaces::Echo instance running in - // that process. This is just for testing. A slightly more - // realistic test spawning a different executable instead of - // the same executable would add a new bitcoin-echo executable, - // and spawn bitcoin-echo below instead of bitcoin-node. But - // using bitcoin-node avoids the need to build and install a - // new executable just for this one test. - auto init = ipc->spawnProcess("bitcoin-node"); - echo = init->makeEcho(); - ipc->addCleanup(*echo, [init = init.release()] { delete init; }); - } else { - // IPC support is not available because this is a bitcoind - // process not a bitcoind-node process, so just create a local - // interfaces::Echo object and return it so the `echoipc` RPC - // method will work, and the python test calling `echoipc` - // can expect the same result. - echo = local_init.makeEcho(); - } - return echo->echo(request.params[0].get_str()); - }, - }; -} - -static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) -{ - UniValue ret_summary(UniValue::VOBJ); - if (!index_name.empty() && index_name != summary.name) return ret_summary; - - UniValue entry(UniValue::VOBJ); - entry.pushKV("synced", summary.synced); - entry.pushKV("best_block_height", summary.best_block_height); - ret_summary.pushKV(summary.name, entry); - return ret_summary; -} - -static RPCHelpMan getindexinfo() -{ - return RPCHelpMan{"getindexinfo", - "\nReturns the status of one or all available indices currently running in the node.\n", - { - {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, - }, - RPCResult{ - RPCResult::Type::OBJ_DYN, "", "", { - { - RPCResult::Type::OBJ, "name", "The name of the index", - { - {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"}, - {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"}, - } - }, - }, - }, - RPCExamples{ - HelpExampleCli("getindexinfo", "") - + HelpExampleRpc("getindexinfo", "") - + HelpExampleCli("getindexinfo", "txindex") - + HelpExampleRpc("getindexinfo", "txindex") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - UniValue result(UniValue::VOBJ); - const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str(); - - if (g_txindex) { - result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name)); - } - - if (g_coin_stats_index) { - result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name)); - } - - ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) { - result.pushKVs(SummaryToJSON(index.GetSummary(), index_name)); - }); - - return result; -}, - }; -} - -void RegisterMiscRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "control", &getmemoryinfo, }, - { "control", &logging, }, - { "util", &validateaddress, }, - { "util", &createmultisig, }, - { "util", &deriveaddresses, }, - { "util", &getdescriptorinfo, }, - { "util", &verifymessage, }, - { "util", &signmessagewithprivkey, }, - { "util", &getindexinfo, }, - - /* Not shown in help */ - { "hidden", &setmocktime, }, - { "hidden", &mockscheduler, }, - { "hidden", &echo, }, - { "hidden", &echojson, }, - { "hidden", &echoipc, }, -#if defined(USE_SYSCALL_SANDBOX) - { "hidden", &invokedisallowedsyscall, }, -#endif // USE_SYSCALL_SANDBOX -}; -// clang-format on - for (const auto& c : commands) { - t.appendCommand(c.name, &c); - } -} diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 225feabf14..09dc8eb3eb 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -156,7 +156,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n" "When a message type is not listed in this json object, the bytes received are 0.\n" "Only known message types can appear as keys in the object and all bytes received\n" - "of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."} + "of unknown message types are listed under '"+NET_MESSAGE_TYPE_OTHER+"'."} }}, {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" @@ -243,19 +243,19 @@ static RPCHelpMan getpeerinfo() } obj.pushKV("permissions", permissions); - UniValue sendPerMsgCmd(UniValue::VOBJ); - for (const auto& i : stats.mapSendBytesPerMsgCmd) { + UniValue sendPerMsgType(UniValue::VOBJ); + for (const auto& i : stats.mapSendBytesPerMsgType) { if (i.second > 0) - sendPerMsgCmd.pushKV(i.first, i.second); + sendPerMsgType.pushKV(i.first, i.second); } - obj.pushKV("bytessent_per_msg", sendPerMsgCmd); + obj.pushKV("bytessent_per_msg", sendPerMsgType); - UniValue recvPerMsgCmd(UniValue::VOBJ); - for (const auto& i : stats.mapRecvBytesPerMsgCmd) { + UniValue recvPerMsgType(UniValue::VOBJ); + for (const auto& i : stats.mapRecvBytesPerMsgType) { if (i.second > 0) - recvPerMsgCmd.pushKV(i.first, i.second); + recvPerMsgType.pushKV(i.first, i.second); } - obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd); + obj.pushKV("bytesrecv_per_msg", recvPerMsgType); obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type)); ret.push_back(obj); @@ -959,30 +959,25 @@ static RPCHelpMan addpeeraddress() }; } -void RegisterNetRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor - // --------------------- ----------------------- - { "network", &getconnectioncount, }, - { "network", &ping, }, - { "network", &getpeerinfo, }, - { "network", &addnode, }, - { "network", &disconnectnode, }, - { "network", &getaddednodeinfo, }, - { "network", &getnettotals, }, - { "network", &getnetworkinfo, }, - { "network", &setban, }, - { "network", &listbanned, }, - { "network", &clearbanned, }, - { "network", &setnetworkactive, }, - { "network", &getnodeaddresses, }, - - { "hidden", &addconnection, }, - { "hidden", &addpeeraddress, }, -}; -// clang-format on +void RegisterNetRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"network", &getconnectioncount}, + {"network", &ping}, + {"network", &getpeerinfo}, + {"network", &addnode}, + {"network", &disconnectnode}, + {"network", &getaddednodeinfo}, + {"network", &getnettotals}, + {"network", &getnetworkinfo}, + {"network", &setban}, + {"network", &listbanned}, + {"network", &clearbanned}, + {"network", &setnetworkactive}, + {"network", &getnodeaddresses}, + {"hidden", &addconnection}, + {"hidden", &addpeeraddress}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp new file mode 100644 index 0000000000..24697a606e --- /dev/null +++ b/src/rpc/node.cpp @@ -0,0 +1,440 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <chainparams.h> +#include <httpserver.h> +#include <index/blockfilterindex.h> +#include <index/coinstatsindex.h> +#include <index/txindex.h> +#include <interfaces/chain.h> +#include <interfaces/echo.h> +#include <interfaces/init.h> +#include <interfaces/ipc.h> +#include <node/context.h> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#include <scheduler.h> +#include <univalue.h> +#include <util/check.h> +#include <util/syscall_sandbox.h> +#include <util/system.h> + +#include <stdint.h> +#ifdef HAVE_MALLOC_INFO +#include <malloc.h> +#endif + +using node::NodeContext; + +static RPCHelpMan setmocktime() +{ + return RPCHelpMan{"setmocktime", + "\nSet the local time to given timestamp (-regtest only)\n", + { + {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" + "Pass 0 to go back to using the system time."}, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (!Params().IsMockableChain()) { + throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only"); + } + + // For now, don't change mocktime if we're in the middle of validation, as + // this could have an effect on mempool time-based eviction, as well as + // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). + // TODO: figure out the right way to synchronize around mocktime, and + // ensure all call sites of GetTime() are accessing this safely. + LOCK(cs_main); + + RPCTypeCheck(request.params, {UniValue::VNUM}); + const int64_t time{request.params[0].get_int64()}; + if (time < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); + } + SetMockTime(time); + auto node_context = util::AnyPtr<NodeContext>(request.context); + if (node_context) { + for (const auto& chain_client : node_context->chain_clients) { + chain_client->setMockTime(time); + } + } + + return NullUniValue; +}, + }; +} + +#if defined(USE_SYSCALL_SANDBOX) +static RPCHelpMan invokedisallowedsyscall() +{ + return RPCHelpMan{ + "invokedisallowedsyscall", + "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n", + {}, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + if (!Params().IsTestChain()) { + throw std::runtime_error("invokedisallowedsyscall is used for testing only."); + } + TestDisallowedSandboxCall(); + return NullUniValue; + }, + }; +} +#endif // USE_SYSCALL_SANDBOX + +static RPCHelpMan mockscheduler() +{ + return RPCHelpMan{"mockscheduler", + "\nBump the scheduler into the future (-regtest only)\n", + { + {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (!Params().IsMockableChain()) { + throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); + } + + // check params are valid values + RPCTypeCheck(request.params, {UniValue::VNUM}); + int64_t delta_seconds = request.params[0].get_int64(); + if (delta_seconds <= 0 || delta_seconds > 3600) { + throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); + } + + auto node_context = CHECK_NONFATAL(util::AnyPtr<NodeContext>(request.context)); + // protect against null pointer dereference + CHECK_NONFATAL(node_context->scheduler); + node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); + + return NullUniValue; +}, + }; +} + +static UniValue RPCLockedMemoryInfo() +{ + LockedPool::Stats stats = LockedPoolManager::Instance().stats(); + UniValue obj(UniValue::VOBJ); + obj.pushKV("used", uint64_t(stats.used)); + obj.pushKV("free", uint64_t(stats.free)); + obj.pushKV("total", uint64_t(stats.total)); + obj.pushKV("locked", uint64_t(stats.locked)); + obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); + obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); + return obj; +} + +#ifdef HAVE_MALLOC_INFO +static std::string RPCMallocInfo() +{ + char *ptr = nullptr; + size_t size = 0; + FILE *f = open_memstream(&ptr, &size); + if (f) { + malloc_info(0, f); + fclose(f); + if (ptr) { + std::string rv(ptr, size); + free(ptr); + return rv; + } + } + return ""; +} +#endif + +static RPCHelpMan getmemoryinfo() +{ + /* Please, avoid using the word "pool" here in the RPC interface or help, + * as users will undoubtedly confuse it with the other "memory pool" + */ + return RPCHelpMan{"getmemoryinfo", + "Returns an object containing information about memory usage.\n", + { + {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n" + " - \"stats\" returns general statistics about memory usage in the daemon.\n" + " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, + }, + { + RPCResult{"mode \"stats\"", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ, "locked", "Information about locked memory manager", + { + {RPCResult::Type::NUM, "used", "Number of bytes used"}, + {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"}, + {RPCResult::Type::NUM, "total", "Total number of bytes managed"}, + {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."}, + {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"}, + {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"}, + }}, + } + }, + RPCResult{"mode \"mallocinfo\"", + RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\"" + }, + }, + RPCExamples{ + HelpExampleCli("getmemoryinfo", "") + + HelpExampleRpc("getmemoryinfo", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); + if (mode == "stats") { + UniValue obj(UniValue::VOBJ); + obj.pushKV("locked", RPCLockedMemoryInfo()); + return obj; + } else if (mode == "mallocinfo") { +#ifdef HAVE_MALLOC_INFO + return RPCMallocInfo(); +#else + throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); +#endif + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); + } +}, + }; +} + +static void EnableOrDisableLogCategories(UniValue cats, bool enable) { + cats = cats.get_array(); + for (unsigned int i = 0; i < cats.size(); ++i) { + std::string cat = cats[i].get_str(); + + bool success; + if (enable) { + success = LogInstance().EnableCategory(cat); + } else { + success = LogInstance().DisableCategory(cat); + } + + if (!success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); + } + } +} + +static RPCHelpMan logging() +{ + return RPCHelpMan{"logging", + "Gets and sets the logging configuration.\n" + "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" + "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" + "The arguments are evaluated in order \"include\", \"exclude\".\n" + "If an item is both included and excluded, it will thus end up being excluded.\n" + "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n" + "In addition, the following are available as category names with special meanings:\n" + " - \"all\", \"1\" : represent all logging categories.\n" + " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" + , + { + {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging", + { + {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, + }}, + {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging", + { + {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, + }}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status", + { + {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"}, + } + }, + RPCExamples{ + HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + uint32_t original_log_categories = LogInstance().GetCategoryMask(); + if (request.params[0].isArray()) { + EnableOrDisableLogCategories(request.params[0], true); + } + if (request.params[1].isArray()) { + EnableOrDisableLogCategories(request.params[1], false); + } + uint32_t updated_log_categories = LogInstance().GetCategoryMask(); + uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; + + // Update libevent logging if BCLog::LIBEVENT has changed. + if (changed_log_categories & BCLog::LIBEVENT) { + UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT)); + } + + UniValue result(UniValue::VOBJ); + for (const auto& logCatActive : LogInstance().LogCategoriesList()) { + result.pushKV(logCatActive.category, logCatActive.active); + } + + return result; +}, + }; +} + +static RPCHelpMan echo(const std::string& name) +{ + return RPCHelpMan{name, + "\nSimply echo back the input arguments. This command is for testing.\n" + "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" + "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " + "bitcoin-cli and the GUI. There is no server-side difference.", + { + {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + }, + RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (request.params[9].isStr()) { + CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug"); + } + + return request.params; +}, + }; +} + +static RPCHelpMan echo() { return echo("echo"); } +static RPCHelpMan echojson() { return echo("echojson"); } + +static RPCHelpMan echoipc() +{ + return RPCHelpMan{ + "echoipc", + "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" + "This command is for testing.\n", + {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, + RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, + RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + + HelpExampleRpc("echo", "\"Hello world\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; + std::unique_ptr<interfaces::Echo> echo; + if (interfaces::Ipc* ipc = local_init.ipc()) { + // Spawn a new bitcoin-node process and call makeEcho to get a + // client pointer to a interfaces::Echo instance running in + // that process. This is just for testing. A slightly more + // realistic test spawning a different executable instead of + // the same executable would add a new bitcoin-echo executable, + // and spawn bitcoin-echo below instead of bitcoin-node. But + // using bitcoin-node avoids the need to build and install a + // new executable just for this one test. + auto init = ipc->spawnProcess("bitcoin-node"); + echo = init->makeEcho(); + ipc->addCleanup(*echo, [init = init.release()] { delete init; }); + } else { + // IPC support is not available because this is a bitcoind + // process not a bitcoind-node process, so just create a local + // interfaces::Echo object and return it so the `echoipc` RPC + // method will work, and the python test calling `echoipc` + // can expect the same result. + echo = local_init.makeEcho(); + } + return echo->echo(request.params[0].get_str()); + }, + }; +} + +static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) +{ + UniValue ret_summary(UniValue::VOBJ); + if (!index_name.empty() && index_name != summary.name) return ret_summary; + + UniValue entry(UniValue::VOBJ); + entry.pushKV("synced", summary.synced); + entry.pushKV("best_block_height", summary.best_block_height); + ret_summary.pushKV(summary.name, entry); + return ret_summary; +} + +static RPCHelpMan getindexinfo() +{ + return RPCHelpMan{"getindexinfo", + "\nReturns the status of one or all available indices currently running in the node.\n", + { + {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "", { + { + RPCResult::Type::OBJ, "name", "The name of the index", + { + {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"}, + {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"}, + } + }, + }, + }, + RPCExamples{ + HelpExampleCli("getindexinfo", "") + + HelpExampleRpc("getindexinfo", "") + + HelpExampleCli("getindexinfo", "txindex") + + HelpExampleRpc("getindexinfo", "txindex") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue result(UniValue::VOBJ); + const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str(); + + if (g_txindex) { + result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name)); + } + + if (g_coin_stats_index) { + result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name)); + } + + ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) { + result.pushKVs(SummaryToJSON(index.GetSummary(), index_name)); + }); + + return result; +}, + }; +} + +void RegisterNodeRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"control", &getmemoryinfo}, + {"control", &logging}, + {"util", &getindexinfo}, + {"hidden", &setmocktime}, + {"hidden", &mockscheduler}, + {"hidden", &echo}, + {"hidden", &echojson}, + {"hidden", &echoipc}, +#if defined(USE_SYSCALL_SANDBOX) + {"hidden", &invokedisallowedsyscall}, +#endif // USE_SYSCALL_SANDBOX + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp new file mode 100644 index 0000000000..1cf952d0c8 --- /dev/null +++ b/src/rpc/output_script.cpp @@ -0,0 +1,319 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2022 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 <key_io.h> +#include <outputtype.h> +#include <pubkey.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <script/descriptor.h> +#include <script/script.h> +#include <script/signingprovider.h> +#include <script/standard.h> +#include <tinyformat.h> +#include <univalue.h> +#include <util/check.h> +#include <util/strencodings.h> + +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <tuple> +#include <vector> + +namespace node { +struct NodeContext; +} +using node::NodeContext; + +static RPCHelpMan validateaddress() +{ + return RPCHelpMan{ + "validateaddress", + "\nReturn information about the given bitcoin address.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"}, + {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"}, + {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"}, + {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"}, + {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"}, + {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"}, + {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"}, + {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)", + { + {RPCResult::Type::NUM, "index", "index of a potential error"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + + HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string error_msg; + std::vector<int> error_locations; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); + const bool isValid = IsValidDestination(dest); + CHECK_NONFATAL(isValid == error_msg.empty()); + + UniValue ret(UniValue::VOBJ); + ret.pushKV("isvalid", isValid); + if (isValid) { + std::string currentAddress = EncodeDestination(dest); + ret.pushKV("address", currentAddress); + + CScript scriptPubKey = GetScriptForDestination(dest); + ret.pushKV("scriptPubKey", HexStr(scriptPubKey)); + + UniValue detail = DescribeAddress(dest); + ret.pushKVs(detail); + } else { + UniValue error_indices(UniValue::VARR); + for (int i : error_locations) error_indices.push_back(i); + ret.pushKV("error_locations", error_indices); + ret.pushKV("error", error_msg); + } + + return ret; + }, + }; +} + +static RPCHelpMan createmultisig() +{ + return RPCHelpMan{"createmultisig", + "\nCreates a multi-signature address with n signature of m keys required.\n" + "It returns a json object with the address and redeemScript.\n", + { + {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."}, + {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.", + { + {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"}, + }}, + {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The value of the new multisig address."}, + {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, + {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, + {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig", + { + {RPCResult::Type::STR, "", ""}, + }}, + } + }, + RPCExamples{ + "\nCreate a multisig address from 2 public keys\n" + + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + int required = request.params[0].get_int(); + + // Get the public keys + const UniValue& keys = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys.size(); ++i) { + if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys[i].get_str())); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); + } + } + + // Get the output type + OutputType output_type = OutputType::LEGACY; + if (!request.params[2].isNull()) { + std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str()); + if (!parsed) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); + } else if (parsed.value() == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); + } + output_type = parsed.value(); + } + + // Construct using pay-to-script-hash: + FillableSigningProvider keystore; + CScript inner; + const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); + + // Make the descriptor + std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore); + + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(dest)); + result.pushKV("redeemScript", HexStr(inner)); + result.pushKV("descriptor", descriptor->ToString()); + + UniValue warnings(UniValue::VARR); + if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { + // Only warns if the user has explicitly chosen an address type we cannot generate + warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); + } + if (warnings.size()) result.pushKV("warnings", warnings); + + return result; + }, + }; +} + +static RPCHelpMan getdescriptorinfo() +{ + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; + + return RPCHelpMan{"getdescriptorinfo", + {"\nAnalyses a descriptor.\n"}, + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"}, + {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"}, + {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"}, + {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"}, + {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"}, + } + }, + RPCExamples{ + "Analyse a descriptor\n" + + HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + + HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VSTR}); + + FlatSigningProvider provider; + std::string error; + auto desc = Parse(request.params[0].get_str(), provider, error); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("descriptor", desc->ToString()); + result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); + result.pushKV("isrange", desc->IsRange()); + result.pushKV("issolvable", desc->IsSolvable()); + result.pushKV("hasprivatekeys", provider.keys.size() > 0); + return result; + }, + }; +} + +static RPCHelpMan deriveaddresses() +{ + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; + + return RPCHelpMan{"deriveaddresses", + {"\nDerives one or more addresses corresponding to an output descriptor.\n" + "Examples of output descriptors are:\n" + " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" + " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n" + " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" + " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" + "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" + "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n" + "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"}, + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, + {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR, "address", "the derived addresses"}, + } + }, + RPCExamples{ + "First three native segwit receive addresses\n" + + HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + + HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later + const std::string desc_str = request.params[0].get_str(); + + int64_t range_begin = 0; + int64_t range_end = 0; + + if (request.params.size() >= 2 && !request.params[1].isNull()) { + std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]); + } + + FlatSigningProvider key_provider; + std::string error; + auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + + if (!desc->IsRange() && request.params.size() > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); + } + + if (desc->IsRange() && request.params.size() == 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor"); + } + + UniValue addresses(UniValue::VARR); + + for (int i = range_begin; i <= range_end; ++i) { + FlatSigningProvider provider; + std::vector<CScript> scripts; + if (!desc->Expand(i, key_provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); + } + + for (const CScript& script : scripts) { + CTxDestination dest; + if (!ExtractDestination(script, dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); + } + + addresses.push_back(EncodeDestination(dest)); + } + } + + // This should not be possible, but an assert seems overkill: + if (addresses.empty()) { + throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); + } + + return addresses; + }, + }; +} + +void RegisterOutputScriptRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &validateaddress}, + {"util", &createmultisig}, + {"util", &deriveaddresses}, + {"util", &getdescriptorinfo}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 8e4b396da9..e8713fbd2e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -33,6 +33,7 @@ #include <script/standard.h> #include <uint256.h> #include <util/bip32.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/string.h> #include <util/vector.h> @@ -466,7 +467,7 @@ static RPCHelpMan decodescript() // Should not be wrapped return false; } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); }()}; if (can_wrap_P2WSH) { UniValue sr(UniValue::VOBJ); @@ -566,7 +567,7 @@ static RPCHelpMan combinerawtransaction() sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out)); } } - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); UpdateInput(txin, sigdata); } @@ -1679,28 +1680,24 @@ static RPCHelpMan analyzepsbt() }; } -void RegisterRawTransactionRPCCommands(CRPCTable &t) +void RegisterRawTransactionRPCCommands(CRPCTable& t) { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ----------------------- - { "rawtransactions", &getrawtransaction, }, - { "rawtransactions", &createrawtransaction, }, - { "rawtransactions", &decoderawtransaction, }, - { "rawtransactions", &decodescript, }, - { "rawtransactions", &combinerawtransaction, }, - { "rawtransactions", &signrawtransactionwithkey, }, - { "rawtransactions", &decodepsbt, }, - { "rawtransactions", &combinepsbt, }, - { "rawtransactions", &finalizepsbt, }, - { "rawtransactions", &createpsbt, }, - { "rawtransactions", &converttopsbt, }, - { "rawtransactions", &utxoupdatepsbt, }, - { "rawtransactions", &joinpsbts, }, - { "rawtransactions", &analyzepsbt, }, -}; -// clang-format on + static const CRPCCommand commands[]{ + {"rawtransactions", &getrawtransaction}, + {"rawtransactions", &createrawtransaction}, + {"rawtransactions", &decoderawtransaction}, + {"rawtransactions", &decodescript}, + {"rawtransactions", &combinerawtransaction}, + {"rawtransactions", &signrawtransactionwithkey}, + {"rawtransactions", &decodepsbt}, + {"rawtransactions", &combinepsbt}, + {"rawtransactions", &finalizepsbt}, + {"rawtransactions", &createpsbt}, + {"rawtransactions", &converttopsbt}, + {"rawtransactions", &utxoupdatepsbt}, + {"rawtransactions", &joinpsbts}, + {"rawtransactions", &analyzepsbt}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/register.h b/src/rpc/register.h index 5a604ad428..301f410da3 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -10,26 +10,32 @@ class CRPCTable; void RegisterBlockchainRPCCommands(CRPCTable &tableRPC); +void RegisterFeeRPCCommands(CRPCTable&); void RegisterMempoolRPCCommands(CRPCTable&); -void RegisterTxoutProofRPCCommands(CRPCTable&); -void RegisterNetRPCCommands(CRPCTable &tableRPC); -void RegisterMiscRPCCommands(CRPCTable &tableRPC); void RegisterMiningRPCCommands(CRPCTable &tableRPC); +void RegisterNodeRPCCommands(CRPCTable&); +void RegisterNetRPCCommands(CRPCTable&); +void RegisterOutputScriptRPCCommands(CRPCTable&); void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC); +void RegisterSignMessageRPCCommands(CRPCTable&); void RegisterSignerRPCCommands(CRPCTable &tableRPC); +void RegisterTxoutProofRPCCommands(CRPCTable&); static inline void RegisterAllCoreRPCCommands(CRPCTable &t) { RegisterBlockchainRPCCommands(t); + RegisterFeeRPCCommands(t); RegisterMempoolRPCCommands(t); - RegisterTxoutProofRPCCommands(t); - RegisterNetRPCCommands(t); - RegisterMiscRPCCommands(t); RegisterMiningRPCCommands(t); + RegisterNodeRPCCommands(t); + RegisterNetRPCCommands(t); + RegisterOutputScriptRPCCommands(t); RegisterRawTransactionRPCCommands(t); + RegisterSignMessageRPCCommands(t); #ifdef ENABLE_EXTERNAL_SIGNER RegisterSignerRPCCommands(t); #endif // ENABLE_EXTERNAL_SIGNER + RegisterTxoutProofRPCCommands(t); } #endif // BITCOIN_RPC_REGISTER_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 333ed6f5da..3817a4a45c 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -9,10 +9,9 @@ #include <shutdown.h> #include <sync.h> #include <util/strencodings.h> +#include <util/string.h> #include <util/system.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> #include <boost/signals2/signal.hpp> #include <cassert> @@ -248,17 +247,13 @@ static RPCHelpMan getrpcinfo() }; } -// clang-format off -static const CRPCCommand vRPCCommands[] = -{ // category actor (function) - // --------------------- ----------------------- +static const CRPCCommand vRPCCommands[]{ /* Overall control/query calls */ - { "control", &getrpcinfo, }, - { "control", &help, }, - { "control", &stop, }, - { "control", &uptime, }, + {"control", &getrpcinfo}, + {"control", &help}, + {"control", &stop}, + {"control", &uptime}, }; -// clang-format on CRPCTable::CRPCTable() { @@ -407,8 +402,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c // Process expected parameters. int hole = 0; for (const std::string &argNamePattern: argNames) { - std::vector<std::string> vargNames; - boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|")); + std::vector<std::string> vargNames = SplitString(argNamePattern, '|'); auto fr = argsIn.end(); for (const std::string & argName : vargNames) { fr = argsIn.find(argName); diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp new file mode 100644 index 0000000000..8c752ba1fd --- /dev/null +++ b/src/rpc/signmessage.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2022 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 <key.h> +#include <key_io.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <univalue.h> +#include <util/message.h> + +#include <string> + +static RPCHelpMan verifymessage() +{ + return RPCHelpMan{"verifymessage", + "Verify a signed message.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, + {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, + {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."}, + }, + RPCResult{ + RPCResult::Type::BOOL, "", "If the signature is verified or not." + }, + RPCExamples{ + "\nUnlock the wallet for 30 seconds\n" + + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + + "\nCreate the signature\n" + + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string strAddress = request.params[0].get_str(); + std::string strSign = request.params[1].get_str(); + std::string strMessage = request.params[2].get_str(); + + switch (MessageVerify(strAddress, strSign, strMessage)) { + case MessageVerificationResult::ERR_INVALID_ADDRESS: + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + case MessageVerificationResult::ERR_ADDRESS_NO_KEY: + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: + throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); + case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: + case MessageVerificationResult::ERR_NOT_SIGNED: + return false; + case MessageVerificationResult::OK: + return true; + } + + return false; + }, + }; +} + +static RPCHelpMan signmessagewithprivkey() +{ + return RPCHelpMan{"signmessagewithprivkey", + "\nSign a message with the private key of an address\n", + { + {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."}, + {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, + }, + RPCResult{ + RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64" + }, + RPCExamples{ + "\nCreate the signature\n" + + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string strPrivkey = request.params[0].get_str(); + std::string strMessage = request.params[1].get_str(); + + CKey key = DecodeSecret(strPrivkey); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + } + + std::string signature; + + if (!MessageSign(key, strMessage, signature)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + } + + return signature; + }, + }; +} + +void RegisterSignMessageRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &verifymessage}, + {"util", &signmessagewithprivkey}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index a5443b0329..d16820baeb 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -172,8 +172,6 @@ static RPCHelpMan verifytxoutproof() void RegisterTxoutProofRPCCommands(CRPCTable& t) { static const CRPCCommand commands[]{ - // category actor (function) - // -------- ---------------- {"blockchain", &gettxoutproof}, {"blockchain", &verifytxoutproof}, }; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 01fae140cc..ef3e58494e 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -9,15 +9,13 @@ #include <script/descriptor.h> #include <script/signingprovider.h> #include <tinyformat.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> #include <tuple> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; @@ -513,8 +511,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP { std::set<std::string> named_args; for (const auto& arg : m_args) { - std::vector<std::string> names; - boost::split(names, arg.m_names, boost::is_any_of("|")); + std::vector<std::string> names = SplitString(arg.m_names, '|'); // Should have unique named arguments for (const std::string& name : names) { CHECK_NONFATAL(named_args.insert(name).second); @@ -542,7 +539,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP // Null values are accepted in all arguments break; default: - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); break; } } @@ -665,8 +662,7 @@ UniValue RPCHelpMan::GetArgMap() const UniValue arr{UniValue::VARR}; for (int i{0}; i < int(m_args.size()); ++i) { const auto& arg = m_args.at(i); - std::vector<std::string> arg_names; - boost::split(arg_names, arg.m_names, boost::is_any_of("|")); + std::vector<std::string> arg_names = SplitString(arg.m_names, '|'); for (const auto& arg_name : arg_names) { UniValue map{UniValue::VARR}; map.push_back(m_name); @@ -793,7 +789,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const return; } case Type::ANY: { - CHECK_NONFATAL(false); // Only for testing + NONFATAL_UNREACHABLE(); // Only for testing } case Type::NONE: { sections.PushSection({indent + "null" + maybe_separator, Description("json null")}); @@ -860,7 +856,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const return; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } bool RPCResult::MatchesType(const UniValue& result) const @@ -938,7 +934,7 @@ bool RPCResult::MatchesType(const UniValue& result) const return true; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } void RPCResult::CheckInnerDoc() const @@ -984,9 +980,9 @@ std::string RPCArg::ToStringObj(const bool oneline) const case Type::OBJ: case Type::OBJ_USER_KEYS: // Currently unused, so avoid writing dead code - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } std::string RPCArg::ToString(const bool oneline) const @@ -1021,7 +1017,7 @@ std::string RPCArg::ToString(const bool oneline) const return "[" + res + "...]"; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } static std::pair<int64_t, int64_t> ParseRange(const UniValue& value) diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 197d009f7c..570b417ff5 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -4,11 +4,11 @@ #include <scheduler.h> -#include <random.h> +#include <sync.h> #include <util/syscall_sandbox.h> #include <util/time.h> -#include <assert.h> +#include <cassert> #include <functional> #include <utility> @@ -43,7 +43,7 @@ void CScheduler::serviceQueue() // the time of the first item on the queue: while (!shouldStop() && !taskQueue.empty()) { - std::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first; + std::chrono::steady_clock::time_point timeToWaitFor = taskQueue.begin()->first; if (newTaskScheduled.wait_until(lock, timeToWaitFor) == std::cv_status::timeout) { break; // Exit loop after timeout, it means we reached the time of the event } @@ -72,7 +72,7 @@ void CScheduler::serviceQueue() newTaskScheduled.notify_one(); } -void CScheduler::schedule(CScheduler::Function f, std::chrono::system_clock::time_point t) +void CScheduler::schedule(CScheduler::Function f, std::chrono::steady_clock::time_point t) { { LOCK(newTaskMutex); @@ -89,7 +89,7 @@ void CScheduler::MockForward(std::chrono::seconds delta_seconds) LOCK(newTaskMutex); // use temp_queue to maintain updated schedule - std::multimap<std::chrono::system_clock::time_point, Function> temp_queue; + std::multimap<std::chrono::steady_clock::time_point, Function> temp_queue; for (const auto& element : taskQueue) { temp_queue.emplace_hint(temp_queue.cend(), element.first - delta_seconds, element.second); @@ -114,8 +114,8 @@ void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds scheduleFromNow([this, f, delta] { Repeat(*this, f, delta); }, delta); } -size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const +size_t CScheduler::getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const { LOCK(newTaskMutex); size_t result = taskQueue.size(); @@ -143,7 +143,7 @@ void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() if (m_are_callbacks_running) return; if (m_callbacks_pending.empty()) return; } - m_pscheduler->schedule(std::bind(&SingleThreadedSchedulerClient::ProcessQueue, this), std::chrono::system_clock::now()); + m_scheduler.schedule([this] { this->ProcessQueue(); }, std::chrono::steady_clock::now()); } void SingleThreadedSchedulerClient::ProcessQueue() @@ -179,8 +179,6 @@ void SingleThreadedSchedulerClient::ProcessQueue() void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void()> func) { - assert(m_pscheduler); - { LOCK(m_callbacks_mutex); m_callbacks_pending.emplace_back(std::move(func)); @@ -190,7 +188,7 @@ void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void()> func void SingleThreadedSchedulerClient::EmptyQueue() { - assert(!m_pscheduler->AreThreadsServicingQueue()); + assert(!m_scheduler.AreThreadsServicingQueue()); bool should_continue = true; while (should_continue) { ProcessQueue(); diff --git a/src/scheduler.h b/src/scheduler.h index bb0abfbf7a..b8245f97ed 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -5,13 +5,18 @@ #ifndef BITCOIN_SCHEDULER_H #define BITCOIN_SCHEDULER_H +#include <attributes.h> +#include <sync.h> +#include <threadsafety.h> + +#include <chrono> #include <condition_variable> +#include <cstddef> #include <functional> #include <list> #include <map> #include <thread> - -#include <sync.h> +#include <utility> /** * Simple class for background tasks that should be run @@ -41,12 +46,12 @@ public: typedef std::function<void()> Function; /** Call func at/after time t */ - void schedule(Function f, std::chrono::system_clock::time_point t); + void schedule(Function f, std::chrono::steady_clock::time_point t); /** Call f once after the delta has passed */ void scheduleFromNow(Function f, std::chrono::milliseconds delta) { - schedule(std::move(f), std::chrono::system_clock::now() + delta); + schedule(std::move(f), std::chrono::steady_clock::now() + delta); } /** @@ -88,8 +93,8 @@ public: * Returns number of tasks waiting to be serviced, * and first and last task times */ - size_t getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const; + size_t getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const; /** Returns true if there are threads actively running in serviceQueue() */ bool AreThreadsServicingQueue() const; @@ -97,7 +102,7 @@ public: private: mutable Mutex newTaskMutex; std::condition_variable newTaskScheduled; - std::multimap<std::chrono::system_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); + std::multimap<std::chrono::steady_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); int nThreadsServicingQueue GUARDED_BY(newTaskMutex){0}; bool stopRequested GUARDED_BY(newTaskMutex){false}; bool stopWhenEmpty GUARDED_BY(newTaskMutex){false}; @@ -117,7 +122,7 @@ private: class SingleThreadedSchedulerClient { private: - CScheduler* m_pscheduler; + CScheduler& m_scheduler; Mutex m_callbacks_mutex; std::list<std::function<void()>> m_callbacks_pending GUARDED_BY(m_callbacks_mutex); @@ -127,7 +132,7 @@ private: void ProcessQueue(); public: - explicit SingleThreadedSchedulerClient(CScheduler* pschedulerIn) : m_pscheduler(pschedulerIn) {} + explicit SingleThreadedSchedulerClient(CScheduler& scheduler LIFETIMEBOUND) : m_scheduler{scheduler} {} /** * Add a callback to be executed. Callbacks are executed serially diff --git a/src/script/interpreter.h b/src/script/interpreter.h index fbd904fb7b..adf454aa15 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -151,7 +151,7 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i struct PrecomputedTransactionData { // BIP341 precomputed data. - // These are single-SHA256, see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-15. + // These are single-SHA256, see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-16. uint256 m_prevouts_single_hash; uint256 m_sequences_single_hash; uint256 m_outputs_single_hash; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index d77515f16c..7328e8d1ad 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -18,16 +18,16 @@ typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type) - : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, checker{txTo, nIn, amount, MissingDataBehavior::FAIL}, +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction& tx, unsigned int input_idx, const CAmount& amount, int hash_type) + : m_txto{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, checker{&m_txto, nIn, amount, MissingDataBehavior::FAIL}, m_txdata(nullptr) { } -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type) - : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, - checker{txdata ? MutableTransactionSignatureChecker{txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL} : - MutableTransactionSignatureChecker{txTo, nIn, amount, MissingDataBehavior::FAIL}}, +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction& tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type) + : m_txto{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, + checker{txdata ? MutableTransactionSignatureChecker{&m_txto, nIn, amount, *txdata, MissingDataBehavior::FAIL} : + MutableTransactionSignatureChecker{&m_txto, nIn, amount, MissingDataBehavior::FAIL}}, m_txdata(txdata) { } @@ -50,7 +50,7 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid // 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, hashtype, amount, sigversion, m_txdata); + uint256 hash = SignatureHash(scriptCode, m_txto, nIn, hashtype, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)hashtype); @@ -80,7 +80,7 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& execdata.m_tapleaf_hash = *leaf_hash; } uint256 hash; - if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; + if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; sig.resize(64); // Use uint256{} as aux_rnd for now. if (!key.SignSchnorr(hash, sig, merkle_root, {})) return false; @@ -540,7 +540,7 @@ bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, C { assert(nIn < txTo.vin.size()); - MutableTransactionSignatureCreator creator(&txTo, nIn, amount, nHashType); + MutableTransactionSignatureCreator creator(txTo, nIn, amount, nHashType); SignatureData sigdata; bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); @@ -679,7 +679,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, &txdata, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); diff --git a/src/script/sign.h b/src/script/sign.h index 7301826ba5..71203d08ec 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H +#include <attributes.h> #include <coins.h> #include <hash.h> #include <pubkey.h> @@ -34,8 +35,9 @@ public: }; /** A signature creator for transactions. */ -class MutableTransactionSignatureCreator : public BaseSignatureCreator { - const CMutableTransaction* txTo; +class MutableTransactionSignatureCreator : public BaseSignatureCreator +{ + const CMutableTransaction& m_txto; unsigned int nIn; int nHashType; CAmount amount; @@ -43,8 +45,8 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { const PrecomputedTransactionData* m_txdata; public: - MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type); - MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type); + MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, int hash_type); + MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type); 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; diff --git a/src/serialize.h b/src/serialize.h index 44bb471f25..a1cce78451 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -472,10 +472,10 @@ struct CustomUintFormatter if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range"); if (BigEndian) { uint64_t raw = htobe64(v); - s.write({BytePtr(&raw) + 8 - Bytes, Bytes}); + s.write({AsBytePtr(&raw) + 8 - Bytes, Bytes}); } else { uint64_t raw = htole64(v); - s.write({BytePtr(&raw), Bytes}); + s.write({AsBytePtr(&raw), Bytes}); } } @@ -485,10 +485,10 @@ struct CustomUintFormatter static_assert(std::numeric_limits<U>::max() >= MAX && std::numeric_limits<U>::min() <= 0, "Assigned type too small"); uint64_t raw = 0; if (BigEndian) { - s.read({BytePtr(&raw) + 8 - Bytes, Bytes}); + s.read({AsBytePtr(&raw) + 8 - Bytes, Bytes}); v = static_cast<I>(be64toh(raw)); } else { - s.read({BytePtr(&raw), Bytes}); + s.read({AsBytePtr(&raw), Bytes}); v = static_cast<I>(le64toh(raw)); } } diff --git a/src/shutdown.cpp b/src/shutdown.cpp index 93f04e9021..fdf726b5f1 100644 --- a/src/shutdown.cpp +++ b/src/shutdown.cpp @@ -5,13 +5,15 @@ #include <shutdown.h> +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <logging.h> #include <node/ui_interface.h> #include <util/tokenpipe.h> #include <warnings.h> -#include <config/bitcoin-config.h> - #include <assert.h> #include <atomic> #ifdef WIN32 diff --git a/src/span.h b/src/span.h index b627b993c2..444d63001f 100644 --- a/src/span.h +++ b/src/span.h @@ -245,19 +245,19 @@ T& SpanPopBack(Span<T>& span) //! Convert a data pointer to a std::byte data pointer. //! Where possible, please use the safer AsBytes helpers. -inline const std::byte* BytePtr(const void* data) { return reinterpret_cast<const std::byte*>(data); } -inline std::byte* BytePtr(void* data) { return reinterpret_cast<std::byte*>(data); } +inline const std::byte* AsBytePtr(const void* data) { return reinterpret_cast<const std::byte*>(data); } +inline std::byte* AsBytePtr(void* data) { return reinterpret_cast<std::byte*>(data); } // From C++20 as_bytes and as_writeable_bytes template <typename T> Span<const std::byte> AsBytes(Span<T> s) noexcept { - return {BytePtr(s.data()), s.size_bytes()}; + return {AsBytePtr(s.data()), s.size_bytes()}; } template <typename T> Span<std::byte> AsWritableBytes(Span<T> s) noexcept { - return {BytePtr(s.data()), s.size_bytes()}; + return {AsBytePtr(s.data()), s.size_bytes()}; } template <typename V> diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index a9aa928082..c6bd685189 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -37,7 +37,7 @@ struct secure_allocator : public std::allocator<T> { typedef secure_allocator<_Other> other; }; - T* allocate(std::size_t n, const void* hint = 0) + T* allocate(std::size_t n, const void* hint = nullptr) { T* allocation = static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n)); if (!allocation) { diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index 5fab7f0d1e..c6109dfeb0 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -22,20 +22,16 @@ BOOST_AUTO_TEST_CASE(base32_testvectors) BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); strEnc = EncodeBase32(vstrIn[i], false); BOOST_CHECK_EQUAL(strEnc, vstrOutNoPadding[i]); - std::string strDec = DecodeBase32(vstrOut[i]); - BOOST_CHECK_EQUAL(strDec, vstrIn[i]); + auto dec = DecodeBase32(vstrOut[i]); + BOOST_REQUIRE(dec); + BOOST_CHECK_MESSAGE(MakeByteSpan(*dec) == MakeByteSpan(vstrIn[i]), vstrOut[i]); } // Decoding strings with embedded NUL characters should fail - bool failure; - (void)DecodeBase32("invalid\0"s, &failure); // correct size, invalid due to \0 - BOOST_CHECK(failure); - (void)DecodeBase32("AWSX3VPP"s, &failure); // valid - BOOST_CHECK(!failure); - (void)DecodeBase32("AWSX3VPP\0invalid"s, &failure); // correct size, invalid due to \0 - BOOST_CHECK(failure); - (void)DecodeBase32("AWSX3VPPinvalid"s, &failure); // invalid size - BOOST_CHECK(failure); + BOOST_CHECK(!DecodeBase32("invalid\0"s)); // correct size, invalid due to \0 + BOOST_CHECK(DecodeBase32("AWSX3VPP"s)); // valid + BOOST_CHECK(!DecodeBase32("AWSX3VPP\0invalid"s)); // correct size, invalid due to \0 + BOOST_CHECK(!DecodeBase32("AWSX3VPPinvalid"s)); // invalid size } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index 6ee1b83691..54a02c6bf8 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -19,8 +19,9 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) { std::string strEnc = EncodeBase64(vstrIn[i]); BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); - std::string strDec = DecodeBase64(strEnc); - BOOST_CHECK_EQUAL(strDec, vstrIn[i]); + auto dec = DecodeBase64(strEnc); + BOOST_REQUIRE(dec); + BOOST_CHECK_MESSAGE(MakeByteSpan(*dec) == MakeByteSpan(vstrIn[i]), vstrOut[i]); } { @@ -34,15 +35,10 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) } // Decoding strings with embedded NUL characters should fail - bool failure; - (void)DecodeBase64("invalid\0"s, &failure); - BOOST_CHECK(failure); - (void)DecodeBase64("nQB/pZw="s, &failure); - BOOST_CHECK(!failure); - (void)DecodeBase64("nQB/pZw=\0invalid"s, &failure); - BOOST_CHECK(failure); - (void)DecodeBase64("nQB/pZw=invalid\0"s, &failure); - BOOST_CHECK(failure); + BOOST_CHECK(!DecodeBase64("invalid\0"s)); + BOOST_CHECK(DecodeBase64("nQB/pZw="s)); + BOOST_CHECK(!DecodeBase64("nQB/pZw=\0invalid"s)); + BOOST_CHECK(!DecodeBase64("nQB/pZw=invalid\0"s)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 0d95bd7670..79d6b94dff 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -42,7 +42,7 @@ struct FakeCheck { { return true; } - void swap(FakeCheck& x){}; + void swap(FakeCheck& x) noexcept {}; }; struct FakeCheckCheckCompletion { @@ -52,7 +52,7 @@ struct FakeCheckCheckCompletion { n_calls.fetch_add(1, std::memory_order_relaxed); return true; } - void swap(FakeCheckCheckCompletion& x){}; + void swap(FakeCheckCheckCompletion& x) noexcept {}; }; struct FailingCheck { @@ -63,7 +63,7 @@ struct FailingCheck { { return !fails; } - void swap(FailingCheck& x) + void swap(FailingCheck& x) noexcept { std::swap(fails, x.fails); }; @@ -81,7 +81,10 @@ struct UniqueCheck { results.insert(check_id); return true; } - void swap(UniqueCheck& x) { std::swap(x.check_id, check_id); }; + void swap(UniqueCheck& x) noexcept + { + std::swap(x.check_id, check_id); + }; }; @@ -109,7 +112,10 @@ struct MemoryCheck { { fake_allocated_memory.fetch_sub(b, std::memory_order_relaxed); }; - void swap(MemoryCheck& x) { std::swap(b, x.b); }; + void swap(MemoryCheck& x) noexcept + { + std::swap(b, x.b); + }; }; struct FrozenCleanupCheck { @@ -133,7 +139,10 @@ struct FrozenCleanupCheck { cv.wait(l, []{ return nFrozen.load(std::memory_order_relaxed) == 0;}); } } - void swap(FrozenCleanupCheck& x){std::swap(should_freeze, x.should_freeze);}; + void swap(FrozenCleanupCheck& x) noexcept + { + std::swap(should_freeze, x.should_freeze); + }; }; // Static Allocations diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 69fcd73429..7b492fd1da 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -15,7 +15,6 @@ #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> @@ -33,8 +32,6 @@ static CService ip(uint32_t i) return CService(CNetAddr(s), Params().GetDefaultPort()); } -void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds); - BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // Test eviction of an outbound peer whose chain never advances @@ -196,7 +193,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // Update the last announced block time for the last // peer, and check that the next newest node gets evicted. - UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); + peerLogic->UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay - 1; ++i) { @@ -429,121 +426,4 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) peerLogic->FinalizeNode(dummyNode); } -class TxOrphanageTest : public TxOrphanage -{ -public: - inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) - { - return m_orphans.size(); - } - - CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) - { - std::map<uint256, OrphanTx>::iterator it; - it = m_orphans.lower_bound(InsecureRand256()); - if (it == m_orphans.end()) - it = m_orphans.begin(); - return it->second.tx; - } -}; - -static void MakeNewKeyWithFastRandomContext(CKey& key) -{ - std::vector<unsigned char> keydata; - keydata = g_insecure_rand_ctx.randbytes(32); - key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); - assert(key.IsValid()); -} - -BOOST_AUTO_TEST_CASE(DoS_mapOrphans) -{ - // This test had non-deterministic coverage due to - // randomly selected seeds. - // This seed is chosen so that all branches of the function - // ecdsa_signature_parse_der_lax are executed during this test. - // Specifically branches that run only when an ECDSA - // signature's R and S values have leading zeros. - g_insecure_rand_ctx = FastRandomContext{uint256{33}}; - - TxOrphanageTest orphanage; - CKey key; - MakeNewKeyWithFastRandomContext(key); - FillableSigningProvider keystore; - BOOST_CHECK(keystore.AddKey(key)); - - LOCK(g_cs_orphans); - - // 50 orphan transactions: - for (int i = 0; i < 50; i++) - { - CMutableTransaction tx; - tx.vin.resize(1); - tx.vin[0].prevout.n = 0; - tx.vin[0].prevout.hash = InsecureRand256(); - tx.vin[0].scriptSig << OP_1; - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - - orphanage.AddTx(MakeTransactionRef(tx), i); - } - - // ... and 50 that depend on other orphans: - for (int i = 0; i < 50; i++) - { - CTransactionRef txPrev = orphanage.RandomOrphan(); - - CMutableTransaction tx; - tx.vin.resize(1); - tx.vin[0].prevout.n = 0; - tx.vin[0].prevout.hash = txPrev->GetHash(); - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - - orphanage.AddTx(MakeTransactionRef(tx), i); - } - - // This really-big orphan should be ignored: - for (int i = 0; i < 10; i++) - { - CTransactionRef txPrev = orphanage.RandomOrphan(); - - CMutableTransaction tx; - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - tx.vin.resize(2777); - for (unsigned int j = 0; j < tx.vin.size(); j++) - { - tx.vin[j].prevout.n = j; - tx.vin[j].prevout.hash = txPrev->GetHash(); - } - BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - // Re-use same signature for other inputs - // (they don't have to be valid for this test) - for (unsigned int j = 1; j < tx.vin.size(); j++) - tx.vin[j].scriptSig = tx.vin[0].scriptSig; - - BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); - } - - // Test EraseOrphansFor: - for (NodeId i = 0; i < 3; i++) - { - size_t sizeBefore = orphanage.CountOrphans(); - orphanage.EraseForPeer(i); - BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); - } - - // Test LimitOrphanTxSize() function: - orphanage.LimitOrphans(40); - BOOST_CHECK(orphanage.CountOrphans() <= 40); - orphanage.LimitOrphans(10); - BOOST_CHECK(orphanage.CountOrphans() <= 10); - orphanage.LimitOrphans(0); - BOOST_CHECK(orphanage.CountOrphans() == 0); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 30add9c16d..63c86a896d 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -312,7 +312,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& std::vector<CTxOut> utxos(1); PrecomputedTransactionData txdata; txdata.Init(spend, std::move(utxos), /*force=*/true); - MutableTransactionSignatureCreator creator(&spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT); + MutableTransactionSignatureCreator creator{spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT}; SignatureData sigdata; BOOST_CHECK_MESSAGE(ProduceSignature(Merge(keys_priv, script_provider), creator, spks[n], sigdata), prv); } diff --git a/src/test/fuzz/base_encode_decode.cpp b/src/test/fuzz/base_encode_decode.cpp index 196410e29c..48356065b0 100644 --- a/src/test/fuzz/base_encode_decode.cpp +++ b/src/test/fuzz/base_encode_decode.cpp @@ -26,7 +26,7 @@ FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode) std::vector<unsigned char> decoded; if (DecodeBase58(random_encoded_string, decoded, 100)) { const std::string encoded_string = EncodeBase58(decoded); - assert(encoded_string == TrimString(encoded_string)); + assert(encoded_string == TrimStringView(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } @@ -36,17 +36,16 @@ FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode) assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } - bool pf_invalid; - std::string decoded_string = DecodeBase32(random_encoded_string, &pf_invalid); - if (!pf_invalid) { - const std::string encoded_string = EncodeBase32(decoded_string); - assert(encoded_string == TrimString(encoded_string)); + auto result = DecodeBase32(random_encoded_string); + if (result) { + const std::string encoded_string = EncodeBase32(*result); + assert(encoded_string == TrimStringView(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } - decoded_string = DecodeBase64(random_encoded_string, &pf_invalid); - if (!pf_invalid) { - const std::string encoded_string = EncodeBase64(decoded_string); + result = DecodeBase64(random_encoded_string); + if (result) { + const std::string encoded_string = EncodeBase64(*result); assert(encoded_string == TrimString(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } diff --git a/src/test/fuzz/checkqueue.cpp b/src/test/fuzz/checkqueue.cpp index 0b16f0f0d5..7d107995aa 100644 --- a/src/test/fuzz/checkqueue.cpp +++ b/src/test/fuzz/checkqueue.cpp @@ -26,7 +26,7 @@ struct DumbCheck { return result; } - void swap(DumbCheck& x) + void swap(DumbCheck& x) noexcept { } }; diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index fcc96c6418..1b89d55773 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -128,7 +128,7 @@ void ECRYPT_encrypt_bytes(ECRYPT_ctx* x, const u8* m, u8* c, u32 bytes) { u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - u8* ctarget = NULL; + u8* ctarget = nullptr; u8 tmp[64]; uint32_t i; diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp index 916e90e986..0fe18abaa9 100644 --- a/src/test/fuzz/http_request.cpp +++ b/src/test/fuzz/http_request.cpp @@ -39,7 +39,7 @@ FUZZ_TARGET(http_request) // and is a consequence of our hacky but necessary use of the internal function evhttp_parse_firstline_ in // this fuzzing harness. The workaround is not aesthetically pleasing, but it successfully avoids the troublesome // code path. " http:// HTTP/1.1\n" was a crashing input prior to this workaround. - const std::string http_buffer_str = ToLower({http_buffer.begin(), http_buffer.end()}); + const std::string http_buffer_str = ToLower(std::string{http_buffer.begin(), http_buffer.end()}); if (http_buffer_str.find(" http://") != std::string::npos || http_buffer_str.find(" https://") != std::string::npos || evhttp_parse_firstline_(evreq, evbuf) != 1 || evhttp_parse_headers_(evreq, evbuf) != 1) { evbuffer_free(evbuf); diff --git a/src/test/fuzz/prevector.cpp b/src/test/fuzz/prevector.cpp index a48bab1ee2..e2d65a4796 100644 --- a/src/test/fuzz/prevector.cpp +++ b/src/test/fuzz/prevector.cpp @@ -161,7 +161,7 @@ public: pre_vector.shrink_to_fit(); } - void swap() + void swap() noexcept { real_vector.swap(real_vector_alt); pre_vector.swap(pre_vector_alt); diff --git a/src/test/fuzz/psbt.cpp b/src/test/fuzz/psbt.cpp index 669688a80d..baa64bba0f 100644 --- a/src/test/fuzz/psbt.cpp +++ b/src/test/fuzz/psbt.cpp @@ -32,7 +32,8 @@ FUZZ_TARGET_INIT(psbt, initialize_psbt) FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; PartiallySignedTransaction psbt_mut; std::string error; - if (!DecodeRawPSBT(psbt_mut, fuzzed_data_provider.ConsumeRandomLengthString(), error)) { + auto str = fuzzed_data_provider.ConsumeRandomLengthString(); + if (!DecodeRawPSBT(psbt_mut, MakeByteSpan(str), error)) { return; } const PartiallySignedTransaction psbt = psbt_mut; @@ -79,7 +80,8 @@ FUZZ_TARGET_INIT(psbt, initialize_psbt) } PartiallySignedTransaction psbt_merge; - if (!DecodeRawPSBT(psbt_merge, fuzzed_data_provider.ConsumeRandomLengthString(), error)) { + str = fuzzed_data_provider.ConsumeRandomLengthString(); + if (!DecodeRawPSBT(psbt_merge, MakeByteSpan(str), error)) { psbt_merge = psbt; } psbt_mut = psbt; diff --git a/src/test/fuzz/script_assets_test_minimizer.cpp b/src/test/fuzz/script_assets_test_minimizer.cpp index 00a3bed12f..a18a0de47e 100644 --- a/src/test/fuzz/script_assets_test_minimizer.cpp +++ b/src/test/fuzz/script_assets_test_minimizer.cpp @@ -11,8 +11,8 @@ #include <streams.h> #include <univalue.h> #include <util/strencodings.h> +#include <util/string.h> -#include <boost/algorithm/string.hpp> #include <cstdint> #include <string> #include <vector> @@ -130,8 +130,7 @@ unsigned int ParseScriptFlags(const std::string& str) if (str.empty()) return 0; unsigned int flags = 0; - std::vector<std::string> words; - boost::algorithm::split(words, str, boost::algorithm::is_any_of(",")); + std::vector<std::string> words = SplitString(str, ','); for (const std::string& word : words) { auto it = FLAG_NAMES.find(word); diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 1446eafe92..3ddb30d870 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -113,7 +113,7 @@ FUZZ_TARGET_INIT(script_sign, initialize_script_sign) } if (n_in < script_tx_to.vin.size()) { (void)SignSignature(provider, ConsumeScript(fuzzed_data_provider), script_tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()); - MutableTransactionSignatureCreator signature_creator{&tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()}; + MutableTransactionSignatureCreator signature_creator{tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()}; std::vector<unsigned char> vch_sig; CKeyID address; if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index ca57af25c4..94399faf04 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -42,7 +42,7 @@ bool LegacyParsePrechecks(const std::string& str) return false; if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size() - 1]))) // No padding allowed return false; - if (!ValidAsCString(str)) // No embedded NUL characters allowed + if (!ContainsNoNUL(str)) // No embedded NUL characters allowed return false; return true; } @@ -188,7 +188,7 @@ FUZZ_TARGET(string) (void)TrimString(random_string_1); (void)TrimString(random_string_1, random_string_2); (void)urlDecode(random_string_1); - (void)ValidAsCString(random_string_1); + (void)ContainsNoNUL(random_string_1); (void)_(random_string_1.c_str()); try { throw scriptnum_error{random_string_1}; @@ -225,6 +225,12 @@ FUZZ_TARGET(string) (void)ParseFixedPoint(random_string_1, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 1024), &amount_out); } { + const auto single_split{SplitString(random_string_1, fuzzed_data_provider.ConsumeIntegral<char>())}; + assert(single_split.size() >= 1); + const auto any_split{SplitString(random_string_1, random_string_2)}; + assert(any_split.size() >= 1); + } + { (void)Untranslated(random_string_1); const bilingual_str bs1{random_string_1, random_string_2}; const bilingual_str bs2{random_string_2, random_string_1}; diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index c877105fe7..70dd137e22 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -13,7 +13,6 @@ #include <utility> #include <vector> -#include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) @@ -21,8 +20,9 @@ BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) void ResetArgs(ArgsManager& local_args, const std::string& strArg) { std::vector<std::string> vecArg; - if (strArg.size()) - boost::split(vecArg, strArg, IsSpace, boost::token_compress_on); + if (strArg.size()) { + vecArg = SplitString(strArg, ' '); + } // Insert dummy executable name: vecArg.insert(vecArg.begin(), "testbitcoin"); diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index d6f24210eb..89424a0cd2 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -747,6 +747,15 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 9ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); +} + +BOOST_AUTO_TEST_CASE(MempoolAncestryTestsDiamond) +{ + size_t ancestors, descendants; + + CTxMemPool pool; + LOCK2(::cs_main, pool.cs); + TestMemPoolEntryHelper entry; /* Ancestors represented more than once ("diamond") */ // @@ -759,7 +768,6 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) tb = make_tx(/*output_values=*/{5 * COIN, 3 * COIN}, /*inputs=*/ {ta}); tc = make_tx(/*output_values=*/{2 * COIN}, /*inputs=*/{tb}, /*input_indices=*/{1}); td = make_tx(/*output_values=*/{6 * COIN}, /*inputs=*/{tb, tc}, /*input_indices=*/{0, 0}); - pool.clear(); pool.addUnchecked(entry.Fee(10000LL).FromTx(ta)); pool.addUnchecked(entry.Fee(10000LL).FromTx(tb)); pool.addUnchecked(entry.Fee(10000LL).FromTx(tc)); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index f6c1d1efad..cafa5710fa 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -30,6 +30,8 @@ using node::CBlockTemplate; namespace miner_tests { struct MinerTestingSetup : public TestingSetup { void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); + void TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); + void TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) { CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); @@ -191,60 +193,17 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } -// NOTE: These tests rely on CreateNewBlock doing its own self-validation! -BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) +void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) { - // Note that by default, these tests run with size accounting enabled. - const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); - const CChainParams& chainparams = *chainParams; - CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; - std::unique_ptr<CBlockTemplate> pblocktemplate; - CMutableTransaction tx; - CScript script; uint256 hash; + CMutableTransaction tx; TestMemPoolEntryHelper entry; entry.nFee = 11; entry.nHeight = 11; - fCheckpointsEnabled = false; - - // Simple block creation, nothing special yet: - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - - // We can't make transactions until we have inputs - // Therefore, load 110 blocks :) - static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import"); - int baseheight = 0; - std::vector<CTransactionRef> txFirst; - for (const auto& bi : BLOCKINFO) { - CBlock *pblock = &pblocktemplate->block; // pointer for convenience - { - LOCK(cs_main); - pblock->nVersion = VERSIONBITS_TOP_BITS; - pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; - CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.nVersion = 1; - txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce; - 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 = m_node.chainman->ActiveChain().Height(); - if (txFirst.size() < 4) - txFirst.push_back(pblock->vtx[0]); - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - pblock->nNonce = bi.nonce; - } - std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr)); - pblock->hashPrevBlock = pblock->GetHash(); - } - - LOCK(cs_main); - LOCK(m_node.mempool->cs); - // Just to make sure we can still make simple blocks - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); const CAmount BLOCKSUBSIDY = 50*COIN; const CAmount LOWFEE = CENT; @@ -386,7 +345,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.n = 0; tx.vin[0].scriptSig = CScript() << OP_1; tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE; - script = CScript() << OP_0; + CScript script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); @@ -446,16 +405,18 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail - for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - + const int SEQUENCE_LOCK_TIME = 512; // Sequence locks pass 512 seconds later + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += SEQUENCE_LOCK_TIME; // 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 + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 1, active_chain_tip))); } - for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) { + CBlockIndex* ancestor{Assert(m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i))}; + ancestor->nTime -= SEQUENCE_LOCK_TIME; // undo tricked MTP + } // absolute height locked tx.vin[0].prevout.hash = txFirst[2]->GetHash(); @@ -500,14 +461,140 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // but relative locked txs will if inconsistently added to mempool. // For now these will still generate a valid template until BIP68 soft fork 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++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + // However if we advance height by 1 and time by SEQUENCE_LOCK_TIME, all of them should be mined + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) { + CBlockIndex* ancestor{Assert(m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i))}; + ancestor->nTime += SEQUENCE_LOCK_TIME; // 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); +} + +void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +{ + TestMemPoolEntryHelper entry; + + // Test that a tx below min fee but prioritised is included + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout.resize(1); + tx.vout[0].nValue = 5000000000LL; // 0 fee + uint256 hashFreePrioritisedTx = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN); + + tx.vin[0].prevout.hash = txFirst[1]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vout[0].nValue = 5000000000LL - 1000; + // This tx has a low fee: 1000 satoshis + uint256 hashParentTx = tx.GetHash(); // save this txid for later use + m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + + // This tx has a medium fee: 10000 satoshis + tx.vin[0].prevout.hash = txFirst[2]->GetHash(); + tx.vout[0].nValue = 5000000000LL - 10000; + uint256 hashMediumFeeTx = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->PrioritiseTransaction(hashMediumFeeTx, -5 * COIN); + + // This tx also has a low fee, but is prioritised + tx.vin[0].prevout.hash = hashParentTx; + tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee + uint256 hashPrioritsedChild = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->PrioritiseTransaction(hashPrioritsedChild, 2 * COIN); + + // Test that transaction selection properly updates ancestor fee calculations as prioritised + // parents get included in a block. Create a transaction with two prioritised ancestors, each + // included by itself: FreeParent <- FreeChild <- FreeGrandchild. + // When FreeParent is added, a modified entry will be created for FreeChild + FreeGrandchild + // FreeParent's prioritisation should not be included in that entry. + // When FreeChild is included, FreeChild's prioritisation should also not be included. + tx.vin[0].prevout.hash = txFirst[3]->GetHash(); + tx.vout[0].nValue = 5000000000LL; // 0 fee + uint256 hashFreeParent = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + m_node.mempool->PrioritiseTransaction(hashFreeParent, 10 * COIN); + + tx.vin[0].prevout.hash = hashFreeParent; + tx.vout[0].nValue = 5000000000LL; // 0 fee + uint256 hashFreeChild = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + m_node.mempool->PrioritiseTransaction(hashFreeChild, 1 * COIN); + + tx.vin[0].prevout.hash = hashFreeChild; + tx.vout[0].nValue = 5000000000LL; // 0 fee + uint256 hashFreeGrandchild = tx.GetHash(); + m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + + auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); + BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent); + BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashFreePrioritisedTx); + BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashParentTx); + BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashPrioritsedChild); + BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashFreeChild); + for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { + // The FreeParent and FreeChild's prioritisations should not impact the child. + BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeGrandchild); + // De-prioritised transaction should not be included. + BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashMediumFeeTx); + } +} + +// NOTE: These tests rely on CreateNewBlock doing its own self-validation! +BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) +{ + // Note that by default, these tests run with size accounting enabled. + const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); + const CChainParams& chainparams = *chainParams; + CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + std::unique_ptr<CBlockTemplate> pblocktemplate; + + fCheckpointsEnabled = false; + + // Simple block creation, nothing special yet: + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + + // We can't make transactions until we have inputs + // Therefore, load 110 blocks :) + static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import"); + int baseheight = 0; + std::vector<CTransactionRef> txFirst; + for (const auto& bi : BLOCKINFO) { + CBlock *pblock = &pblocktemplate->block; // pointer for convenience + { + LOCK(cs_main); + pblock->nVersion = VERSIONBITS_TOP_BITS; + pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; + CMutableTransaction txCoinbase(*pblock->vtx[0]); + txCoinbase.nVersion = 1; + txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce; + 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 = m_node.chainman->ActiveChain().Height(); + if (txFirst.size() < 4) + txFirst.push_back(pblock->vtx[0]); + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + pblock->nNonce = bi.nonce; + } + std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr)); + pblock->hashPrevBlock = pblock->GetHash(); + } + + LOCK(cs_main); + LOCK(m_node.mempool->cs); + + TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); @@ -515,6 +602,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) TestPackageSelection(chainparams, scriptPubKey, txFirst); + m_node.chainman->ActiveChain().Tip()->nHeight--; + SetMockTime(0); + m_node.mempool->clear(); + + TestPrioritisedMining(chainparams, scriptPubKey, txFirst); + fCheckpointsEnabled = true; } diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp new file mode 100644 index 0000000000..842daa8bd4 --- /dev/null +++ b/src/test/orphanage_tests.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2011-2022 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 <arith_uint256.h> +#include <pubkey.h> +#include <script/sign.h> +#include <script/signingprovider.h> +#include <script/standard.h> +#include <test/util/setup_common.h> +#include <txorphanage.h> + +#include <array> +#include <cstdint> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(orphanage_tests, TestingSetup) + +class TxOrphanageTest : public TxOrphanage +{ +public: + inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + return m_orphans.size(); + } + + CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + std::map<uint256, OrphanTx>::iterator it; + it = m_orphans.lower_bound(InsecureRand256()); + if (it == m_orphans.end()) + it = m_orphans.begin(); + return it->second.tx; + } +}; + +static void MakeNewKeyWithFastRandomContext(CKey& key) +{ + std::vector<unsigned char> keydata; + keydata = g_insecure_rand_ctx.randbytes(32); + key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); + assert(key.IsValid()); +} + +BOOST_AUTO_TEST_CASE(DoS_mapOrphans) +{ + // This test had non-deterministic coverage due to + // randomly selected seeds. + // This seed is chosen so that all branches of the function + // ecdsa_signature_parse_der_lax are executed during this test. + // Specifically branches that run only when an ECDSA + // signature's R and S values have leading zeros. + g_insecure_rand_ctx = FastRandomContext{uint256{33}}; + + TxOrphanageTest orphanage; + CKey key; + MakeNewKeyWithFastRandomContext(key); + FillableSigningProvider keystore; + BOOST_CHECK(keystore.AddKey(key)); + + LOCK(g_cs_orphans); + + // 50 orphan transactions: + for (int i = 0; i < 50; i++) + { + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.n = 0; + tx.vin[0].prevout.hash = InsecureRand256(); + tx.vin[0].scriptSig << OP_1; + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + + orphanage.AddTx(MakeTransactionRef(tx), i); + } + + // ... and 50 that depend on other orphans: + for (int i = 0; i < 50; i++) + { + CTransactionRef txPrev = orphanage.RandomOrphan(); + + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.n = 0; + tx.vin[0].prevout.hash = txPrev->GetHash(); + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); + + orphanage.AddTx(MakeTransactionRef(tx), i); + } + + // This really-big orphan should be ignored: + for (int i = 0; i < 10; i++) + { + CTransactionRef txPrev = orphanage.RandomOrphan(); + + CMutableTransaction tx; + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + tx.vin.resize(2777); + for (unsigned int j = 0; j < tx.vin.size(); j++) + { + tx.vin[j].prevout.n = j; + tx.vin[j].prevout.hash = txPrev->GetHash(); + } + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); + // Re-use same signature for other inputs + // (they don't have to be valid for this test) + for (unsigned int j = 1; j < tx.vin.size(); j++) + tx.vin[j].scriptSig = tx.vin[0].scriptSig; + + BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); + } + + // Test EraseOrphansFor: + for (NodeId i = 0; i < 3; i++) + { + size_t sizeBefore = orphanage.CountOrphans(); + orphanage.EraseForPeer(i); + BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); + } + + // Test LimitOrphanTxSize() function: + orphanage.LimitOrphans(40); + BOOST_CHECK(orphanage.CountOrphans() <= 40); + orphanage.LimitOrphans(10); + BOOST_CHECK(orphanage.CountOrphans() <= 10); + orphanage.LimitOrphans(0); + BOOST_CHECK(orphanage.CountOrphans() == 0); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index 89814748fe..3977a3d548 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -165,7 +165,8 @@ public: test(); } - void swap() { + void swap() noexcept + { real_vector.swap(real_vector_alt); pre_vector.swap(pre_vector_alt); test(); diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index eba7b51592..9b2760fd1c 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -31,6 +31,16 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); } + { + constexpr SteadySeconds time_point{1s}; + FastRandomContext ctx{true}; + BOOST_CHECK_EQUAL(7, ctx.rand_uniform_delay(time_point, 9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-6, ctx.rand_uniform_delay(time_point, -9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1, ctx.rand_uniform_delay(time_point, 0s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1467825113502396065, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-970181367944767837, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(24761, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count()); + } BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand64(), ctx2.rand64()); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 50b5078110..7cec287e8f 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -2,25 +2,21 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <rpc/client.h> -#include <rpc/server.h> -#include <rpc/util.h> - #include <core_io.h> #include <interfaces/chain.h> #include <node/context.h> +#include <rpc/blockchain.h> +#include <rpc/client.h> +#include <rpc/server.h> +#include <rpc/util.h> #include <test/util/setup_common.h> +#include <univalue.h> #include <util/time.h> #include <any> -#include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> -#include <univalue.h> - -#include <rpc/blockchain.h> - class RPCTestingSetup : public TestingSetup { public: @@ -29,8 +25,7 @@ public: UniValue RPCTestingSetup::CallRPC(std::string args) { - std::vector<std::string> vArgs; - boost::split(vArgs, args, boost::is_any_of(" \t")); + std::vector<std::string> vArgs{SplitString(args, ' ')}; std::string strMethod = vArgs[0]; vArgs.erase(vArgs.begin()); JSONRPCRequest request; diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 4195d413fc..7b5dda8114 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -15,13 +15,13 @@ BOOST_AUTO_TEST_SUITE(scheduler_tests) -static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::system_clock::time_point rescheduleTime) +static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::steady_clock::time_point rescheduleTime) { { std::lock_guard<std::mutex> lock(mutex); counter += delta; } - std::chrono::system_clock::time_point noTime = std::chrono::system_clock::time_point::min(); + auto noTime = std::chrono::steady_clock::time_point::min(); if (rescheduleTime != noTime) { CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); @@ -49,15 +49,15 @@ BOOST_AUTO_TEST_CASE(manythreads) auto randomMsec = [](FastRandomContext& rc) -> int { return -11 + (int)rc.randrange(1012); }; // [-11, 1000] auto randomDelta = [](FastRandomContext& rc) -> int { return -1000 + (int)rc.randrange(2001); }; // [-1000, 1000] - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - std::chrono::system_clock::time_point now = start; - std::chrono::system_clock::time_point first, last; + auto start = std::chrono::steady_clock::now(); + auto now = start; + std::chrono::steady_clock::time_point first, last; size_t nTasks = microTasks.getQueueInfo(first, last); BOOST_CHECK(nTasks == 0); for (int i = 0; i < 100; ++i) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -75,14 +75,14 @@ BOOST_AUTO_TEST_CASE(manythreads) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); UninterruptibleSleep(std::chrono::microseconds{600}); - now = std::chrono::system_clock::now(); + now = std::chrono::steady_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -111,8 +111,8 @@ BOOST_AUTO_TEST_CASE(wait_until_past) Mutex mtx; WAIT_LOCK(mtx, lock); - const auto no_wait= [&](const std::chrono::seconds& d) { - return condvar.wait_until(lock, std::chrono::system_clock::now() - d); + const auto no_wait = [&](const std::chrono::seconds& d) { + return condvar.wait_until(lock, std::chrono::steady_clock::now() - d); }; BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::seconds{1})); @@ -128,8 +128,8 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) CScheduler scheduler; // each queue should be well ordered with respect to itself but not other queues - SingleThreadedSchedulerClient queue1(&scheduler); - SingleThreadedSchedulerClient queue2(&scheduler); + SingleThreadedSchedulerClient queue1(scheduler); + SingleThreadedSchedulerClient queue2(scheduler); // create more threads than queues // if the queues only permit execution of one task at once then @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) // if they don't we'll get out of order behaviour std::vector<std::thread> threads; for (int i = 0; i < 5; ++i) { - threads.emplace_back(std::bind(&CScheduler::serviceQueue, &scheduler)); + threads.emplace_back([&] { scheduler.serviceQueue(); }); } // these are not atomic, if SinglethreadedSchedulerClient prevents @@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(mockforward) scheduler.scheduleFromNow(dummy, std::chrono::minutes{8}); // check taskQueue - std::chrono::system_clock::time_point first, last; + std::chrono::steady_clock::time_point first, last; size_t num_tasks = scheduler.getQueueInfo(first, last); BOOST_CHECK_EQUAL(num_tasks, 3ul); @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(mockforward) BOOST_CHECK_EQUAL(counter, 2); // check that the time of the remaining job has been updated - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + auto now = std::chrono::steady_clock::now(); int delta = std::chrono::duration_cast<std::chrono::seconds>(first - now).count(); // should be between 2 & 3 minutes from now BOOST_CHECK(delta > 2*60 && delta < 3*60); diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index c453f22594..19e32d0c36 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1162,7 +1162,7 @@ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& SignatureData data; data.MergeSignatureData(scriptSig1); data.MergeSignatureData(scriptSig2); - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue, SIGHASH_DEFAULT), txout.scriptPubKey, data); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(tx, 0, txout.nValue, SIGHASH_DEFAULT), txout.scriptPubKey, data); return data; } @@ -1796,7 +1796,7 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) // Sign and verify signature. FlatSigningProvider provider; provider.keys[key.GetPubKey().GetID()] = key; - MutableTransactionSignatureCreator creator(&tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype); + MutableTransactionSignatureCreator creator(tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype); std::vector<unsigned char> signature; BOOST_CHECK(creator.CreateSchnorrSig(provider, signature, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT)); BOOST_CHECK_EQUAL(HexStr(signature), input["expected"]["witness"][0].get_str()); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 4fb7f9c405..c3a2a66027 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -31,8 +31,6 @@ #include <map> #include <string> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> #include <boost/test/unit_test.hpp> #include <univalue.h> @@ -70,8 +68,7 @@ unsigned int ParseScriptFlags(std::string strFlags) { if (strFlags.empty() || strFlags == "NONE") return 0; unsigned int flags = 0; - std::vector<std::string> words; - boost::algorithm::split(words, strFlags, boost::algorithm::is_any_of(",")); + std::vector<std::string> words = SplitString(strFlags, ','); for (const std::string& word : words) { @@ -562,7 +559,7 @@ SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutabl SignatureData sigdata; sigdata = DataFromTransaction(input1, 0, tx->vout[0]); sigdata.MergeSignatureData(DataFromTransaction(input2, 0, tx->vout[0])); - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&input1, 0, tx->vout[0].nValue, SIGHASH_ALL), tx->vout[0].scriptPubKey, sigdata); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(input1, 0, tx->vout[0].nValue, SIGHASH_ALL), tx->vout[0].scriptPubKey, sigdata); return sigdata; } diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index b1e2119c64..d41b54af20 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -329,7 +329,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup) // Sign SignatureData sigdata; - BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&valid_with_witness_tx, 0, 11*CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata)); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(valid_with_witness_tx, 0, 11 * CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata)); UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. @@ -355,9 +355,9 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup) tx.vout[0].scriptPubKey = p2pk_scriptPubKey; // Sign - for (int i=0; i<2; ++i) { + for (int i = 0; i < 2; ++i) { SignatureData sigdata; - BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata)); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(tx, i, 11 * CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata)); UpdateInput(tx.vin[i], sigdata); } diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h index 09f96a033c..5ac504c24f 100644 --- a/src/test/util/chainstate.h +++ b/src/test/util/chainstate.h @@ -30,7 +30,7 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma // int height; WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight()); - fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height); + fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height)); FILE* outfile{fsbridge::fopen(snapshot_path, "wb")}; CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION}; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 1830ec05af..2fc71c2a6e 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -182,7 +182,6 @@ ChainTestingSetup::~ChainTestingSetup() m_node.addrman.reset(); m_node.netgroupman.reset(); m_node.args = nullptr; - WITH_LOCK(::cs_main, UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman)); m_node.mempool.reset(); m_node.scheduler.reset(); m_node.chainman.reset(); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index b5d8411e1d..9dcfe009b3 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -198,6 +198,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr) BOOST_CHECK_EQUAL(HexStr(in_s), out_exp); BOOST_CHECK_EQUAL(HexStr(in_b), out_exp); } + + { + auto input = std::string(); + for (size_t i=0; i<256; ++i) { + input.push_back(static_cast<char>(i)); + } + + auto hex = HexStr(input); + BOOST_TEST_REQUIRE(hex.size() == 512); + static constexpr auto hexmap = std::string_view("0123456789abcdef"); + for (size_t i = 0; i < 256; ++i) { + auto upper = hexmap.find(hex[i * 2]); + auto lower = hexmap.find(hex[i * 2 + 1]); + BOOST_TEST_REQUIRE(upper != std::string_view::npos); + BOOST_TEST_REQUIRE(lower != std::string_view::npos); + BOOST_TEST_REQUIRE(i == upper*16 + lower); + } + } } BOOST_AUTO_TEST_CASE(span_write_bytes) @@ -226,17 +244,17 @@ BOOST_AUTO_TEST_CASE(util_Join) BOOST_AUTO_TEST_CASE(util_TrimString) { BOOST_CHECK_EQUAL(TrimString(" foo bar "), "foo bar"); - BOOST_CHECK_EQUAL(TrimString("\t \n \n \f\n\r\t\v\tfoo \n \f\n\r\t\v\tbar\t \n \f\n\r\t\v\t\n "), "foo \n \f\n\r\t\v\tbar"); + BOOST_CHECK_EQUAL(TrimStringView("\t \n \n \f\n\r\t\v\tfoo \n \f\n\r\t\v\tbar\t \n \f\n\r\t\v\t\n "), "foo \n \f\n\r\t\v\tbar"); BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n "), "foo \n\tbar"); - BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n ", "fobar"), "\t \n foo \n\tbar\t \n "); + BOOST_CHECK_EQUAL(TrimStringView("\t \n foo \n\tbar\t \n ", "fobar"), "\t \n foo \n\tbar\t \n "); BOOST_CHECK_EQUAL(TrimString("foo bar"), "foo bar"); - BOOST_CHECK_EQUAL(TrimString("foo bar", "fobar"), " "); + BOOST_CHECK_EQUAL(TrimStringView("foo bar", "fobar"), " "); BOOST_CHECK_EQUAL(TrimString(std::string("\0 foo \0 ", 8)), std::string("\0 foo \0", 7)); - BOOST_CHECK_EQUAL(TrimString(std::string(" foo ", 5)), std::string("foo", 3)); + BOOST_CHECK_EQUAL(TrimStringView(std::string(" foo ", 5)), std::string("foo", 3)); BOOST_CHECK_EQUAL(TrimString(std::string("\t\t\0\0\n\n", 6)), std::string("\0\0", 2)); - BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6)), std::string("\x05\x04\x03\x02\x01\x00", 6)); + BOOST_CHECK_EQUAL(TrimStringView(std::string("\x05\x04\x03\x02\x01\x00", 6)), std::string("\x05\x04\x03\x02\x01\x00", 6)); BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01", 5)), std::string("\0", 1)); - BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); + BOOST_CHECK_EQUAL(TrimStringView(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); } BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) @@ -1470,8 +1488,8 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime) { SetMockTime(111); // Check that mock time does not change after a sleep - for (const auto& num_sleep : {0, 1}) { - UninterruptibleSleep(std::chrono::milliseconds{num_sleep}); + for (const auto& num_sleep : {0ms, 1ms}) { + UninterruptibleSleep(num_sleep); BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter BOOST_CHECK_EQUAL(111, GetTime<std::chrono::seconds>().count()); BOOST_CHECK_EQUAL(111000, GetTime<std::chrono::milliseconds>().count()); @@ -1479,10 +1497,14 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime) } SetMockTime(0); - // Check that system time changes after a sleep + // Check that steady time and system time changes after a sleep + const auto steady_ms_0 = Now<SteadyMilliseconds>(); + const auto steady_0 = std::chrono::steady_clock::now(); const auto ms_0 = GetTime<std::chrono::milliseconds>(); const auto us_0 = GetTime<std::chrono::microseconds>(); - UninterruptibleSleep(std::chrono::milliseconds{1}); + UninterruptibleSleep(1ms); + BOOST_CHECK(steady_ms_0 < Now<SteadyMilliseconds>()); + BOOST_CHECK(steady_0 + 1ms <= std::chrono::steady_clock::now()); BOOST_CHECK(ms_0 < GetTime<std::chrono::milliseconds>()); BOOST_CHECK(us_0 < GetTime<std::chrono::microseconds>()); } @@ -2049,7 +2071,7 @@ BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) BOOST_CHECK(!ParseFixedPoint("31.999999999999999999999", 3, &amount)); } -static void TestOtherThread(fs::path dirname, std::string lockname, bool *result) +static void TestOtherThread(fs::path dirname, fs::path lockname, bool *result) { *result = LockDirectory(dirname, lockname); } @@ -2059,7 +2081,7 @@ static constexpr char LockCommand = 'L'; static constexpr char UnlockCommand = 'U'; static constexpr char ExitCommand = 'X'; -[[noreturn]] static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) +[[noreturn]] static void TestOtherProcess(fs::path dirname, fs::path lockname, int fd) { char ch; while (true) { @@ -2090,7 +2112,7 @@ static constexpr char ExitCommand = 'X'; BOOST_AUTO_TEST_CASE(test_LockDirectory) { fs::path dirname = m_args.GetDataDirBase() / "lock_dir"; - const std::string lockname = ".lock"; + const fs::path lockname = ".lock"; #ifndef WIN32 // Revert SIGCHLD to default, otherwise boost.test will catch and fail on // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined @@ -2349,6 +2371,68 @@ BOOST_AUTO_TEST_CASE(test_spanparsing) BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); } +BOOST_AUTO_TEST_CASE(test_SplitString) +{ + // Empty string. + { + std::vector<std::string> result = SplitString("", '-'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], ""); + } + + // Empty items. + { + std::vector<std::string> result = SplitString("-", '-'); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK_EQUAL(result[0], ""); + BOOST_CHECK_EQUAL(result[1], ""); + } + + // More empty items. + { + std::vector<std::string> result = SplitString("--", '-'); + BOOST_CHECK_EQUAL(result.size(), 3); + BOOST_CHECK_EQUAL(result[0], ""); + BOOST_CHECK_EQUAL(result[1], ""); + BOOST_CHECK_EQUAL(result[2], ""); + } + + // Separator is not present. + { + std::vector<std::string> result = SplitString("abc", '-'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], "abc"); + } + + // Basic behavior. + { + std::vector<std::string> result = SplitString("a-b", '-'); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK_EQUAL(result[0], "a"); + BOOST_CHECK_EQUAL(result[1], "b"); + } + + // Case-sensitivity of the separator. + { + std::vector<std::string> result = SplitString("AAA", 'a'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], "AAA"); + } + + // multiple split characters + { + using V = std::vector<std::string>; + BOOST_TEST(SplitString("a,b.c:d;e", ",;") == V({"a", "b.c:d", "e"})); + BOOST_TEST(SplitString("a,b.c:d;e", ",;:.") == V({"a", "b", "c", "d", "e"})); + BOOST_TEST(SplitString("a,b.c:d;e", "") == V({"a,b.c:d;e"})); + BOOST_TEST(SplitString("aaa", "bcdefg") == V({"aaa"})); + BOOST_TEST(SplitString("x\0a,b"s, "\0"s) == V({"x", "a,b"})); + BOOST_TEST(SplitString("x\0a,b"s, '\0') == V({"x", "a,b"})); + BOOST_TEST(SplitString("x\0a,b"s, "\0,"s) == V({"x", "a", "b"})); + BOOST_TEST(SplitString("abcdefg", "bcd") == V({"a", "", "", "efg"})); + } +} + BOOST_AUTO_TEST_CASE(test_LogEscapeMessage) { // ASCII and UTF-8 must pass through unaltered. @@ -2569,13 +2653,13 @@ BOOST_AUTO_TEST_CASE(message_hash) BOOST_AUTO_TEST_CASE(remove_prefix) { BOOST_CHECK_EQUAL(RemovePrefix("./util/system.h", "./"), "util/system.h"); - BOOST_CHECK_EQUAL(RemovePrefix("foo", "foo"), ""); + BOOST_CHECK_EQUAL(RemovePrefixView("foo", "foo"), ""); BOOST_CHECK_EQUAL(RemovePrefix("foo", "fo"), "o"); - BOOST_CHECK_EQUAL(RemovePrefix("foo", "f"), "oo"); + BOOST_CHECK_EQUAL(RemovePrefixView("foo", "f"), "oo"); BOOST_CHECK_EQUAL(RemovePrefix("foo", ""), "foo"); - BOOST_CHECK_EQUAL(RemovePrefix("fo", "foo"), "fo"); + BOOST_CHECK_EQUAL(RemovePrefixView("fo", "foo"), "fo"); BOOST_CHECK_EQUAL(RemovePrefix("f", "foo"), "f"); - BOOST_CHECK_EQUAL(RemovePrefix("", "foo"), ""); + BOOST_CHECK_EQUAL(RemovePrefixView("", "foo"), ""); BOOST_CHECK_EQUAL(RemovePrefix("", ""), ""); } diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index b0d7389d39..2a3990bb7c 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -93,7 +93,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root)); // Ensure our active chain is the snapshot chainstate. - BOOST_CHECK(chainman.IsSnapshotActive()); + BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive())); curr_tip = ::g_best_block; diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 5d0ec593e3..6dc522b421 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); BOOST_CHECK(!manager.IsSnapshotActive()); - BOOST_CHECK(!manager.IsSnapshotValidated()); + BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); auto all = manager.GetAll(); BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end()); @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) BOOST_CHECK(c2.ActivateBestChain(_, nullptr)); BOOST_CHECK(manager.IsSnapshotActive()); - BOOST_CHECK(!manager.IsSnapshotValidated()); + BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate()); BOOST_CHECK(&c1 != &manager.ActiveChainstate()); auto all2 = manager.GetAll(); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 74450f591d..05dbc6057f 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -24,10 +24,6 @@ #include <set> #include <vector> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/replace.hpp> -#include <boost/algorithm/string/split.hpp> - #include <event2/buffer.h> #include <event2/bufferevent.h> #include <event2/event.h> @@ -308,7 +304,7 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target): base(_base), - m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0), + m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(nullptr), reconnect_timeout(RECONNECT_TIMEOUT_START), m_target(target) { @@ -347,8 +343,8 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe for (const auto& line : reply.lines) { if (0 == line.compare(0, 20, "net/listeners/socks=")) { const std::string port_list_str = line.substr(20); - std::vector<std::string> port_list; - boost::split(port_list, port_list_str, boost::is_any_of(" ")); + std::vector<std::string> port_list = SplitString(port_list_str, ' '); + for (auto& portstr : port_list) { if (portstr.empty()) continue; if ((portstr[0] == '"' || portstr[0] == '\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) { @@ -542,8 +538,10 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (l.first == "AUTH") { std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); std::map<std::string,std::string>::iterator i; - if ((i = m.find("METHODS")) != m.end()) - boost::split(methods, i->second, boost::is_any_of(",")); + if ((i = m.find("METHODS")) != m.end()) { + std::vector<std::string> m_vec = SplitString(i->second, ','); + methods = std::set<std::string>(m_vec.begin(), m_vec.end()); + } if ((i = m.find("COOKIEFILE")) != m.end()) cookiefile = i->second; } else if (l.first == "VERSION") { @@ -566,7 +564,7 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (!torpassword.empty()) { if (methods.count("HASHEDPASSWORD")) { LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); - boost::replace_all(torpassword, "\"", "\\\""); + ReplaceAll(torpassword, "\"", "\\\""); _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); diff --git a/src/util/check.h b/src/util/check.h index 4ee65c8d34..aca957925a 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -18,8 +18,23 @@ class NonFatalCheckError : public std::runtime_error using std::runtime_error::runtime_error; }; +#define format_internal_error(msg, file, line, func, report) \ + strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", \ + msg, file, line, func, report) + +/** Helper for CHECK_NONFATAL() */ +template <typename T> +T&& inline_check_non_fatal(T&& val, const char* file, int line, const char* func, const char* assertion) +{ + if (!(val)) { + throw NonFatalCheckError( + format_internal_error(assertion, file, line, func, PACKAGE_BUGREPORT)); + } + return std::forward<T>(val); +} + /** - * Throw a NonFatalCheckError when the condition evaluates to false + * Identity function. Throw a NonFatalCheckError when the condition evaluates to false * * This should only be used * - where the condition is assumed to be true, not for error handling or validating user input @@ -29,18 +44,8 @@ class NonFatalCheckError : public std::runtime_error * asserts or recoverable logic errors. A NonFatalCheckError in RPC code is caught and passed as a string to the RPC * caller, which can then report the issue to the developers. */ -#define CHECK_NONFATAL(condition) \ - do { \ - if (!(condition)) { \ - throw NonFatalCheckError( \ - strprintf("Internal bug detected: '%s'\n" \ - "%s:%d (%s)\n" \ - "You may report this issue here: %s\n", \ - (#condition), \ - __FILE__, __LINE__, __func__, \ - PACKAGE_BUGREPORT)); \ - } \ - } while (false) +#define CHECK_NONFATAL(condition) \ + inline_check_non_fatal(condition, __FILE__, __LINE__, __func__, #condition) #if defined(NDEBUG) #error "Cannot compile without assertions!" @@ -80,4 +85,13 @@ T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_u */ #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) +/** + * NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError. + * This is used to mark code that is not yet implemented or is not yet reachable. + */ +#define NONFATAL_UNREACHABLE() \ + throw NonFatalCheckError( \ + format_internal_error("Unreachable code reached (non-fatal)", \ + __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)) + #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/util/getuniquepath.cpp b/src/util/getuniquepath.cpp index 6776e7785b..1d8e511c83 100644 --- a/src/util/getuniquepath.cpp +++ b/src/util/getuniquepath.cpp @@ -9,6 +9,6 @@ fs::path GetUniquePath(const fs::path& base) { FastRandomContext rnd; - fs::path tmpFile = base / HexStr(rnd.randbytes(8)); + fs::path tmpFile = base / fs::u8path(HexStr(rnd.randbytes(8))); return tmpFile; }
\ No newline at end of file diff --git a/src/util/message.cpp b/src/util/message.cpp index 2c7f0406f0..f58876f915 100644 --- a/src/util/message.cpp +++ b/src/util/message.cpp @@ -35,14 +35,13 @@ MessageVerificationResult MessageVerify( return MessageVerificationResult::ERR_ADDRESS_NO_KEY; } - bool invalid = false; - std::vector<unsigned char> signature_bytes = DecodeBase64(signature.c_str(), &invalid); - if (invalid) { + auto signature_bytes = DecodeBase64(signature); + if (!signature_bytes) { return MessageVerificationResult::ERR_MALFORMED_SIGNATURE; } CPubKey pubkey; - if (!pubkey.RecoverCompact(MessageHash(message), signature_bytes)) { + if (!pubkey.RecoverCompact(MessageHash(message), *signature_bytes)) { return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED; } diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 2cd7a426f8..8c4bc6e6f4 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -40,7 +40,7 @@ std::string FormatMoney(const CAmount n) std::optional<CAmount> ParseMoney(const std::string& money_string) { - if (!ValidAsCString(money_string)) { + if (!ContainsNoNUL(money_string)) { return std::nullopt; } const std::string str = TrimString(money_string); diff --git a/src/util/sock.cpp b/src/util/sock.cpp index b5c1e28294..3579af4458 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -7,6 +7,7 @@ #include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> +#include <util/syserror.h> #include <util/system.h> #include <util/time.h> @@ -344,19 +345,8 @@ std::string NetworkErrorString(int err) #else std::string NetworkErrorString(int err) { - char buf[256]; - buf[0] = 0; - /* Too bad there are two incompatible implementations of the - * thread-safe strerror. */ - const char *s; -#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ - s = strerror_r(err, buf, sizeof(buf)); -#else /* POSIX variant always returns message in buffer */ - s = buf; - if (strerror_r(err, buf, sizeof(buf))) - buf[0] = 0; -#endif - return strprintf("%s (%d)", s, err); + // On BSD sockets implementations, NetworkErrorString is the same as SysErrorString. + return SysErrorString(err); } #endif diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp index 50f6aee419..8614bd1176 100644 --- a/src/util/spanparsing.cpp +++ b/src/util/spanparsing.cpp @@ -48,20 +48,4 @@ Span<const char> Expr(Span<const char>& sp) return ret; } -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) -{ - std::vector<Span<const char>> ret; - auto it = sp.begin(); - auto start = it; - while (it != sp.end()) { - if (*it == sep) { - ret.emplace_back(start, it); - start = it + 1; - } - ++it; - } - ret.emplace_back(start, it); - return ret; -} - } // namespace spanparsing diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h index fa2e698e6d..51795271de 100644 --- a/src/util/spanparsing.h +++ b/src/util/spanparsing.h @@ -8,6 +8,7 @@ #include <span.h> #include <string> +#include <string_view> #include <vector> namespace spanparsing { @@ -36,6 +37,30 @@ bool Func(const std::string& str, Span<const char>& sp); */ Span<const char> Expr(Span<const char>& sp); +/** Split a string on any char found in separators, returning a vector. + * + * If sep does not occur in sp, a singleton with the entirety of sp is returned. + * + * Note that this function does not care about braces, so splitting + * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. + */ +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, std::string_view separators) +{ + std::vector<T> ret; + auto it = sp.begin(); + auto start = it; + while (it != sp.end()) { + if (separators.find(*it) != std::string::npos) { + ret.emplace_back(start, it); + start = it + 1; + } + ++it; + } + ret.emplace_back(start, it); + return ret; +} + /** Split a string on every instance of sep, returning a vector. * * If sep does not occur in sp, a singleton with the entirety of sp is returned. @@ -43,7 +68,11 @@ Span<const char> Expr(Span<const char>& sp); * Note that this function does not care about braces, so splitting * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. */ -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep); +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, char sep) +{ + return Split<T>(sp, std::string_view{&sep, 1}); +} } // namespace spanparsing diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 940fa90da2..bcedd4f517 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -9,6 +9,7 @@ #include <tinyformat.h> #include <algorithm> +#include <array> #include <cstdlib> #include <cstring> #include <limits> @@ -24,15 +25,15 @@ static const std::string SAFE_CHARS[] = CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI }; -std::string SanitizeString(const std::string& str, int rule) +std::string SanitizeString(std::string_view str, int rule) { - std::string strResult; - for (std::string::size_type i = 0; i < str.size(); i++) - { - if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) - strResult.push_back(str[i]); + std::string result; + for (char c : str) { + if (SAFE_CHARS[rule].find(c) != std::string::npos) { + result.push_back(c); + } } - return strResult; + return result; } const signed char p_util_hexdigit[256] = @@ -58,56 +59,43 @@ signed char HexDigit(char c) return p_util_hexdigit[(unsigned char)c]; } -bool IsHex(const std::string& str) +bool IsHex(std::string_view str) { - for(std::string::const_iterator it(str.begin()); it != str.end(); ++it) - { - if (HexDigit(*it) < 0) - return false; + for (char c : str) { + if (HexDigit(c) < 0) return false; } return (str.size() > 0) && (str.size()%2 == 0); } -bool IsHexNumber(const std::string& str) +bool IsHexNumber(std::string_view str) { - size_t starting_location = 0; - if (str.size() > 2 && *str.begin() == '0' && *(str.begin()+1) == 'x') { - starting_location = 2; - } - for (const char c : str.substr(starting_location)) { + if (str.substr(0, 2) == "0x") str.remove_prefix(2); + for (char c : str) { if (HexDigit(c) < 0) return false; } // Return false for empty string or "0x". - return (str.size() > starting_location); + return str.size() > 0; } -std::vector<unsigned char> ParseHex(const char* psz) +std::vector<unsigned char> ParseHex(std::string_view str) { // convert hex dump to vector std::vector<unsigned char> vch; - while (true) - { - while (IsSpace(*psz)) - psz++; - signed char c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - auto n{uint8_t(c << 4)}; - c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - n |= c; - vch.push_back(n); + auto it = str.begin(); + while (it != str.end() && it + 1 != str.end()) { + if (IsSpace(*it)) { + ++it; + continue; + } + auto c1 = HexDigit(*(it++)); + auto c2 = HexDigit(*(it++)); + if (c1 < 0 || c2 < 0) break; + vch.push_back(uint8_t(c1 << 4) | c2); } return vch; } -std::vector<unsigned char> ParseHex(const std::string& str) -{ - return ParseHex(str.c_str()); -} - -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut) +void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut) { size_t colon = in.find_last_of(':'); // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator @@ -139,7 +127,7 @@ std::string EncodeBase64(Span<const unsigned char> input) return str; } -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str) { static const int8_t decode64_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -157,46 +145,23 @@ std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode64_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 4 != 0) return {}; + /* One or two = characters at the end are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); std::vector<unsigned char> ret; - ret.reserve((val.size() * 3) / 4); - bool valid = ConvertBits<6, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 4 == 0 && p - q < 4; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 3) / 4); + bool valid = ConvertBits<6, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode64_table[uint8_t(c)]; } + ); + if (!valid) return {}; return ret; } -std::string DecodeBase64(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase64(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); -} - std::string EncodeBase32(Span<const unsigned char> input, bool pad) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; @@ -212,12 +177,12 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad) return str; } -std::string EncodeBase32(const std::string& str, bool pad) +std::string EncodeBase32(std::string_view str, bool pad) { return EncodeBase32(MakeUCharSpan(str), pad); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str) { static const int8_t decode32_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -235,49 +200,29 @@ std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode32_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 8 != 0) return {}; + /* 1, 3, 4, or 6 padding '=' suffix characters are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); std::vector<unsigned char> ret; - ret.reserve((val.size() * 5) / 8); - bool valid = ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 8 == 0 && p - q < 8; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 5) / 8); + bool valid = ConvertBits<5, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode32_table[uint8_t(c)]; } + ); - return ret; -} + if (!valid) return {}; -std::string DecodeBase32(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase32(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); + return ret; } namespace { template <typename T> -bool ParseIntegral(const std::string& str, T* out) +bool ParseIntegral(std::string_view str, T* out) { static_assert(std::is_integral<T>::value); // Replicate the exact behavior of strtol/strtoll/strtoul/strtoull when @@ -296,37 +241,37 @@ bool ParseIntegral(const std::string& str, T* out) } }; // namespace -bool ParseInt32(const std::string& str, int32_t* out) +bool ParseInt32(std::string_view str, int32_t* out) { return ParseIntegral<int32_t>(str, out); } -bool ParseInt64(const std::string& str, int64_t* out) +bool ParseInt64(std::string_view str, int64_t* out) { return ParseIntegral<int64_t>(str, out); } -bool ParseUInt8(const std::string& str, uint8_t* out) +bool ParseUInt8(std::string_view str, uint8_t* out) { return ParseIntegral<uint8_t>(str, out); } -bool ParseUInt16(const std::string& str, uint16_t* out) +bool ParseUInt16(std::string_view str, uint16_t* out) { return ParseIntegral<uint16_t>(str, out); } -bool ParseUInt32(const std::string& str, uint32_t* out) +bool ParseUInt32(std::string_view str, uint32_t* out) { return ParseIntegral<uint32_t>(str, out); } -bool ParseUInt64(const std::string& str, uint64_t* out) +bool ParseUInt64(std::string_view str, uint64_t* out) { return ParseIntegral<uint64_t>(str, out); } -std::string FormatParagraph(const std::string& in, size_t width, size_t indent) +std::string FormatParagraph(std::string_view in, size_t width, size_t indent) { assert(width >= indent); std::stringstream out; @@ -395,7 +340,7 @@ static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantiss return true; } -bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) +bool ParseFixedPoint(std::string_view val, int decimals, int64_t *amount_out) { int64_t mantissa = 0; int64_t exponent = 0; @@ -487,14 +432,14 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -std::string ToLower(const std::string& str) +std::string ToLower(std::string_view str) { std::string r; for (auto ch : str) r += ToLower(ch); return r; } -std::string ToUpper(const std::string& str) +std::string ToUpper(std::string_view str) { std::string r; for (auto ch : str) r += ToUpper(ch); @@ -508,21 +453,41 @@ std::string Capitalize(std::string str) return str; } +namespace { + +using ByteAsHex = std::array<char, 2>; + +constexpr std::array<ByteAsHex, 256> CreateByteToHexMap() +{ + constexpr char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + std::array<ByteAsHex, 256> byte_to_hex{}; + for (size_t i = 0; i < byte_to_hex.size(); ++i) { + byte_to_hex[i][0] = hexmap[i >> 4]; + byte_to_hex[i][1] = hexmap[i & 15]; + } + return byte_to_hex; +} + +} // namespace + std::string HexStr(const Span<const uint8_t> s) { std::string rv(s.size() * 2, '\0'); - static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - auto it = rv.begin(); + static constexpr auto byte_to_hex = CreateByteToHexMap(); + static_assert(sizeof(byte_to_hex) == 512); + + char* it = rv.data(); for (uint8_t v : s) { - *it++ = hexmap[v >> 4]; - *it++ = hexmap[v & 15]; + std::memcpy(it, byte_to_hex[v].data(), 2); + it += 2; } - assert(it == rv.end()); + + assert(it == rv.data() + rv.size()); return rv; } -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier) +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier) { if (str.empty()) { return std::nullopt; diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1f83fa3ffa..ebb6d88952 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -54,24 +54,21 @@ enum class ByteUnit : uint64_t { * @param[in] rule The set of safe chars to choose (default: least restrictive) * @return A new string without unsafe chars */ -std::string SanitizeString(const std::string& str, int rule = SAFE_CHARS_DEFAULT); -std::vector<unsigned char> ParseHex(const char* psz); -std::vector<unsigned char> ParseHex(const std::string& str); +std::string SanitizeString(std::string_view str, int rule = SAFE_CHARS_DEFAULT); +std::vector<unsigned char> ParseHex(std::string_view str); signed char HexDigit(char c); /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ -bool IsHex(const std::string& str); +bool IsHex(std::string_view str); /** * Return true if the string is a hex number, optionally prefixed with "0x" */ -bool IsHexNumber(const std::string& str); -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase64(const std::string& str, bool* pf_invalid = nullptr); +bool IsHexNumber(std::string_view str); +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str); std::string EncodeBase64(Span<const unsigned char> input); inline std::string EncodeBase64(Span<const std::byte> input) { return EncodeBase64(MakeUCharSpan(input)); } -inline std::string EncodeBase64(const std::string& str) { return EncodeBase64(MakeUCharSpan(str)); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); +inline std::string EncodeBase64(std::string_view str) { return EncodeBase64(MakeUCharSpan(str)); } +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str); /** * Base32 encode. @@ -85,9 +82,9 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); * If `pad` is true, then the output will be padded with '=' so that its length * is a multiple of 8. */ -std::string EncodeBase32(const std::string& str, bool pad = true); +std::string EncodeBase32(std::string_view str, bool pad = true); -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); +void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut); // LocaleIndependentAtoi is provided for backwards compatibility reasons. // @@ -101,12 +98,12 @@ void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); // undefined behavior, while this function returns the maximum or minimum // values, respectively. template <typename T> -T LocaleIndependentAtoi(const std::string& str) +T LocaleIndependentAtoi(std::string_view str) { static_assert(std::is_integral<T>::value); T result; // Emulate atoi(...) handling of white space and leading +/-. - std::string s = TrimString(str); + std::string_view s = TrimStringView(str); if (!s.empty() && s[0] == '+') { if (s.length() >= 2 && s[1] == '-') { return 0; @@ -162,7 +159,7 @@ constexpr inline bool IsSpace(char c) noexcept { * parsed value is not in the range representable by the type T. */ template <typename T> -std::optional<T> ToIntegral(const std::string& str) +std::optional<T> ToIntegral(std::string_view str) { static_assert(std::is_integral<T>::value); T result; @@ -178,42 +175,42 @@ std::optional<T> ToIntegral(const std::string& str) * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseInt32(const std::string& str, int32_t *out); +[[nodiscard]] bool ParseInt32(std::string_view str, int32_t *out); /** * Convert string to signed 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseInt64(const std::string& str, int64_t *out); +[[nodiscard]] bool ParseInt64(std::string_view str, int64_t *out); /** * Convert decimal string to unsigned 8-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt8(const std::string& str, uint8_t *out); +[[nodiscard]] bool ParseUInt8(std::string_view str, uint8_t *out); /** * Convert decimal string to unsigned 16-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if the entire string could not be parsed or if overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt16(const std::string& str, uint16_t* out); +[[nodiscard]] bool ParseUInt16(std::string_view str, uint16_t* out); /** * Convert decimal string to unsigned 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt32(const std::string& str, uint32_t *out); +[[nodiscard]] bool ParseUInt32(std::string_view str, uint32_t *out); /** * Convert decimal string to unsigned 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt64(const std::string& str, uint64_t *out); +[[nodiscard]] bool ParseUInt64(std::string_view str, uint64_t *out); /** * Convert a span of bytes to a lower-case hexadecimal string. @@ -226,7 +223,7 @@ inline std::string HexStr(const Span<const std::byte> s) { return HexStr(MakeUCh * Format a paragraph of text to a fixed width, adding spaces for * indentation to any added line. */ -std::string FormatParagraph(const std::string& in, size_t width = 79, size_t indent = 0); +std::string FormatParagraph(std::string_view in, size_t width = 79, size_t indent = 0); /** * Timing-attack-resistant comparison. @@ -248,17 +245,28 @@ bool TimingResistantEqual(const T& a, const T& b) * @returns true on success, false on error. * @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. */ -[[nodiscard]] bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +[[nodiscard]] bool ParseFixedPoint(std::string_view, int decimals, int64_t *amount_out); + +namespace { +/** Helper class for the default infn argument to ConvertBits (just returns the input). */ +struct IntIdentity +{ + [[maybe_unused]] int operator()(int x) const { return x; } +}; + +} // namespace /** Convert from one power-of-2 number base to another. */ -template<int frombits, int tobits, bool pad, typename O, typename I> -bool ConvertBits(const O& outfn, I it, I end) { +template<int frombits, int tobits, bool pad, typename O, typename It, typename I = IntIdentity> +bool ConvertBits(O outfn, It it, It end, I infn = {}) { size_t acc = 0; size_t bits = 0; constexpr size_t maxv = (1 << tobits) - 1; constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; while (it != end) { - acc = ((acc << frombits) | *it) & max_acc; + int v = infn(*it); + if (v < 0) return false; + acc = ((acc << frombits) | v) & max_acc; bits += frombits; while (bits >= tobits) { bits -= tobits; @@ -298,7 +306,7 @@ constexpr char ToLower(char c) * @param[in] str the string to convert to lowercase. * @returns lowercased equivalent of str */ -std::string ToLower(const std::string& str); +std::string ToLower(std::string_view str); /** * Converts the given character to its uppercase equivalent. @@ -324,7 +332,7 @@ constexpr char ToUpper(char c) * @param[in] str the string to convert to uppercase. * @returns UPPERCASED EQUIVALENT OF str */ -std::string ToUpper(const std::string& str); +std::string ToUpper(std::string_view str); /** * Capitalizes the first character of the given string. @@ -348,6 +356,6 @@ std::string Capitalize(std::string str); * @returns optional uint64_t bytes from str or nullopt * if ToIntegral is false, str is empty, trailing whitespace or overflow */ -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier); +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier); #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/string.cpp b/src/util/string.cpp index 8ea3a1afc6..d05222e8b8 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -3,3 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/string.h> + +#include <boost/algorithm/string/replace.hpp> + +void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute) +{ + boost::replace_all(in_out, search, substitute); +} diff --git a/src/util/string.h b/src/util/string.h index a3b8df8d78..df20e34ae9 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -5,27 +5,46 @@ #ifndef BITCOIN_UTIL_STRING_H #define BITCOIN_UTIL_STRING_H -#include <attributes.h> +#include <util/spanparsing.h> #include <algorithm> #include <array> +#include <cstdint> #include <cstring> #include <locale> #include <sstream> #include <string> +#include <string_view> #include <vector> -[[nodiscard]] inline std::string TrimString(const std::string& str, const std::string& pattern = " \f\n\r\t\v") +void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute); + +[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep) +{ + return spanparsing::Split<std::string>(str, sep); +} + +[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators) +{ + return spanparsing::Split<std::string>(str, separators); +} + +[[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v") { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { - return std::string(); + return {}; } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } -[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix) +[[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v") +{ + return std::string(TrimStringView(str, pattern)); +} + +[[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix) { if (str.substr(0, prefix.size()) == prefix) { return str.substr(prefix.size()); @@ -33,6 +52,11 @@ return str; } +[[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix) +{ + return std::string(RemovePrefixView(str, prefix)); +} + /** * Join a list of items * @@ -52,14 +76,14 @@ auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_o return ret; } -template <typename T> -T Join(const std::vector<T>& list, const T& separator) +template <typename T, typename T2> +T Join(const std::vector<T>& list, const T2& separator) { return Join(list, separator, [](const T& i) { return i; }); } // Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above. -inline std::string Join(const std::vector<std::string>& list, const std::string& separator) +inline std::string Join(const std::vector<std::string>& list, std::string_view separator) { return Join<std::string>(list, separator); } @@ -75,9 +99,12 @@ inline std::string MakeUnorderedList(const std::vector<std::string>& items) /** * Check if a string does not contain any embedded NUL (\0) characters */ -[[nodiscard]] inline bool ValidAsCString(const std::string& str) noexcept +[[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept { - return str.size() == strlen(str.c_str()); + for (auto c : str) { + if (c == 0) return false; + } + return true; } /** diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp new file mode 100644 index 0000000000..391ddd3560 --- /dev/null +++ b/src/util/syserror.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <tinyformat.h> +#include <util/syserror.h> + +#include <cstring> + +std::string SysErrorString(int err) +{ + char buf[1024]; + /* Too bad there are three incompatible implementations of the + * thread-safe strerror. */ + const char *s = nullptr; +#ifdef WIN32 + if (strerror_s(buf, sizeof(buf), err) == 0) s = buf; +#else +#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ + s = strerror_r(err, buf, sizeof(buf)); +#else /* POSIX variant always returns message in buffer */ + if (strerror_r(err, buf, sizeof(buf)) == 0) s = buf; +#endif +#endif + if (s != nullptr) { + return strprintf("%s (%d)", s, err); + } else { + return strprintf("Unknown error (%d)", err); + } +} diff --git a/src/util/syserror.h b/src/util/syserror.h new file mode 100644 index 0000000000..a54ba553ee --- /dev/null +++ b/src/util/syserror.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010-2022 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_UTIL_SYSERROR_H +#define BITCOIN_UTIL_SYSERROR_H + +#include <string> + +/** Return system error string from errno value. Use this instead of + * std::strerror, which is not thread-safe. For network errors use + * NetworkErrorString from sock.h instead. + */ +std::string SysErrorString(int err); + +#endif // BITCOIN_UTIL_SYSERROR_H diff --git a/src/util/system.cpp b/src/util/system.cpp index a7e66defcd..44ebf5cb3e 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -25,6 +25,7 @@ #include <util/getuniquepath.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/syserror.h> #include <util/translation.h> @@ -75,7 +76,6 @@ #include <malloc.h> #endif -#include <boost/algorithm/string/replace.hpp> #include <univalue.h> #include <fstream> @@ -104,7 +104,7 @@ static Mutex cs_dir_locks; */ static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only) +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; @@ -128,7 +128,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b return true; } -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name) +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); @@ -853,8 +853,8 @@ static bool GetConfigOptions(std::istream& stream, const std::string& filepath, error = strprintf("parse error on line %i: %s, options in configuration file must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { - std::string name = prefix + TrimString(str.substr(0, pos), pattern); - std::string value = TrimString(str.substr(pos + 1), pattern); + std::string name = prefix + TrimString(std::string_view{str}.substr(0, pos), pattern); + std::string_view value = TrimStringView(std::string_view{str}.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); return false; @@ -1252,7 +1252,7 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate) std::string ShellEscape(const std::string& arg) { std::string escaped = arg; - boost::replace_all(escaped, "'", "'\"'\"'"); + ReplaceAll(escaped, "'", "'\"'\"'"); return "'" + escaped + "'"; } #endif @@ -1374,7 +1374,7 @@ void ScheduleBatchPriority() const static sched_param param{}; const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (rc != 0) { - LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc)); + LogPrintf("Failed to pthread_setschedparam: %s\n", SysErrorString(rc)); } #endif } diff --git a/src/util/system.h b/src/util/system.h index a66b597d41..a7f4d16911 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -76,8 +76,8 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); */ [[nodiscard]] bool RenameOver(fs::path src, fs::path dest); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name); +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only=false); +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name); bool DirIsWritable(const fs::path& directory); bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); diff --git a/src/util/time.h b/src/util/time.h index 9d92b23725..041b8aa6a1 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -14,6 +14,10 @@ using namespace std::chrono_literals; +using SteadySeconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::seconds>; +using SteadyMilliseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::milliseconds>; +using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>; + void UninterruptibleSleep(const std::chrono::microseconds& n); /** @@ -67,6 +71,15 @@ std::chrono::seconds GetMockTime(); /** Return system time (or mocked time, if set) */ template <typename T> T GetTime(); +/** + * Return the current time point cast to the given precicion. Only use this + * when an exact precicion is needed, otherwise use T::clock::now() directly. + */ +template <typename T> +T Now() +{ + return std::chrono::time_point_cast<typename T::duration>(T::clock::now()); +} /** * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date} diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp index 4c091cd2e6..49456814e2 100644 --- a/src/util/tokenpipe.cpp +++ b/src/util/tokenpipe.cpp @@ -3,7 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/tokenpipe.h> +#if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> +#endif #ifndef WIN32 diff --git a/src/validation.cpp b/src/validation.cpp index 58686632f9..b5d6a66088 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -19,7 +19,6 @@ #include <deploymentstatus.h> #include <flatfile.h> #include <hash.h> -#include <index/blockfilterindex.h> #include <logging.h> #include <logging/timer.h> #include <node/blockstorage.h> @@ -56,12 +55,11 @@ #include <warnings.h> #include <algorithm> +#include <deque> #include <numeric> #include <optional> #include <string> -#include <boost/algorithm/string/replace.hpp> - using node::BLOCKFILE_CHUNK_SIZE; using node::BlockManager; using node::BlockMap; @@ -106,6 +104,12 @@ const std::vector<std::string> CHECKLEVEL_DOC { "level 4 tries to reconnect the blocks", "each level includes the checks of the previous levels", }; +/** The number of blocks to keep below the deepest prune lock. + * There is nothing special about this number. It is higher than what we + * expect to see in regular mainnet reorgs, but not so high that it would + * noticeably interfere with the pruning mechanism. + * */ +static constexpr int PRUNE_LOCK_BUFFER{10}; /** * Mutex to guard access to validation specific variables, such as reading @@ -246,7 +250,12 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, maxInputHeight = std::max(maxInputHeight, height); } } - lp->maxInputBlock = tip->GetAncestor(maxInputHeight); + // tip->GetAncestor(maxInputHeight) should never return a nullptr + // because maxInputHeight is always less than the tip height. + // It would, however, be a bad bug to continue execution, since a + // LockPoints object with the maxInputBlock member set to nullptr + // signifies no relative lock time. + lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight)); } } return EvaluateSequenceLocks(index, lockPair); @@ -1111,6 +1120,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& if (!ConsensusScriptChecks(args, ws)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since PolicyScriptChecks() passed, this should never fail. + Assume(false); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s", @@ -1125,6 +1135,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& m_limit_descendant_size, unused_err_string)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail. + Assume(false); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! Mempool ancestors or descendants were underestimated: %s", @@ -1138,6 +1149,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& if (!Finalize(args, ws)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since LimitMempoolSize() won't be called, this should never fail. + Assume(false); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! Adding to mempool failed: %s", ws.m_ptx->GetHash().ToString())); @@ -1483,7 +1495,7 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) } CoinsViews::CoinsViews( - std::string ldb_name, + fs::path ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe) : m_dbview( @@ -1511,7 +1523,7 @@ void CChainState::InitCoinsDB( size_t cache_size_bytes, bool in_memory, bool should_wipe, - std::string leveldb_name) + fs::path leveldb_name) { if (m_from_snapshot_blockhash) { leveldb_name += "_" + m_from_snapshot_blockhash->ToString(); @@ -1569,7 +1581,7 @@ static void AlertNotify(const std::string& strMessage) std::string singleQuote("'"); std::string safeStatus = SanitizeString(strMessage); safeStatus = singleQuote+safeStatus+singleQuote; - boost::replace_all(strCmd, "%s", safeStatus); + ReplaceAll(strCmd, "%s", safeStatus); std::thread t(runCommand, strCmd); t.detach(); // thread runs free @@ -1916,7 +1928,7 @@ public: } }; -static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_main); +static std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> warningcache GUARDED_BY(cs_main); static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Consensus::Params& consensusparams) { @@ -2338,12 +2350,24 @@ bool CChainState::FlushStateToDisk( CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(); LOCK(m_blockman.cs_LastBlockFile); if (fPruneMode && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && !fReindex) { - // make sure we don't prune above the blockfilterindexes bestblocks + // make sure we don't prune above any of the prune locks bestblocks // pruning is height-based - int last_prune = m_chain.Height(); // last height we can prune - ForEachBlockFilterIndex([&](BlockFilterIndex& index) { - last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height)); - }); + int last_prune{m_chain.Height()}; // last height we can prune + std::optional<std::string> limiting_lock; // prune lock that actually was the limiting factor, only used for logging + + for (const auto& prune_lock : m_blockman.m_prune_locks) { + if (prune_lock.second.height_first == std::numeric_limits<int>::max()) continue; + // Remove the buffer and one additional block here to get actual height that is outside of the buffer + const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1}; + last_prune = std::max(1, std::min(last_prune, lock_height)); + if (last_prune == lock_height) { + limiting_lock = prune_lock.first; + } + } + + if (limiting_lock) { + LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune); + } if (nManualPruneHeight > 0) { LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH); @@ -2533,7 +2557,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); - ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache[bit]); + ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache.at(bit)); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); if (state == ThresholdState::ACTIVE) { @@ -2581,6 +2605,18 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr assert(flushed); } LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI); + + { + // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg + const int max_height_first{pindexDelete->nHeight - 1}; + for (auto& prune_lock : m_blockman.m_prune_locks) { + if (prune_lock.second.height_first <= max_height_first) continue; + + prune_lock.second.height_first = max_height_first; + LogPrint(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first); + } + } + // Write the chain state to disk, if necessary. if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; @@ -4077,10 +4113,11 @@ bool CChainState::ReplayBlocks() // Roll forward from the forking point to the new tip. int nForkHeight = pindexFork ? pindexFork->nHeight : 0; for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight; ++nHeight) { - const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight); - LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight); + const CBlockIndex& pindex{*Assert(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)) return false; + if (!RollforwardBlock(&pindex, cache)) return false; } cache.SetBestBlock(pindexNew->GetBlockHash()); @@ -4114,20 +4151,6 @@ void CChainState::UnloadBlockIndex() setBlockIndexCandidates.clear(); } -// May NOT be used after any connections are up as much -// of the peer-processing logic assumes a consistent -// block index state -void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman) -{ - AssertLockHeld(::cs_main); - chainman.Unload(); - if (mempool) mempool->clear(); - g_versionbitscache.Clear(); - for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { - warningcache[b].clear(); - } -} - bool ChainstateManager::LoadBlockIndex() { AssertLockHeld(cs_main); @@ -5158,20 +5181,6 @@ bool ChainstateManager::IsSnapshotActive() const return m_snapshot_chainstate && m_active_chainstate == m_snapshot_chainstate.get(); } -void ChainstateManager::Unload() -{ - AssertLockHeld(::cs_main); - for (CChainState* chainstate : this->GetAll()) { - chainstate->m_chain.SetTip(nullptr); - chainstate->UnloadBlockIndex(); - } - - m_failed_blocks.clear(); - m_blockman.Unload(); - m_best_header = nullptr; - m_best_invalid = nullptr; -} - void ChainstateManager::MaybeRebalanceCaches() { AssertLockHeld(::cs_main); @@ -5202,3 +5211,15 @@ void ChainstateManager::MaybeRebalanceCaches() } } } + +ChainstateManager::~ChainstateManager() +{ + LOCK(::cs_main); + + // TODO: The version bits cache and warning cache should probably become + // non-globals + g_versionbitscache.Clear(); + for (auto& i : warningcache) { + i.clear(); + } +} diff --git a/src/validation.h b/src/validation.h index 2e7ab42f88..42e41502f9 100644 --- a/src/validation.h +++ b/src/validation.h @@ -134,8 +134,6 @@ extern arith_uint256 nMinimumChainWork; /** Documentation for argument 'checklevel'. */ extern const std::vector<std::string> CHECKLEVEL_DOC; -/** Unload database information */ -void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Run instances of script checking worker threads */ void StartScriptCheckWorkerThreads(int threads_num); /** Stop all of the script checking worker threads */ @@ -330,7 +328,8 @@ public: bool operator()(); - void swap(CScriptCheck &check) { + void swap(CScriptCheck& check) noexcept + { std::swap(ptxTo, check.ptxTo); std::swap(m_tx_out, check.m_tx_out); std::swap(nIn, check.nIn); @@ -425,7 +424,7 @@ public: //! state to disk, which should not be done until the health of the database is verified. //! //! All arguments forwarded onto CCoinsViewDB. - CoinsViews(std::string ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); + CoinsViews(fs::path ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); //! Initialize the CCoinsViewCache member. void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); @@ -519,7 +518,7 @@ public: size_t cache_size_bytes, bool in_memory, bool should_wipe, - std::string leveldb_name = "chainstate"); + fs::path leveldb_name = "chainstate"); //! Initialize the in-memory coins cache (to be done after the health of the on-disk database //! is verified). @@ -831,10 +830,9 @@ private: //! If true, the assumed-valid chainstate has been fully validated //! by the background validation chainstate. - bool m_snapshot_validated{false}; + bool m_snapshot_validated GUARDED_BY(::cs_main){false}; - CBlockIndex* m_best_invalid; - friend bool node::BlockManager::LoadBlockIndex(const Consensus::Params&); + CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( @@ -941,7 +939,7 @@ public: std::optional<uint256> SnapshotBlockhash() const; //! Is there a snapshot in use and has it been fully validated? - bool IsSnapshotValidated() const { return m_snapshot_validated; } + bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return m_snapshot_validated; } /** * Process an incoming block. This only returns after the best known valid @@ -989,17 +987,11 @@ public: //! Load the block tree and coins database from disk, initializing state if we're running with -reindex bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main); - //! Unload block index and chain data before shutdown. - void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - //! Check to see if caches are out of balance and if so, call //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - ~ChainstateManager() { - LOCK(::cs_main); - UnloadBlockIndex(/*mempool=*/nullptr, *this); - } + ~ChainstateManager(); }; using FopenFn = std::function<FILE*(const fs::path&, const char*)>; diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 1e07ff23ae..edc4633c01 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -40,7 +40,7 @@ public: // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; - explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} + explicit MainSignalsInstance(CScheduler& scheduler LIFETIMEBOUND) : m_schedulerClient(scheduler) {} void Register(std::shared_ptr<CValidationInterface> callbacks) { @@ -92,7 +92,7 @@ static CMainSignals g_signals; void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); - m_internals.reset(new MainSignalsInstance(&scheduler)); + m_internals = std::make_unique<MainSignalsInstance>(scheduler); } void CMainSignals::UnregisterBackgroundSignalScheduler() diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 7a297c2bbb..dc85655028 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -2,8 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <versionbits.h> #include <consensus/params.h> +#include <util/check.h> +#include <versionbits.h> ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const { @@ -158,7 +159,7 @@ int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* // if we are computing for the last block of a period, then pindexPrev points to the second to last block of the period, and // if we are computing for the first block of a period, then pindexPrev points to the last block of the previous period. // The parent of the genesis block is represented by nullptr. - pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); + pindexPrev = Assert(pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod))); const CBlockIndex* previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index 0d0456af4b..1aa0339445 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -261,7 +261,7 @@ BerkeleyBatch::SafeDbt::operator Dbt*() bool BerkeleyDatabase::Verify(bilingual_str& errorStr) { fs::path walletDir = env->Directory(); - fs::path file_path = walletDir / strFile; + fs::path file_path = walletDir / m_filename; LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion()); LogPrintf("Using wallet %s\n", fs::PathToString(file_path)); @@ -275,6 +275,7 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr) assert(m_refcount == 0); Db db(env->dbenv.get(), 0); + const std::string strFile = fs::PathToString(m_filename); int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); if (result != 0) { errorStr = strprintf(_("%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup."), fs::quoted(fs::PathToString(file_path))); @@ -297,11 +298,11 @@ BerkeleyDatabase::~BerkeleyDatabase() { if (env) { LOCK(cs_db); - env->CloseDb(strFile); + env->CloseDb(m_filename); assert(!m_db); - size_t erased = env->m_databases.erase(strFile); + size_t erased = env->m_databases.erase(m_filename); assert(erased == 1); - env->m_fileids.erase(strFile); + env->m_fileids.erase(fs::PathToString(m_filename)); } } @@ -313,7 +314,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, b fFlushOnClose = fFlushOnCloseIn; env = database.env.get(); pdb = database.m_db.get(); - strFile = database.strFile; + strFile = fs::PathToString(database.m_filename); if (!Exists(std::string("version"))) { bool fTmp = fReadOnly; fReadOnly = false; @@ -335,6 +336,7 @@ void BerkeleyDatabase::Open() if (m_db == nullptr) { int ret; std::unique_ptr<Db> pdb_temp = std::make_unique<Db>(env->dbenv.get(), 0); + const std::string strFile = fs::PathToString(m_filename); bool fMockDb = env->IsMock(); if (fMockDb) { @@ -407,11 +409,11 @@ void BerkeleyBatch::Close() Flush(); } -void BerkeleyEnvironment::CloseDb(const std::string& strFile) +void BerkeleyEnvironment::CloseDb(const fs::path& filename) { { LOCK(cs_db); - auto it = m_databases.find(strFile); + auto it = m_databases.find(filename); assert(it != m_databases.end()); BerkeleyDatabase& database = it->second.get(); if (database.m_db) { @@ -434,12 +436,12 @@ void BerkeleyEnvironment::ReloadDbEnv() return true; }); - std::vector<std::string> filenames; + std::vector<fs::path> filenames; for (auto it : m_databases) { filenames.push_back(it.first); } // Close the individual Db's - for (const std::string& filename : filenames) { + for (const fs::path& filename : filenames) { CloseDb(filename); } // Reset the environment @@ -454,9 +456,10 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) while (true) { { LOCK(cs_db); + const std::string strFile = fs::PathToString(m_filename); if (m_refcount <= 0) { // Flush log data to the dat file - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; @@ -508,7 +511,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) } if (fSuccess) { db.Close(); - env->CloseDb(strFile); + env->CloseDb(m_filename); if (pdbCopy->close(0)) fSuccess = false; } else { @@ -544,13 +547,14 @@ void BerkeleyEnvironment::Flush(bool fShutdown) LOCK(cs_db); bool no_dbs_accessed = true; for (auto& db_it : m_databases) { - std::string strFile = db_it.first; + const fs::path& filename = db_it.first; int nRefCount = db_it.second.get().m_refcount; if (nRefCount < 0) continue; + const std::string strFile = fs::PathToString(filename); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); if (nRefCount == 0) { // Move log data to the dat file - CloseDb(strFile); + CloseDb(filename); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile); dbenv->txn_checkpoint(0, 0, 0); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile); @@ -590,11 +594,12 @@ bool BerkeleyDatabase::PeriodicFlush() // Don't flush if there haven't been any batch writes for this database. if (m_refcount < 0) return false; + const std::string strFile = fs::PathToString(m_filename); LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile); int64_t nStart = GetTimeMillis(); // Flush wallet file so it's self contained - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; @@ -605,6 +610,7 @@ bool BerkeleyDatabase::PeriodicFlush() bool BerkeleyDatabase::Backup(const std::string& strDest) const { + const std::string strFile = fs::PathToString(m_filename); while (true) { { @@ -612,14 +618,14 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) const if (m_refcount <= 0) { // Flush log data to the dat file - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); // Copy wallet file - fs::path pathSrc = env->Directory() / strFile; + fs::path pathSrc = env->Directory() / m_filename; fs::path pathDest(fs::PathFromString(strDest)); if (fs::is_directory(pathDest)) - pathDest /= fs::PathFromString(strFile); + pathDest /= m_filename; try { if (fs::exists(pathDest) && fs::equivalent(pathSrc, pathDest)) { @@ -683,10 +689,10 @@ bool BerkeleyBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& // Convert to streams ssKey.SetType(SER_DISK); ssKey.clear(); - ssKey.write({BytePtr(datKey.get_data()), datKey.get_size()}); + ssKey.write({AsBytePtr(datKey.get_data()), datKey.get_size()}); ssValue.SetType(SER_DISK); ssValue.clear(); - ssValue.write({BytePtr(datValue.get_data()), datValue.get_size()}); + ssValue.write({AsBytePtr(datValue.get_data()), datValue.get_size()}); return true; } @@ -758,7 +764,7 @@ bool BerkeleyBatch::ReadKey(CDataStream&& key, CDataStream& value) SafeDbt datValue; int ret = pdb->get(activeTxn, datKey, datValue, 0); if (ret == 0 && datValue.get_data() != nullptr) { - value.write({BytePtr(datValue.get_data()), datValue.get_size()}); + value.write({AsBytePtr(datValue.get_data()), datValue.get_size()}); return true; } return false; @@ -831,7 +837,7 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con std::unique_ptr<BerkeleyDatabase> db; { LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor - std::string data_filename = fs::PathToString(data_file.filename()); + fs::path data_filename = data_file.filename(); std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory); if (env->m_databases.count(data_filename)) { error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename))); diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h index fd6c76183e..1c99e1f9af 100644 --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -51,7 +51,7 @@ private: public: std::unique_ptr<DbEnv> dbenv; - std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases; + std::map<fs::path, std::reference_wrapper<BerkeleyDatabase>> m_databases; std::unordered_map<std::string, WalletDatabaseFileId> m_fileids; std::condition_variable_any m_db_in_use; bool m_use_shared_memory; @@ -70,7 +70,7 @@ public: void Flush(bool fShutdown); void CheckpointLSN(const std::string& strFile); - void CloseDb(const std::string& strFile); + void CloseDb(const fs::path& filename); void ReloadDbEnv(); DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) @@ -97,10 +97,10 @@ public: BerkeleyDatabase() = delete; /** Create DB handle to real database */ - BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename, const DatabaseOptions& options) : - WalletDatabase(), env(std::move(env)), strFile(std::move(filename)), m_max_log_mb(options.max_log_mb) + BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, fs::path filename, const DatabaseOptions& options) : + WalletDatabase(), env(std::move(env)), m_filename(std::move(filename)), m_max_log_mb(options.max_log_mb) { - auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this)); + auto inserted = this->env->m_databases.emplace(m_filename, std::ref(*this)); assert(inserted.second); } @@ -141,7 +141,7 @@ public: bool Verify(bilingual_str& error); /** Return path to main database filename */ - std::string Filename() override { return fs::PathToString(env->Directory() / strFile); } + std::string Filename() override { return fs::PathToString(env->Directory() / m_filename); } std::string Format() override { return "bdb"; } /** @@ -158,7 +158,7 @@ public: /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ std::unique_ptr<Db> m_db; - std::string strFile; + fs::path m_filename; int64_t m_max_log_mb; /** Make a BerkeleyBatch connected to this database */ diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 433759e086..0b236e2e48 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -64,7 +64,7 @@ static const size_t TOTAL_TRIES = 100000; std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change) { - SelectionResult result(selection_target); + SelectionResult result(selection_target, SelectionAlgorithm::BNB); CAmount curr_value = 0; std::vector<size_t> curr_selection; // selected utxo indexes @@ -167,7 +167,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng) { - SelectionResult result(target_value); + SelectionResult result(target_value, SelectionAlgorithm::SRD); std::vector<size_t> indexes; indexes.resize(utxo_pool.size()); @@ -249,7 +249,7 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, CAmount change_target, FastRandomContext& rng) { - SelectionResult result(nTargetValue); + SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK); // List of values less than target std::optional<OutputGroup> lowest_larger; @@ -460,4 +460,17 @@ std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue)); } + +std::string GetAlgorithmName(const SelectionAlgorithm algo) +{ + switch (algo) + { + case SelectionAlgorithm::BNB: return "bnb"; + case SelectionAlgorithm::KNAPSACK: return "knapsack"; + case SelectionAlgorithm::SRD: return "srd"; + case SelectionAlgorithm::MANUAL: return "manual"; + // No default case to allow for compiler to warn + } + assert(false); +} } // namespace wallet diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index c1484c0a57..25c672eda0 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -239,21 +239,34 @@ struct OutputGroup */ [[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng); +enum class SelectionAlgorithm : uint8_t +{ + BNB = 0, + KNAPSACK = 1, + SRD = 2, + MANUAL = 3, +}; + +std::string GetAlgorithmName(const SelectionAlgorithm algo); + struct SelectionResult { private: /** Set of inputs selected by the algorithm to use in the transaction */ std::set<COutput> m_selected_inputs; - /** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */ - const CAmount m_target; /** Whether the input values for calculations should be the effective value (true) or normal value (false) */ bool m_use_effective{false}; /** The computed waste */ std::optional<CAmount> m_waste; public: - explicit SelectionResult(const CAmount target) - : m_target(target) {} + /** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */ + const CAmount m_target; + /** The algorithm used to produce this result */ + const SelectionAlgorithm m_algo; + + explicit SelectionResult(const CAmount target, SelectionAlgorithm algo) + : m_target(target), m_algo(algo) {} SelectionResult() = delete; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 7e21126298..7f038eda84 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -94,6 +94,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const #endif argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + argsman.AddArg("-walletcrosschain", strprintf("Allow reusing wallet files across chains (default: %u)", DEFAULT_WALLETCROSSCHAIN), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddHiddenArgs({"-zapwallettxes"}); } diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index b048ddfc6e..b4f01b00de 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -26,8 +26,6 @@ #include <tuple> #include <string> -#include <boost/algorithm/string.hpp> - #include <univalue.h> @@ -546,8 +544,7 @@ RPCHelpMan importwallet() if (line.empty() || line[0] == '#') continue; - std::vector<std::string> vstr; - boost::split(vstr, line, boost::is_any_of(" ")); + std::vector<std::string> vstr = SplitString(line, ' '); if (vstr.size() < 2) continue; CKey key = DecodeSecret(vstr[0]); @@ -915,7 +912,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d case TxoutType::WITNESS_V1_TAPROOT: return "unrecognized script"; } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) @@ -1748,13 +1745,13 @@ RPCHelpMan listdescriptors() {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, - {RPCResult::Type::BOOL, "active", "Activeness flag"}, - {RPCResult::Type::BOOL, "internal", true, "Whether this is an internal or external descriptor; defined only for active descriptors"}, - {RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", { + {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, + {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"}, + {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", { {RPCResult::Type::NUM, "", "Range start inclusive"}, {RPCResult::Type::NUM, "", "Range end inclusive"}, }}, - {RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"}, + {RPCResult::Type::NUM, "next", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"}, }}, }} }}, diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 1291663847..efb4d8fc7e 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -664,79 +664,75 @@ RPCHelpMan abortrescan(); Span<const CRPCCommand> GetWalletRPCCommands() { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // ------------------ ------------------------ - { "rawtransactions", &fundrawtransaction, }, - { "wallet", &abandontransaction, }, - { "wallet", &abortrescan, }, - { "wallet", &addmultisigaddress, }, - { "wallet", &backupwallet, }, - { "wallet", &bumpfee, }, - { "wallet", &psbtbumpfee, }, - { "wallet", &createwallet, }, - { "wallet", &restorewallet, }, - { "wallet", &dumpprivkey, }, - { "wallet", &dumpwallet, }, - { "wallet", &encryptwallet, }, - { "wallet", &getaddressesbylabel, }, - { "wallet", &getaddressinfo, }, - { "wallet", &getbalance, }, - { "wallet", &getnewaddress, }, - { "wallet", &getrawchangeaddress, }, - { "wallet", &getreceivedbyaddress, }, - { "wallet", &getreceivedbylabel, }, - { "wallet", &gettransaction, }, - { "wallet", &getunconfirmedbalance, }, - { "wallet", &getbalances, }, - { "wallet", &getwalletinfo, }, - { "wallet", &importaddress, }, - { "wallet", &importdescriptors, }, - { "wallet", &importmulti, }, - { "wallet", &importprivkey, }, - { "wallet", &importprunedfunds, }, - { "wallet", &importpubkey, }, - { "wallet", &importwallet, }, - { "wallet", &keypoolrefill, }, - { "wallet", &listaddressgroupings, }, - { "wallet", &listdescriptors, }, - { "wallet", &listlabels, }, - { "wallet", &listlockunspent, }, - { "wallet", &listreceivedbyaddress, }, - { "wallet", &listreceivedbylabel, }, - { "wallet", &listsinceblock, }, - { "wallet", &listtransactions, }, - { "wallet", &listunspent, }, - { "wallet", &listwalletdir, }, - { "wallet", &listwallets, }, - { "wallet", &loadwallet, }, - { "wallet", &lockunspent, }, - { "wallet", &newkeypool, }, - { "wallet", &removeprunedfunds, }, - { "wallet", &rescanblockchain, }, - { "wallet", &send, }, - { "wallet", &sendmany, }, - { "wallet", &sendtoaddress, }, - { "wallet", &sethdseed, }, - { "wallet", &setlabel, }, - { "wallet", &settxfee, }, - { "wallet", &setwalletflag, }, - { "wallet", &signmessage, }, - { "wallet", &signrawtransactionwithwallet, }, - { "wallet", &sendall, }, - { "wallet", &unloadwallet, }, - { "wallet", &upgradewallet, }, - { "wallet", &walletcreatefundedpsbt, }, + static const CRPCCommand commands[]{ + {"rawtransactions", &fundrawtransaction}, + {"wallet", &abandontransaction}, + {"wallet", &abortrescan}, + {"wallet", &addmultisigaddress}, + {"wallet", &backupwallet}, + {"wallet", &bumpfee}, + {"wallet", &psbtbumpfee}, + {"wallet", &createwallet}, + {"wallet", &restorewallet}, + {"wallet", &dumpprivkey}, + {"wallet", &dumpwallet}, + {"wallet", &encryptwallet}, + {"wallet", &getaddressesbylabel}, + {"wallet", &getaddressinfo}, + {"wallet", &getbalance}, + {"wallet", &getnewaddress}, + {"wallet", &getrawchangeaddress}, + {"wallet", &getreceivedbyaddress}, + {"wallet", &getreceivedbylabel}, + {"wallet", &gettransaction}, + {"wallet", &getunconfirmedbalance}, + {"wallet", &getbalances}, + {"wallet", &getwalletinfo}, + {"wallet", &importaddress}, + {"wallet", &importdescriptors}, + {"wallet", &importmulti}, + {"wallet", &importprivkey}, + {"wallet", &importprunedfunds}, + {"wallet", &importpubkey}, + {"wallet", &importwallet}, + {"wallet", &keypoolrefill}, + {"wallet", &listaddressgroupings}, + {"wallet", &listdescriptors}, + {"wallet", &listlabels}, + {"wallet", &listlockunspent}, + {"wallet", &listreceivedbyaddress}, + {"wallet", &listreceivedbylabel}, + {"wallet", &listsinceblock}, + {"wallet", &listtransactions}, + {"wallet", &listunspent}, + {"wallet", &listwalletdir}, + {"wallet", &listwallets}, + {"wallet", &loadwallet}, + {"wallet", &lockunspent}, + {"wallet", &newkeypool}, + {"wallet", &removeprunedfunds}, + {"wallet", &rescanblockchain}, + {"wallet", &send}, + {"wallet", &sendmany}, + {"wallet", &sendtoaddress}, + {"wallet", &sethdseed}, + {"wallet", &setlabel}, + {"wallet", &settxfee}, + {"wallet", &setwalletflag}, + {"wallet", &signmessage}, + {"wallet", &signrawtransactionwithwallet}, + {"wallet", &sendall}, + {"wallet", &unloadwallet}, + {"wallet", &upgradewallet}, + {"wallet", &walletcreatefundedpsbt}, #ifdef ENABLE_EXTERNAL_SIGNER - { "wallet", &walletdisplayaddress, }, + {"wallet", &walletdisplayaddress}, #endif // ENABLE_EXTERNAL_SIGNER - { "wallet", &walletlock, }, - { "wallet", &walletpassphrase, }, - { "wallet", &walletpassphrasechange, }, - { "wallet", &walletprocesspsbt, }, -}; -// clang-format on + {"wallet", &walletlock}, + {"wallet", &walletpassphrase}, + {"wallet", &walletpassphrasechange}, + {"wallet", &walletprocesspsbt}, + }; return commands; } } // namespace wallet diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 9e508f3a32..55c0a2cb7f 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -11,6 +11,7 @@ #include <util/fees.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <util/trace.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> @@ -435,9 +436,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec */ preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } - SelectionResult result(nTargetValue); + SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); result.AddInput(preset_inputs); if (result.GetSelectedValue() < nTargetValue) return std::nullopt; + result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change); return result; } @@ -519,7 +521,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // permissive CoinEligibilityFilter. std::optional<SelectionResult> res = [&] { // Pre-selected inputs already cover the target amount. - if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue)); + if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, SelectionAlgorithm::MANUAL)); // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six // confirmations on outputs received from other wallets and only spend confirmed change. @@ -573,6 +575,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // Add preset inputs to result res->AddInput(preset_inputs); + if (res->m_algo == SelectionAlgorithm::MANUAL) { + res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + } return res; } @@ -788,6 +793,7 @@ static bool CreateTransactionInternal( error = _("Insufficient funds"); return false; } + TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue()); // Always make a change output // We will reduce the fee from this change output later, and remove the output if it is too small. @@ -978,8 +984,10 @@ bool CreateTransaction( int nChangePosIn = nChangePosInOut; Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); + TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res, nFeeRet, nChangePosInOut); // try with avoidpartialspends unless it's enabled already if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str()); CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; CAmount nFeeRet2; @@ -990,6 +998,7 @@ bool CreateTransaction( // if fee of this alternative one is within the range of the max fee, we use this one const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee; wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); + TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, res, nFeeRet2, nChangePosInOut2); if (use_aps) { tx = tx2; nFeeRet = nFeeRet2; diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index 3f860289f9..2515df3177 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -405,7 +405,7 @@ bool SQLiteBatch::ReadKey(CDataStream&& key, CDataStream& value) return false; } // Leftmost column in result is index 0 - const std::byte* data{BytePtr(sqlite3_column_blob(m_read_stmt, 0))}; + const std::byte* data{AsBytePtr(sqlite3_column_blob(m_read_stmt, 0))}; size_t data_size(sqlite3_column_bytes(m_read_stmt, 0)); value.write({data, data_size}); @@ -497,10 +497,10 @@ bool SQLiteBatch::ReadAtCursor(CDataStream& key, CDataStream& value, bool& compl } // Leftmost column in result is index 0 - const std::byte* key_data{BytePtr(sqlite3_column_blob(m_cursor_stmt, 0))}; + const std::byte* key_data{AsBytePtr(sqlite3_column_blob(m_cursor_stmt, 0))}; size_t key_data_size(sqlite3_column_bytes(m_cursor_stmt, 0)); key.write({key_data, key_data_size}); - const std::byte* value_data{BytePtr(sqlite3_column_blob(m_cursor_stmt, 1))}; + const std::byte* value_data{AsBytePtr(sqlite3_column_blob(m_cursor_stmt, 1))}; size_t value_data_size(sqlite3_column_bytes(m_cursor_stmt, 1)); value.write({value_data, value_data_size}); return true; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 2a08c8ab57..72e749477b 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -168,7 +168,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) FastRandomContext rand{}; // Setup std::vector<COutput> utxo_pool; - SelectionResult expected_result(CAmount(0)); + SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB); ///////////////////////// // Known Outcome tests // diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index fbf1e0efd3..f61808c549 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -15,22 +15,22 @@ namespace wallet { BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup) -static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, std::string& database_filename) +static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, fs::path& database_filename) { fs::path data_file = BDBDataFile(path); - database_filename = fs::PathToString(data_file.filename()); + database_filename = data_file.filename(); return GetBerkeleyEnv(data_file.parent_path(), false); } BOOST_AUTO_TEST_CASE(getwalletenv_file) { - std::string test_name = "test_name.dat"; + fs::path test_name = "test_name.dat"; const fs::path datadir = m_args.GetDataDirNet(); fs::path file_path = datadir / test_name; std::ofstream f{file_path}; f.close(); - std::string filename; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename); BOOST_CHECK_EQUAL(filename, test_name); BOOST_CHECK_EQUAL(env->Directory(), datadir); @@ -38,10 +38,10 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file) BOOST_AUTO_TEST_CASE(getwalletenv_directory) { - std::string expected_name = "wallet.dat"; + fs::path expected_name = "wallet.dat"; const fs::path datadir = m_args.GetDataDirNet(); - std::string filename; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename); BOOST_CHECK_EQUAL(filename, expected_name); BOOST_CHECK_EQUAL(env->Directory(), datadir); @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple) { fs::path datadir = m_args.GetDataDirNet() / "1"; fs::path datadir_2 = m_args.GetDataDirNet() / "2"; - std::string filename; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename); std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename); @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance) { fs::path datadir = gArgs.GetDataDirNet() / "1"; fs::path datadir_2 = gArgs.GetDataDirNet() / "2"; - std::string filename; + fs::path filename; std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename); std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename); diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index 1c16da25bd..9089c8ff46 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -64,7 +64,7 @@ struct FuzzedWallet { assert(RemoveWallet(context, wallet, load_on_start, warnings)); assert(warnings.empty()); UnloadWallet(std::move(wallet)); - fs::remove_all(GetWalletDir() / name); + fs::remove_all(GetWalletDir() / fs::PathFromString(name)); } CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index 34c22a9c0d..8eb7689c94 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -17,8 +17,7 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam { m_wallet_loader = MakeWalletLoader(*m_node.chain, m_args); - std::string sep; - sep += fs::path::preferred_separator; + const auto sep = fs::path::preferred_separator; m_datadir = m_args.GetDataDirNet(); m_cwd = fs::current_path(); @@ -27,8 +26,8 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam m_walletdir_path_cases["custom"] = m_datadir / "my_wallets"; m_walletdir_path_cases["nonexistent"] = m_datadir / "path_does_not_exist"; m_walletdir_path_cases["file"] = m_datadir / "not_a_directory.dat"; - m_walletdir_path_cases["trailing"] = m_datadir / ("wallets" + sep); - m_walletdir_path_cases["trailing2"] = m_datadir / ("wallets" + sep + sep); + m_walletdir_path_cases["trailing"] = (m_datadir / "wallets") + sep; + m_walletdir_path_cases["trailing2"] = (m_datadir / "wallets") + sep + sep; fs::current_path(m_datadir); m_walletdir_path_cases["relative"] = "wallets"; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0d7075810d..5a23f739d3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -44,8 +44,6 @@ #include <assert.h> #include <optional> -#include <boost/algorithm/string/replace.hpp> - using interfaces::FoundBlock; namespace wallet { @@ -523,6 +521,11 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, void CWallet::chainStateFlushed(const CBlockLocator& loc) { + // Don't update the best block until the chain is attached so that in case of a shutdown, + // the rescan will be restarted at next startup. + if (m_attaching_chain) { + return; + } WalletBatch batch(GetDatabase()); batch.WriteBestBlock(loc); } @@ -1013,14 +1016,14 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const if (!strCmd.empty()) { - boost::replace_all(strCmd, "%s", hash.GetHex()); + ReplaceAll(strCmd, "%s", hash.GetHex()); if (auto* conf = wtx.state<TxStateConfirmed>()) { - boost::replace_all(strCmd, "%b", conf->confirmed_block_hash.GetHex()); - boost::replace_all(strCmd, "%h", ToString(conf->confirmed_block_height)); + ReplaceAll(strCmd, "%b", conf->confirmed_block_hash.GetHex()); + ReplaceAll(strCmd, "%h", ToString(conf->confirmed_block_height)); } else { - boost::replace_all(strCmd, "%b", "unconfirmed"); - boost::replace_all(strCmd, "%h", "-1"); + ReplaceAll(strCmd, "%b", "unconfirmed"); + ReplaceAll(strCmd, "%h", "-1"); } #ifndef WIN32 // Substituting the wallet name isn't currently supported on windows @@ -1028,7 +1031,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875 // A few ways it could be implemented in the future are described in: // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094 - boost::replace_all(strCmd, "%w", ShellEscape(GetName())); + ReplaceAll(strCmd, "%w", ShellEscape(GetName())); #endif std::thread t(runCommand, strCmd); t.detach(); // thread runs free @@ -1704,8 +1707,10 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r */ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate) { - int64_t nNow = GetTime(); - int64_t start_time = GetTimeMillis(); + using Clock = std::chrono::steady_clock; + constexpr auto LOG_INTERVAL{60s}; + auto current_time{Clock::now()}; + auto start_time{Clock::now()}; assert(reserver.isReserved()); @@ -1732,8 +1737,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } - if (GetTime() >= nNow + 60) { - nNow = GetTime(); + if (Clock::now() >= current_time + LOG_INTERVAL) { + current_time = Clock::now(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } @@ -1800,7 +1805,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current); result.status = ScanResult::USER_ABORT; } else { - WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); + auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time); + WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count()); } return result; } @@ -2768,7 +2774,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile); - return NULL; + return nullptr; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (spk_man->HavePrivateKeys()) { @@ -2934,12 +2940,28 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf assert(!walletInstance->m_chain || walletInstance->m_chain == &chain); walletInstance->m_chain = &chain; + // Unless allowed, ensure wallet files are not reused across chains: + if (!gArgs.GetBoolArg("-walletcrosschain", DEFAULT_WALLETCROSSCHAIN)) { + WalletBatch batch(walletInstance->GetDatabase()); + CBlockLocator locator; + if (batch.ReadBestBlock(locator) && locator.vHave.size() > 0 && chain.getHeight()) { + // Wallet is assumed to be from another chain, if genesis block in the active + // chain differs from the genesis block known to the wallet. + if (chain.getBlockHash(0) != locator.vHave.back()) { + error = Untranslated("Wallet files should not be reused across chains. Restart bitcoind with -walletcrosschain to override."); + return false; + } + } + } + // Register wallet with validationinterface. It's done before rescan to avoid // missing block connections between end of rescan and validation subscribing. // Because of wallet lock being hold, block connection notifications are going to // be pending on the validation-side until lock release. It's likely to have // block processing duplicata (if rescan block range overlaps with notification one) // but we guarantee at least than wallet state is correct after notifications delivery. + // However, chainStateFlushed notifications are ignored until the rescan is finished + // so that in case of a shutdown event, the rescan will be repeated at the next start. // This is temporary until rescan and notifications delivery are unified under same // interface. walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); @@ -2968,6 +2990,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf if (tip_height && *tip_height != rescan_height) { + walletInstance->m_attaching_chain = true; //ignores chainStateFlushed notifications if (chain.havePruned()) { int block_height = *tip_height; while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { @@ -3007,6 +3030,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf return false; } } + walletInstance->m_attaching_chain = false; walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->GetDatabase().IncrementUpdateCounter(); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 26b7f97b5f..4e81a2b957 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -102,6 +102,7 @@ static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; static const bool DEFAULT_WALLET_RBF = false; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; +static const bool DEFAULT_WALLETCROSSCHAIN = false; //! -maxtxfee default constexpr CAmount DEFAULT_TRANSACTION_MAXFEE{COIN / 10}; //! Discourage users to set fees higher than this amount (in satoshis) per kB @@ -237,6 +238,7 @@ private: std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver + std::atomic<bool> m_attaching_chain{false}; std::atomic<int64_t> m_scanning_start{0}; std::atomic<double> m_scanning_progress{0}; friend class WalletRescanReserver; diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index f9f8b5a9dc..ec6d1cbba3 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -51,10 +51,8 @@ static RPCHelpMan getzmqnotifications() }; } -const CRPCCommand commands[] = -{ // category actor (function) - // ----------------- ----------------------- - { "zmq", &getzmqnotifications, }, +const CRPCCommand commands[]{ + {"zmq", &getzmqnotifications}, }; } // anonymous namespace |