diff options
Diffstat (limited to 'src')
248 files changed, 4877 insertions, 2287 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index bf26cc9674..6d10f86d57 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ noinst_PROGRAMS = TESTS = BENCHMARKS = -BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) +BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(LEVELDB_CPPFLAGS) LIBBITCOIN_NODE=libbitcoin_node.a LIBBITCOIN_COMMON=libbitcoin_common.a @@ -133,6 +133,7 @@ BITCOIN_CORE_H = \ clientversion.h \ coins.h \ common/bloom.h \ + common/run_command.h \ compat/assumptions.h \ compat/byteswap.h \ compat/compat.h \ @@ -197,6 +198,7 @@ BITCOIN_CORE_H = \ node/blockstorage.h \ node/caches.h \ node/chainstate.h \ + node/chainstatemanager_args.h \ node/coin.h \ node/connection_types.h \ node/context.h \ @@ -208,6 +210,7 @@ BITCOIN_CORE_H = \ node/minisketchwrapper.h \ node/psbt.h \ node/transaction.h \ + node/txreconciliation.h \ node/utxo_snapshot.h \ node/validation_cache_args.h \ noui.h \ @@ -344,12 +347,8 @@ obj/build.h: FORCE "$(abs_top_srcdir)" libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h - -# server: shared between bitcoind and bitcoin-qt -# Contains code accessing mempool and chain state that is meant to be separated -# from wallet and gui code (see node/README.md). Shared code should go in -# libbitcoin_common or libbitcoin_util libraries, instead. -libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +# node # +libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_node_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_node_a_SOURCES = \ addrdb.cpp \ @@ -383,6 +382,7 @@ libbitcoin_node_a_SOURCES = \ node/blockstorage.cpp \ node/caches.cpp \ node/chainstate.cpp \ + node/chainstatemanager_args.cpp \ node/coin.cpp \ node/connection_types.cpp \ node/context.cpp \ @@ -395,6 +395,8 @@ libbitcoin_node_a_SOURCES = \ node/minisketchwrapper.cpp \ node/psbt.cpp \ node/transaction.cpp \ + node/txreconciliation.cpp \ + node/utxo_snapshot.cpp \ node/validation_cache_args.cpp \ noui.cpp \ policy/fees.cpp \ @@ -437,7 +439,9 @@ endif if !ENABLE_WALLET libbitcoin_node_a_SOURCES += dummywallet.cpp endif +# +# zmq # if ENABLE_ZMQ libbitcoin_zmq_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) @@ -448,11 +452,10 @@ libbitcoin_zmq_a_SOURCES = \ zmq/zmqrpc.cpp \ zmq/zmqutil.cpp endif +# - -# wallet: shared between bitcoind and bitcoin-qt, but only linked -# when wallet enabled -libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) +# wallet # +libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ wallet/coincontrol.cpp \ @@ -490,14 +493,17 @@ endif if USE_BDB libbitcoin_wallet_a_SOURCES += wallet/bdb.cpp wallet/salvage.cpp endif +# -libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +# wallet tool # +libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_wallet_tool_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_tool_a_SOURCES = \ wallet/wallettool.cpp \ $(BITCOIN_CORE_H) +# -# crypto primitives library +# crypto # crypto_libbitcoin_crypto_base_la_CPPFLAGS = $(AM_CPPFLAGS) # Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a @@ -577,8 +583,9 @@ 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. +# consensus # libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_consensus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_consensus_a_SOURCES = \ @@ -614,9 +621,10 @@ libbitcoin_consensus_a_SOURCES = \ util/strencodings.cpp \ util/strencodings.h \ version.h +# -# common: shared between bitcoind, and bitcoin-qt and non-server tools -libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +# common # +libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ base58.cpp \ @@ -624,6 +632,7 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ common/bloom.cpp \ + common/run_command.cpp \ compressor.cpp \ core_read.cpp \ core_write.cpp \ @@ -653,9 +662,10 @@ libbitcoin_common_a_SOURCES = \ script/standard.cpp \ warnings.cpp \ $(BITCOIN_CORE_H) +# -# util: shared between all executables. -libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +# util # +libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ @@ -702,8 +712,9 @@ libbitcoin_util_a_SOURCES = \ if USE_LIBEVENT libbitcoin_util_a_SOURCES += util/url.cpp endif +# -# cli: shared between bitcoin-cli and bitcoin-qt +# cli # libbitcoin_cli_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_cli_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_cli_a_SOURCES = \ @@ -833,7 +844,7 @@ bitcoin_util_LDADD = \ # bitcoin-chainstate binary # bitcoin_chainstate_SOURCES = bitcoin-chainstate.cpp -bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) # $(LIBTOOL_APP_LDFLAGS) deliberately omitted here so that we can test linking @@ -900,6 +911,7 @@ libbitcoinkernel_la_SOURCES = \ node/blockstorage.cpp \ node/chainstate.cpp \ node/interface_ui.cpp \ + node/utxo_snapshot.cpp \ policy/feerate.cpp \ policy/fees.cpp \ policy/packages.cpp \ @@ -927,7 +939,6 @@ libbitcoinkernel_la_SOURCES = \ txdb.cpp \ txmempool.cpp \ uint256.cpp \ - util/bytevectorhash.cpp \ util/check.cpp \ util/getuniquepath.cpp \ util/hasher.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index a23d872250..e1e2066877 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -51,8 +51,9 @@ bench_bench_bitcoin_SOURCES = \ 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_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ bench_bench_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) bench_bench_bitcoin_LDADD = \ $(LIBTEST_UTIL) \ $(LIBBITCOIN_NODE) \ @@ -66,7 +67,9 @@ bench_bench_bitcoin_LDADD = \ $(LIBSECP256K1) \ $(LIBUNIVALUE) \ $(EVENT_PTHREADS_LIBS) \ - $(EVENT_LIBS) + $(EVENT_LIBS) \ + $(MINIUPNPC_LIBS) \ + $(NATPMP_LIBS) if ENABLE_ZMQ bench_bench_bitcoin_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) @@ -76,11 +79,9 @@ if ENABLE_WALLET bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp +bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS) endif -bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) -bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) - CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES) CLEANFILES += $(CLEAN_BITCOIN_BENCH) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index b4acc47aa1..602a118259 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -295,7 +295,7 @@ BITCOIN_QT_RC = qt/res/bitcoin-qt-res.rc BITCOIN_QT_INCLUDES = -DQT_NO_KEYWORDS -DQT_USE_QSTRINGBUILDER qt_libbitcoinqt_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ - $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) + $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) $(BOOST_CPPFLAGS) qt_libbitcoinqt_a_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS) diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index fa822f2954..89c659d4b9 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -27,7 +27,7 @@ TEST_QT_H = \ qt/test/wallettests.h qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ - $(QT_INCLUDES) $(QT_TEST_INCLUDES) + $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(BOOST_CPPFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ init/bitcoin-qt.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8a2386a2b4..571a85e5c9 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -77,6 +77,7 @@ BITCOIN_TESTS =\ test/blockencodings_tests.cpp \ test/blockfilter_index_tests.cpp \ test/blockfilter_tests.cpp \ + test/blockmanager_tests.cpp \ test/bloom_tests.cpp \ test/bswap_tests.cpp \ test/checkqueue_tests.cpp \ @@ -147,6 +148,7 @@ BITCOIN_TESTS =\ test/transaction_tests.cpp \ test/txindex_tests.cpp \ test/txpackage_tests.cpp \ + test/txreconciliation_tests.cpp \ test/txrequest_tests.cpp \ test/txvalidation_tests.cpp \ test/txvalidationcache_tests.cpp \ @@ -174,7 +176,9 @@ BITCOIN_TESTS += \ wallet/test/availablecoins_tests.cpp \ wallet/test/init_tests.cpp \ wallet/test/ismine_tests.cpp \ - wallet/test/scriptpubkeyman_tests.cpp + wallet/test/rpc_util_tests.cpp \ + wallet/test/scriptpubkeyman_tests.cpp \ + wallet/test/walletload_tests.cpp FUZZ_SUITE_LD_COMMON +=\ $(SQLITE_LIBS) \ @@ -185,7 +189,8 @@ BITCOIN_TESTS += wallet/test/db_tests.cpp endif FUZZ_WALLET_SRC = \ - wallet/test/fuzz/coinselection.cpp + wallet/test/fuzz/coinselection.cpp \ + wallet/test/fuzz/parse_iso8601.cpp if USE_SQLITE FUZZ_WALLET_SRC += \ @@ -202,7 +207,7 @@ BITCOIN_TEST_SUITE += \ endif # ENABLE_WALLET test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) -test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EVENT_CFLAGS) +test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) test_test_bitcoin_LDADD = $(LIBTEST_UTIL) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) @@ -222,7 +227,7 @@ FUZZ_SUITE_LD_COMMON += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif if ENABLE_FUZZ_BINARY -test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) test_fuzz_fuzz_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_fuzz_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) $(RUNTIME_LDFLAGS) @@ -287,7 +292,6 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_serialization.cpp \ test/fuzz/parse_hd_keypath.cpp \ - test/fuzz/parse_iso8601.cpp \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.cpp \ test/fuzz/parse_univalue.cpp \ @@ -372,8 +376,8 @@ endif if TARGET_WINDOWS else if ENABLE_BENCH - @echo "Running bench/bench_bitcoin (one iteration sanity check)..." - $(BENCH_BINARY) --sanity-check > /dev/null + @echo "Running bench/bench_bitcoin (one iteration sanity check, only high priority)..." + $(BENCH_BINARY) -sanity-check -priority-level=high > /dev/null endif endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index b43816636f..b35d713d57 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -10,12 +10,13 @@ EXTRA_LIBRARIES += \ TEST_FUZZ_H = \ test/fuzz/fuzz.h \ test/fuzz/FuzzedDataProvider.h \ - test/fuzz/mempool_utils.h \ - test/fuzz/util.h + test/fuzz/util.h \ + test/fuzz/util/mempool.h -libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/fuzz/util.cpp \ + test/fuzz/util/mempool.cpp \ $(TEST_FUZZ_H) diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 9306bb6fcc..d142572b27 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -5,22 +5,23 @@ LIBTEST_UTIL=libtest_util.a EXTRA_LIBRARIES += \ - $(LIBTEST_UTIL) + $(LIBTEST_UTIL) TEST_UTIL_H = \ - test/util/blockfilter.h \ - test/util/chainstate.h \ - test/util/logging.h \ - test/util/mining.h \ - test/util/net.h \ - test/util/script.h \ - test/util/setup_common.h \ - test/util/str.h \ - test/util/transaction_utils.h \ - test/util/validation.h \ - test/util/wallet.h + test/util/blockfilter.h \ + test/util/chainstate.h \ + test/util/logging.h \ + test/util/mining.h \ + test/util/net.h \ + test/util/script.h \ + test/util/setup_common.h \ + test/util/str.h \ + test/util/transaction_utils.h \ + test/util/txmempool.h \ + test/util/validation.h \ + test/util/wallet.h -libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_util_a_SOURCES = \ test/util/blockfilter.cpp \ @@ -31,6 +32,7 @@ libtest_util_a_SOURCES = \ test/util/setup_common.cpp \ test/util/str.cpp \ test/util/transaction_utils.cpp \ + test/util/txmempool.cpp \ test/util/validation.cpp \ test/util/wallet.cpp \ $(TEST_UTIL_H) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 31f8eadf98..7106d819b0 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -187,11 +187,11 @@ std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, con auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000); addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; const auto path_addr{args.GetDataDirNet() / "peers.dat"}; try { DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION); - LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart); + LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } catch (const DbNotFoundError&) { // Addrman can be in an inconsistent state after failure, reset it addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); diff --git a/src/banman.cpp b/src/banman.cpp index 508383d9f2..3cd646c148 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -31,12 +31,12 @@ void BanMan::LoadBanlist() if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); - int64_t n_start = GetTimeMillis(); + const auto start{SteadyClock::now()}; if (m_ban_db.Read(m_banned)) { SweepBanned(); // sweep out unused entries LogPrint(BCLog::NET, "Loaded %d banned node addresses/subnets %dms\n", m_banned.size(), - GetTimeMillis() - n_start); + Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } else { LogPrintf("Recreating the banlist database\n"); m_banned = {}; @@ -58,13 +58,13 @@ void BanMan::DumpBanlist() SetBannedSetDirty(false); } - int64_t n_start = GetTimeMillis(); + const auto start{SteadyClock::now()}; if (!m_ban_db.Write(banmap)) { SetBannedSetDirty(true); } LogPrint(BCLog::NET, "Flushed %d banned node addresses/subnets to disk %dms\n", banmap.size(), - GetTimeMillis() - n_start); + Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } void BanMan::ClearBanned() diff --git a/src/bech32.cpp b/src/bech32.cpp index dce9b2e4cc..8e0025b8f4 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -241,7 +241,7 @@ constexpr std::array<uint32_t, 25> GenerateSyndromeConstants() { std::array<uint32_t, 25> SYNDROME_CONSTS{}; for (int k = 1; k < 6; ++k) { for (int shift = 0; shift < 5; ++shift) { - int16_t b = GF1024_LOG.at(1 << shift); + int16_t b = GF1024_LOG.at(size_t{1} << shift); int16_t c0 = GF1024_EXP.at((997*k + b) % 1023); int16_t c1 = GF1024_EXP.at((998*k + b) % 1023); int16_t c2 = GF1024_EXP.at((999*k + b) % 1023); diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp index f14d1f89b6..019b133345 100644 --- a/src/bench/addrman.cpp +++ b/src/bench/addrman.cpp @@ -133,7 +133,7 @@ static void AddrManAddThenGood(benchmark::Bench& bench) }); } -BENCHMARK(AddrManAdd); -BENCHMARK(AddrManSelect); -BENCHMARK(AddrManGetAddr); -BENCHMARK(AddrManAddThenGood); +BENCHMARK(AddrManAdd, benchmark::PriorityLevel::HIGH); +BENCHMARK(AddrManSelect, benchmark::PriorityLevel::HIGH); +BENCHMARK(AddrManGetAddr, benchmark::PriorityLevel::HIGH); +BENCHMARK(AddrManAddThenGood, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/base58.cpp b/src/bench/base58.cpp index 6f6b4e3bfa..3d08b7201b 100644 --- a/src/bench/base58.cpp +++ b/src/bench/base58.cpp @@ -50,6 +50,6 @@ static void Base58Decode(benchmark::Bench& bench) } -BENCHMARK(Base58Encode); -BENCHMARK(Base58CheckEncode); -BENCHMARK(Base58Decode); +BENCHMARK(Base58Encode, benchmark::PriorityLevel::HIGH); +BENCHMARK(Base58CheckEncode, benchmark::PriorityLevel::HIGH); +BENCHMARK(Base58Decode, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp index 0f89a8e2b5..1a166e7081 100644 --- a/src/bench/bech32.cpp +++ b/src/bench/bech32.cpp @@ -32,5 +32,5 @@ static void Bech32Decode(benchmark::Bench& bench) } -BENCHMARK(Bech32Encode); -BENCHMARK(Bech32Decode); +BENCHMARK(Bech32Encode, benchmark::PriorityLevel::HIGH); +BENCHMARK(Bech32Decode, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp index 26975bb59d..1a3a006286 100644 --- a/src/bench/bench.cpp +++ b/src/bench/bench.cpp @@ -6,6 +6,7 @@ #include <fs.h> #include <test/util/setup_common.h> +#include <util/string.h> #include <chrono> #include <fstream> @@ -41,18 +42,42 @@ void GenerateTemplateResults(const std::vector<ankerl::nanobench::Result>& bench } // namespace -benchmark::BenchRunner::BenchmarkMap& benchmark::BenchRunner::benchmarks() +namespace benchmark { + +// map a label to one or multiple priority levels +std::map<std::string, uint8_t> map_label_priority = { + {"high", PriorityLevel::HIGH}, + {"low", PriorityLevel::LOW}, + {"all", 0xff} +}; + +std::string ListPriorities() { - static std::map<std::string, BenchFunction> benchmarks_map; + using item_t = std::pair<std::string, uint8_t>; + auto sort_by_priority = [](item_t a, item_t b){ return a.second < b.second; }; + std::set<item_t, decltype(sort_by_priority)> sorted_priorities(map_label_priority.begin(), map_label_priority.end(), sort_by_priority); + return Join(sorted_priorities, ',', [](const auto& entry){ return entry.first; }); +} + +uint8_t StringToPriority(const std::string& str) +{ + auto it = map_label_priority.find(str); + if (it == map_label_priority.end()) throw std::runtime_error(strprintf("Unknown priority level %s", str)); + return it->second; +} + +BenchRunner::BenchmarkMap& BenchRunner::benchmarks() +{ + static BenchmarkMap benchmarks_map; return benchmarks_map; } -benchmark::BenchRunner::BenchRunner(std::string name, benchmark::BenchFunction func) +BenchRunner::BenchRunner(std::string name, BenchFunction func, PriorityLevel level) { - benchmarks().insert(std::make_pair(name, func)); + benchmarks().insert(std::make_pair(name, std::make_pair(func, level))); } -void benchmark::BenchRunner::RunAll(const Args& args) +void BenchRunner::RunAll(const Args& args) { std::regex reFilter(args.regex_filter); std::smatch baseMatch; @@ -62,13 +87,19 @@ void benchmark::BenchRunner::RunAll(const Args& args) } std::vector<ankerl::nanobench::Result> benchmarkResults; - for (const auto& p : benchmarks()) { - if (!std::regex_match(p.first, baseMatch, reFilter)) { + for (const auto& [name, bench_func] : benchmarks()) { + const auto& [func, priority_level] = bench_func; + + if (!(priority_level & args.priority)) { + continue; + } + + if (!std::regex_match(name, baseMatch, reFilter)) { continue; } if (args.is_list_only) { - std::cout << p.first << std::endl; + std::cout << name << std::endl; continue; } @@ -76,7 +107,7 @@ void benchmark::BenchRunner::RunAll(const Args& args) if (args.sanity_check) { bench.epochs(1).epochIterations(1); } - bench.name(p.first); + bench.name(name); if (args.min_time > 0ms) { // convert to nanos before dividing to reduce rounding errors std::chrono::nanoseconds min_time_ns = args.min_time; @@ -84,11 +115,11 @@ void benchmark::BenchRunner::RunAll(const Args& args) } if (args.asymptote.empty()) { - p.second(bench); + func(bench); } else { for (auto n : args.asymptote) { bench.complexityN(n); - p.second(bench); + func(bench); } std::cout << bench.complexityBigO() << std::endl; } @@ -103,3 +134,5 @@ void benchmark::BenchRunner::RunAll(const Args& args) "{{/result}}"); GenerateTemplateResults(benchmarkResults, args.output_json, ankerl::nanobench::templates::json()); } + +} // namespace benchmark diff --git a/src/bench/bench.h b/src/bench/bench.h index 17535e4e81..63e1bf67e2 100644 --- a/src/bench/bench.h +++ b/src/bench/bench.h @@ -41,6 +41,16 @@ using ankerl::nanobench::Bench; typedef std::function<void(Bench&)> BenchFunction; +enum PriorityLevel : uint8_t +{ + LOW = 1 << 0, + HIGH = 1 << 2, +}; + +// List priority labels, comma-separated and sorted by increasing priority +std::string ListPriorities(); +uint8_t StringToPriority(const std::string& str); + struct Args { bool is_list_only; bool sanity_check; @@ -49,22 +59,24 @@ struct Args { fs::path output_csv; fs::path output_json; std::string regex_filter; + uint8_t priority; }; class BenchRunner { - typedef std::map<std::string, BenchFunction> BenchmarkMap; + // maps from "name" -> (function, priority_level) + typedef std::map<std::string, std::pair<BenchFunction, PriorityLevel>> BenchmarkMap; static BenchmarkMap& benchmarks(); public: - BenchRunner(std::string name, BenchFunction func); + BenchRunner(std::string name, BenchFunction func, PriorityLevel level); static void RunAll(const Args& args); }; } // namespace benchmark -// BENCHMARK(foo) expands to: benchmark::BenchRunner bench_11foo("foo", foo); -#define BENCHMARK(n) \ - benchmark::BenchRunner PASTE2(bench_, PASTE2(__LINE__, n))(STRINGIZE(n), n); +// BENCHMARK(foo) expands to: benchmark::BenchRunner bench_11foo("foo", foo, priority_level); +#define BENCHMARK(n, priority_level) \ + benchmark::BenchRunner PASTE2(bench_, PASTE2(__LINE__, n))(STRINGIZE(n), n, priority_level); #endif // BITCOIN_BENCH_BENCH_H diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 1bb4d34db9..1ac8db19fd 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -18,6 +18,8 @@ static const char* DEFAULT_BENCH_FILTER = ".*"; static constexpr int64_t DEFAULT_MIN_TIME_MS{10}; +/** Priority level default value, run "all" priority levels */ +static const std::string DEFAULT_PRIORITY{"all"}; static void SetupBenchArgs(ArgsManager& argsman) { @@ -30,6 +32,8 @@ static void SetupBenchArgs(ArgsManager& argsman) argsman.AddArg("-output-csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-output-json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-sanity-check", "Run benchmarks for only one iteration", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-priority-level=<l1,l2,l3>", strprintf("Run benchmarks of one or multiple priority level(s) (%s), default: '%s'", + benchmark::ListPriorities(), DEFAULT_PRIORITY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } // parses a comma separated list like "10,20,30,50" @@ -45,6 +49,14 @@ static std::vector<double> parseAsymptote(const std::string& str) { return numbers; } +static uint8_t parsePriorityLevel(const std::string& str) { + uint8_t levels{0}; + for (const auto& level: SplitString(str, ',')) { + levels |= benchmark::StringToPriority(level); + } + return levels; +} + int main(int argc, char** argv) { ArgsManager argsman; @@ -106,16 +118,22 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - benchmark::Args args; - args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", "")); - args.is_list_only = argsman.GetBoolArg("-list", false); - args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min-time", DEFAULT_MIN_TIME_MS)); - args.output_csv = argsman.GetPathArg("-output-csv"); - args.output_json = argsman.GetPathArg("-output-json"); - args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER); - args.sanity_check = argsman.GetBoolArg("-sanity-check", false); + try { + benchmark::Args args; + args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", "")); + args.is_list_only = argsman.GetBoolArg("-list", false); + args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min-time", DEFAULT_MIN_TIME_MS)); + args.output_csv = argsman.GetPathArg("-output-csv"); + args.output_json = argsman.GetPathArg("-output-json"); + args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER); + args.sanity_check = argsman.GetBoolArg("-sanity-check", false); + args.priority = parsePriorityLevel(argsman.GetArg("-priority-level", DEFAULT_PRIORITY)); - benchmark::BenchRunner::RunAll(args); + benchmark::BenchRunner::RunAll(args); - return EXIT_SUCCESS; + return EXIT_SUCCESS; + } catch (const std::exception& e) { + tfm::format(std::cerr, "Error: %s\n", e.what()); + return EXIT_FAILURE; + } } diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 4ed5397330..09be011fda 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -47,4 +47,4 @@ static void AssembleBlock(benchmark::Bench& bench) }); } -BENCHMARK(AssembleBlock); +BENCHMARK(AssembleBlock, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 22dfb7aa5b..5d55ed9332 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -51,4 +51,4 @@ static void CCoinsCaching(benchmark::Bench& bench) ECC_Stop(); } -BENCHMARK(CCoinsCaching); +BENCHMARK(CCoinsCaching, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index a6f4eec4ca..9584dd58bb 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -39,6 +39,6 @@ static void CHACHA20_1MB(benchmark::Bench& bench) CHACHA20(bench, BUFFER_SIZE_LARGE); } -BENCHMARK(CHACHA20_64BYTES); -BENCHMARK(CHACHA20_256BYTES); -BENCHMARK(CHACHA20_1MB); +BENCHMARK(CHACHA20_64BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_256BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_1MB, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp index e994279a4d..15b3a4f310 100644 --- a/src/bench/chacha_poly_aead.cpp +++ b/src/bench/chacha_poly_aead.cpp @@ -115,12 +115,12 @@ static void HASH_1MB(benchmark::Bench& bench) HASH(bench, BUFFER_SIZE_LARGE); } -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT); -BENCHMARK(HASH_64BYTES); -BENCHMARK(HASH_256BYTES); -BENCHMARK(HASH_1MB); +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_64BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_256BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_1MB, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 53aa470042..747279c161 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -50,5 +50,5 @@ static void DeserializeAndCheckBlockTest(benchmark::Bench& bench) }); } -BENCHMARK(DeserializeBlockTest); -BENCHMARK(DeserializeAndCheckBlockTest); +BENCHMARK(DeserializeBlockTest, benchmark::PriorityLevel::HIGH); +BENCHMARK(DeserializeAndCheckBlockTest, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 602081fb9b..8517c9fee2 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -70,4 +70,4 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) queue.StopWorkerThreads(); ECC_Stop(); } -BENCHMARK(CCheckQueueSpeedPrevectorJob); +BENCHMARK(CCheckQueueSpeedPrevectorJob, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 6ada28115e..53d89039a7 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -121,5 +121,5 @@ static void BnBExhaustion(benchmark::Bench& bench) }); } -BENCHMARK(CoinSelection); -BENCHMARK(BnBExhaustion); +BENCHMARK(CoinSelection, benchmark::PriorityLevel::HIGH); +BENCHMARK(BnBExhaustion, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index d17ec503e7..162b02f344 100644 --- a/src/bench/crypto_hash.cpp +++ b/src/bench/crypto_hash.cpp @@ -150,19 +150,19 @@ static void MuHashPrecompute(benchmark::Bench& bench) }); } -BENCHMARK(RIPEMD160); -BENCHMARK(SHA1); -BENCHMARK(SHA256); -BENCHMARK(SHA512); -BENCHMARK(SHA3_256_1M); - -BENCHMARK(SHA256_32b); -BENCHMARK(SipHash_32b); -BENCHMARK(SHA256D64_1024); -BENCHMARK(FastRandom_32bit); -BENCHMARK(FastRandom_1bit); - -BENCHMARK(MuHash); -BENCHMARK(MuHashMul); -BENCHMARK(MuHashDiv); -BENCHMARK(MuHashPrecompute); +BENCHMARK(RIPEMD160, benchmark::PriorityLevel::HIGH); +BENCHMARK(SHA1, benchmark::PriorityLevel::HIGH); +BENCHMARK(SHA256, benchmark::PriorityLevel::HIGH); +BENCHMARK(SHA512, benchmark::PriorityLevel::HIGH); +BENCHMARK(SHA3_256_1M, benchmark::PriorityLevel::HIGH); + +BENCHMARK(SHA256_32b, benchmark::PriorityLevel::HIGH); +BENCHMARK(SipHash_32b, benchmark::PriorityLevel::HIGH); +BENCHMARK(SHA256D64_1024, benchmark::PriorityLevel::HIGH); +BENCHMARK(FastRandom_32bit, benchmark::PriorityLevel::HIGH); +BENCHMARK(FastRandom_1bit, benchmark::PriorityLevel::HIGH); + +BENCHMARK(MuHash, benchmark::PriorityLevel::HIGH); +BENCHMARK(MuHashMul, benchmark::PriorityLevel::HIGH); +BENCHMARK(MuHashDiv, benchmark::PriorityLevel::HIGH); +BENCHMARK(MuHashPrecompute, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/descriptors.cpp b/src/bench/descriptors.cpp index 5c868a8573..972a6ff953 100644 --- a/src/bench/descriptors.cpp +++ b/src/bench/descriptors.cpp @@ -4,6 +4,7 @@ #include <bench/bench.h> #include <key.h> +#include <pubkey.h> #include <script/descriptor.h> #include <script/standard.h> @@ -12,6 +13,9 @@ static void ExpandDescriptor(benchmark::Bench& bench) { + const ECCVerifyHandle verify_handle; + ECC_Start(); + const auto desc_str = "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))"; const std::pair<int64_t, int64_t> range = {0, 1000}; FlatSigningProvider provider; @@ -25,6 +29,8 @@ static void ExpandDescriptor(benchmark::Bench& bench) assert(success); } }); + + ECC_Stop(); } -BENCHMARK(ExpandDescriptor); +BENCHMARK(ExpandDescriptor, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 02a2e689b1..559854ff48 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -62,4 +62,4 @@ static void DuplicateInputs(benchmark::Bench& bench) }); } -BENCHMARK(DuplicateInputs); +BENCHMARK(DuplicateInputs, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/examples.cpp b/src/bench/examples.cpp index 72a9922e94..abef69cc42 100644 --- a/src/bench/examples.cpp +++ b/src/bench/examples.cpp @@ -18,4 +18,4 @@ static void Trig(benchmark::Bench& bench) }); } -BENCHMARK(Trig); +BENCHMARK(Trig, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/gcs_filter.cpp b/src/bench/gcs_filter.cpp index 80babb213b..b795ebff39 100644 --- a/src/bench/gcs_filter.cpp +++ b/src/bench/gcs_filter.cpp @@ -81,8 +81,8 @@ static void GCSFilterMatch(benchmark::Bench& bench) filter.Match(GCSFilter::Element()); }); } -BENCHMARK(GCSBlockFilterGetHash); -BENCHMARK(GCSFilterConstruct); -BENCHMARK(GCSFilterDecode); -BENCHMARK(GCSFilterDecodeSkipCheck); -BENCHMARK(GCSFilterMatch); +BENCHMARK(GCSBlockFilterGetHash, benchmark::PriorityLevel::HIGH); +BENCHMARK(GCSFilterConstruct, benchmark::PriorityLevel::HIGH); +BENCHMARK(GCSFilterDecode, benchmark::PriorityLevel::HIGH); +BENCHMARK(GCSFilterDecodeSkipCheck, benchmark::PriorityLevel::HIGH); +BENCHMARK(GCSFilterMatch, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/hashpadding.cpp b/src/bench/hashpadding.cpp index 753c8c2881..ac5aeebe51 100644 --- a/src/bench/hashpadding.cpp +++ b/src/bench/hashpadding.cpp @@ -26,7 +26,7 @@ static void PrePadded(benchmark::Bench& bench) }); } -BENCHMARK(PrePadded); +BENCHMARK(PrePadded, benchmark::PriorityLevel::HIGH); static void RegularPadded(benchmark::Bench& bench) { @@ -44,4 +44,4 @@ static void RegularPadded(benchmark::Bench& bench) }); } -BENCHMARK(RegularPadded); +BENCHMARK(RegularPadded, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/lockedpool.cpp b/src/bench/lockedpool.cpp index b6d8824aba..ac8262654c 100644 --- a/src/bench/lockedpool.cpp +++ b/src/bench/lockedpool.cpp @@ -39,4 +39,4 @@ static void BenchLockedPool(benchmark::Bench& bench) addr.clear(); } -BENCHMARK(BenchLockedPool); +BENCHMARK(BenchLockedPool, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/logging.cpp b/src/bench/logging.cpp index d28777df9e..49a9e59893 100644 --- a/src/bench/logging.cpp +++ b/src/bench/logging.cpp @@ -41,8 +41,8 @@ static void LoggingNoFile(benchmark::Bench& bench) }); } -BENCHMARK(LoggingYoThreadNames); -BENCHMARK(LoggingNoThreadNames); -BENCHMARK(LoggingYoCategory); -BENCHMARK(LoggingNoCategory); -BENCHMARK(LoggingNoFile); +BENCHMARK(LoggingYoThreadNames, benchmark::PriorityLevel::HIGH); +BENCHMARK(LoggingNoThreadNames, benchmark::PriorityLevel::HIGH); +BENCHMARK(LoggingYoCategory, benchmark::PriorityLevel::HIGH); +BENCHMARK(LoggingNoCategory, benchmark::PriorityLevel::HIGH); +BENCHMARK(LoggingNoFile, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index 60d991fab9..878e375a7c 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -132,4 +132,4 @@ static void MempoolEviction(benchmark::Bench& bench) }); } -BENCHMARK(MempoolEviction); +BENCHMARK(MempoolEviction, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index 725a6f8f5b..9f5b28dca7 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -114,5 +114,5 @@ static void MempoolCheck(benchmark::Bench& bench) }); } -BENCHMARK(ComplexMemPool); -BENCHMARK(MempoolCheck); +BENCHMARK(ComplexMemPool, benchmark::PriorityLevel::HIGH); +BENCHMARK(MempoolCheck, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/merkle_root.cpp b/src/bench/merkle_root.cpp index ba6629b9f0..4140d67bc7 100644 --- a/src/bench/merkle_root.cpp +++ b/src/bench/merkle_root.cpp @@ -23,4 +23,4 @@ static void MerkleRoot(benchmark::Bench& bench) }); } -BENCHMARK(MerkleRoot); +BENCHMARK(MerkleRoot, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/nanobench.h b/src/bench/nanobench.h index 27df08fb69..916a7d61ef 100644 --- a/src/bench/nanobench.h +++ b/src/bench/nanobench.h @@ -858,7 +858,7 @@ public: * @brief Retrieves all benchmark results collected by the bench object so far. * * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to - * see all the nitty gritty detials. + * see all the nitty gritty details. * * @return All results collected so far. */ diff --git a/src/bench/peer_eviction.cpp b/src/bench/peer_eviction.cpp index f05f5e8f64..c3e3670de7 100644 --- a/src/bench/peer_eviction.cpp +++ b/src/bench/peer_eviction.cpp @@ -141,15 +141,15 @@ static void EvictionProtection3Networks250Candidates(benchmark::Bench& bench) // - 250 candidates is the number of peers reported by operators of busy nodes // No disadvantaged networks, with 250 eviction candidates. -BENCHMARK(EvictionProtection0Networks250Candidates); +BENCHMARK(EvictionProtection0Networks250Candidates, benchmark::PriorityLevel::HIGH); // 1 disadvantaged network (Tor) with 250 eviction candidates. -BENCHMARK(EvictionProtection1Networks250Candidates); +BENCHMARK(EvictionProtection1Networks250Candidates, benchmark::PriorityLevel::HIGH); // 2 disadvantaged networks (I2P, Tor) with 250 eviction candidates. -BENCHMARK(EvictionProtection2Networks250Candidates); +BENCHMARK(EvictionProtection2Networks250Candidates, benchmark::PriorityLevel::HIGH); // 3 disadvantaged networks (I2P/localhost/Tor) with 50/100/250 eviction candidates. -BENCHMARK(EvictionProtection3Networks050Candidates); -BENCHMARK(EvictionProtection3Networks100Candidates); -BENCHMARK(EvictionProtection3Networks250Candidates); +BENCHMARK(EvictionProtection3Networks050Candidates, benchmark::PriorityLevel::HIGH); +BENCHMARK(EvictionProtection3Networks100Candidates, benchmark::PriorityLevel::HIGH); +BENCHMARK(EvictionProtection3Networks250Candidates, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/poly1305.cpp b/src/bench/poly1305.cpp index cdef97c0ea..ad5a72ffde 100644 --- a/src/bench/poly1305.cpp +++ b/src/bench/poly1305.cpp @@ -36,6 +36,6 @@ static void POLY1305_1MB(benchmark::Bench& bench) POLY1305(bench, BUFFER_SIZE_LARGE); } -BENCHMARK(POLY1305_64BYTES); -BENCHMARK(POLY1305_256BYTES); -BENCHMARK(POLY1305_1MB); +BENCHMARK(POLY1305_64BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(POLY1305_256BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(POLY1305_1MB, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index b3688bab1b..9e2e7d11c4 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -85,12 +85,12 @@ static void PrevectorDeserialize(benchmark::Bench& bench) { \ Prevector##name<nontrivial_t>(bench); \ } \ - BENCHMARK(Prevector##name##Nontrivial); \ + BENCHMARK(Prevector##name##Nontrivial, benchmark::PriorityLevel::HIGH); \ static void Prevector##name##Trivial(benchmark::Bench& bench) \ { \ Prevector##name<trivial_t>(bench); \ } \ - BENCHMARK(Prevector##name##Trivial); + BENCHMARK(Prevector##name##Trivial, benchmark::PriorityLevel::HIGH); PREVECTOR_TEST(Clear) PREVECTOR_TEST(Destructor) diff --git a/src/bench/rollingbloom.cpp b/src/bench/rollingbloom.cpp index 8f05e3bad0..865d99f9e8 100644 --- a/src/bench/rollingbloom.cpp +++ b/src/bench/rollingbloom.cpp @@ -32,5 +32,5 @@ static void RollingBloomReset(benchmark::Bench& bench) }); } -BENCHMARK(RollingBloom); -BENCHMARK(RollingBloomReset); +BENCHMARK(RollingBloom, benchmark::PriorityLevel::HIGH); +BENCHMARK(RollingBloomReset, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index e6fc8d21f4..5a178f308a 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -45,7 +45,7 @@ static void BlockToJsonVerbose(benchmark::Bench& bench) }); } -BENCHMARK(BlockToJsonVerbose); +BENCHMARK(BlockToJsonVerbose, benchmark::PriorityLevel::HIGH); static void BlockToJsonVerboseWrite(benchmark::Bench& bench) { @@ -57,4 +57,4 @@ static void BlockToJsonVerboseWrite(benchmark::Bench& bench) }); } -BENCHMARK(BlockToJsonVerboseWrite); +BENCHMARK(BlockToJsonVerboseWrite, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 0e6fdae3d7..4fdc31ae05 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -40,4 +40,4 @@ static void RpcMempool(benchmark::Bench& bench) }); } -BENCHMARK(RpcMempool); +BENCHMARK(RpcMempool, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/strencodings.cpp b/src/bench/strencodings.cpp index 69b3a83cbf..16d14278a7 100644 --- a/src/bench/strencodings.cpp +++ b/src/bench/strencodings.cpp @@ -15,4 +15,4 @@ static void HexStrBench(benchmark::Bench& bench) }); } -BENCHMARK(HexStrBench); +BENCHMARK(HexStrBench, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp index afc733482e..8256b5f7ba 100644 --- a/src/bench/util_time.cpp +++ b/src/bench/util_time.cpp @@ -36,7 +36,7 @@ static void BenchTimeMillisSys(benchmark::Bench& bench) }); } -BENCHMARK(BenchTimeDeprecated); -BENCHMARK(BenchTimeMillis); -BENCHMARK(BenchTimeMillisSys); -BENCHMARK(BenchTimeMock); +BENCHMARK(BenchTimeDeprecated, benchmark::PriorityLevel::HIGH); +BENCHMARK(BenchTimeMillis, benchmark::PriorityLevel::HIGH); +BENCHMARK(BenchTimeMillisSys, benchmark::PriorityLevel::HIGH); +BENCHMARK(BenchTimeMock, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index 8e4708f260..f0e9db8ba1 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -96,5 +96,5 @@ static void VerifyNestedIfScript(benchmark::Bench& bench) }); } -BENCHMARK(VerifyScriptBench); -BENCHMARK(VerifyNestedIfScript); +BENCHMARK(VerifyScriptBench, benchmark::PriorityLevel::HIGH); +BENCHMARK(VerifyNestedIfScript, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index a5dd2f3bb1..22d99c0e29 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -57,7 +57,7 @@ static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, / static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); } static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/false); } -BENCHMARK(WalletBalanceDirty); -BENCHMARK(WalletBalanceClean); -BENCHMARK(WalletBalanceMine); -BENCHMARK(WalletBalanceWatch); +BENCHMARK(WalletBalanceDirty, benchmark::PriorityLevel::HIGH); +BENCHMARK(WalletBalanceClean, benchmark::PriorityLevel::HIGH); +BENCHMARK(WalletBalanceMine, benchmark::PriorityLevel::HIGH); +BENCHMARK(WalletBalanceWatch, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp index 27e4dd015d..8bfaf3044b 100644 --- a/src/bench/wallet_loading.cpp +++ b/src/bench/wallet_loading.cpp @@ -118,10 +118,10 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) #ifdef USE_BDB static void WalletLoadingLegacy(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/true); } -BENCHMARK(WalletLoadingLegacy); +BENCHMARK(WalletLoadingLegacy, benchmark::PriorityLevel::HIGH); #endif #ifdef USE_SQLITE static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); } -BENCHMARK(WalletLoadingDescriptors); +BENCHMARK(WalletLoadingDescriptors, benchmark::PriorityLevel::HIGH); #endif diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index f3bd543de8..d972b71a65 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -104,7 +104,7 @@ int main(int argc, char* argv[]) } } - for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { + for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(state, nullptr)) { std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl; @@ -253,7 +253,7 @@ epilogue: GetMainSignals().FlushBackgroundCallbacks(); { LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); chainstate->ResetCoinsViews(); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index e64e2202ba..6d77385584 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -642,7 +642,7 @@ public: " send Time since last message sent to the peer, in seconds\n" " recv Time since last message received from the peer, in seconds\n" " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" - " \"*\" - the peer requested we not relay transactions to it (relaytxes is false)\n" + " \"*\" - whether we relay transactions to this peer (relaytxes is false)\n" " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" " hb High-bandwidth BIP152 compact block relay\n" " \".\" (to) - we selected the peer as a high-bandwidth peer\n" diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index c2bb89b8cf..010cac5920 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -15,7 +15,6 @@ #include <key_io.h> #include <fs.h> #include <policy/policy.h> -#include <policy/rbf.h> #include <primitives/transaction.h> #include <script/script.h> #include <script/sign.h> @@ -28,9 +27,9 @@ #include <util/system.h> #include <util/translation.h> +#include <cstdio> #include <functional> #include <memory> -#include <stdio.h> static bool fCreateBlank; static std::map<std::string,UniValue> registers; diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index a7d49452b0..d556300ee2 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -50,15 +50,16 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddCommand("createfromdump", "Create new wallet file from dumped records"); } -static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) +static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[]) { SetupWalletToolArgs(args); std::string error_message; if (!args.ParseParameters(argc, argv, error_message)) { tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message); - return false; + return EXIT_FAILURE; } - if (argc < 2 || HelpRequested(args) || args.IsArgSet("-version")) { + const bool missing_args{argc < 2}; + if (missing_args || HelpRequested(args) || args.IsArgSet("-version")) { std::string strUsage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n"; if (args.IsArgSet("-version")) { @@ -73,7 +74,11 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) strUsage += "\n" + args.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage); - return false; + if (missing_args) { + tfm::format(std::cerr, "Error: too few parameters\n"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } // check for printtoconsole, allow -debug @@ -81,12 +86,12 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) if (!CheckDataDirOption()) { tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", "")); - return false; + return EXIT_FAILURE; } // Check for chain settings (Params() calls are only valid after this clause) SelectParams(args.GetChainName()); - return true; + return std::nullopt; } MAIN_FUNCTION @@ -106,7 +111,7 @@ MAIN_FUNCTION SetupEnvironment(); RandomInit(); try { - if (!WalletAppInit(args, argc, argv)) return EXIT_FAILURE; + if (const auto maybe_exit{WalletAppInit(args, argc, argv)}) return *maybe_exit; } catch (const std::exception& e) { PrintExceptionContinue(&e, "WalletAppInit()"); return EXIT_FAILURE; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 85ba88f6ab..9f81640ddb 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -216,7 +216,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) if (token) { // Success exit(EXIT_SUCCESS); } else { // fRet = false or token read error (premature exit). - tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n"); + tfm::format(std::cerr, "Error during initialization - check debug.log for details\n"); exit(EXIT_FAILURE); } } diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 5b5c76d51d..f065647d49 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -861,17 +861,91 @@ static const uint8_t chainparams_seed_main[] = { }; static const uint8_t chainparams_seed_test[] = { - 0x04,0x20,0xdf,0x55,0xaa,0x83,0xd5,0xc5,0xb8,0xe7,0x75,0x78,0xd4,0x29,0x51,0x4b,0x26,0x1c,0x23,0xdf,0x28,0x4d,0x29,0x85,0x07,0xb5,0xe2,0x29,0x69,0x3e,0x25,0xbb,0x61,0xcf,0x47,0x9d, - 0x04,0x20,0x0a,0xdd,0xa2,0x48,0xb5,0x56,0xa3,0x1f,0xca,0x3c,0x4c,0x9e,0xca,0x6e,0xb3,0xd5,0x5e,0x68,0xf6,0x28,0x31,0x57,0x24,0xfb,0x9d,0x2b,0x55,0x4f,0xd7,0x90,0x62,0xd3,0x47,0x9d, - 0x04,0x20,0x2d,0x04,0xa1,0x4a,0xd4,0x7c,0x7b,0x16,0x2e,0xb7,0xd2,0xa1,0x08,0xc5,0xd2,0xbd,0x53,0x87,0x34,0xdc,0x38,0x26,0xca,0x56,0xf2,0xac,0xc5,0x62,0x70,0x72,0x3f,0x63,0x47,0x9d, - 0x04,0x20,0x30,0x57,0x85,0xe0,0x02,0x4a,0xd1,0x31,0xeb,0x16,0x1b,0x1d,0xa8,0x43,0x0b,0xb4,0xc6,0xac,0x7d,0x46,0x24,0x0b,0x55,0x9d,0x16,0xe6,0x46,0x03,0x72,0xfe,0xd4,0xef,0x47,0x9d, - 0x04,0x20,0x36,0x6c,0xf1,0xd2,0xbb,0xda,0xff,0x8c,0x93,0x61,0x10,0xf2,0x9d,0xa1,0xa4,0x0a,0x30,0x9b,0x0c,0x69,0x6d,0xaa,0xd4,0x9c,0xfd,0xb5,0x5b,0x5e,0x30,0x9f,0xf3,0x13,0x47,0x9d, - 0x04,0x20,0x3e,0xe2,0xf3,0xe5,0xc5,0xbe,0x61,0xdd,0x4c,0x3e,0xdb,0x0d,0xd2,0xf9,0x42,0xe3,0x31,0xb2,0xa8,0x51,0x31,0xf6,0xce,0xc2,0x38,0x20,0x27,0x39,0x73,0x68,0x5a,0x42,0x47,0x9d, - 0x04,0x20,0x51,0x79,0x05,0x9c,0x8a,0xdf,0x03,0xb5,0x1b,0x17,0xc3,0x86,0xb6,0x54,0xcc,0xe0,0x6e,0x58,0xa6,0x41,0x4c,0xcc,0x0c,0x60,0x08,0xa6,0x0f,0x1d,0x11,0xd8,0x29,0xa6,0x47,0x9d, + 0x04,0x20,0xd7,0x13,0xfe,0x00,0xf0,0xf1,0x07,0xcb,0x30,0xb1,0x31,0xc7,0x68,0xbb,0x05,0xca,0x18,0xb5,0x1d,0xb1,0x0f,0xf2,0x7e,0x66,0xdb,0xaa,0xf4,0x05,0xaf,0x37,0x0a,0x62,0x47,0x9d, + 0x04,0x20,0xd1,0x71,0xfe,0x4c,0x9e,0xe9,0x99,0xb1,0x6c,0xd5,0xdd,0x3a,0xc3,0xd8,0x74,0x1d,0x42,0x32,0x9f,0xca,0xe9,0x47,0x9e,0x18,0x74,0x43,0x22,0xb9,0xa5,0x31,0xb9,0x66,0x47,0x9d, + 0x04,0x20,0xd2,0xe4,0xd1,0x40,0x65,0x5d,0x95,0xaf,0xe8,0x67,0xc0,0xe3,0x72,0xfd,0x0a,0x2e,0x35,0xb7,0xbd,0xac,0x67,0x78,0x36,0xe1,0xb8,0xba,0x30,0xcf,0x2e,0x05,0xdd,0x9d,0x47,0x9d, + 0x04,0x20,0xde,0xdd,0xe8,0x01,0x03,0x98,0x3f,0x2d,0x3d,0x8b,0x99,0x27,0xea,0xe1,0xa3,0xde,0xff,0x50,0x30,0xc2,0x41,0x4e,0x7e,0x72,0x24,0x3d,0xad,0x78,0x3c,0xab,0x12,0x67,0x47,0x9d, + 0x04,0x20,0xdb,0x9d,0xe0,0xc8,0x5d,0x65,0x4b,0xe0,0x7b,0x0f,0x63,0x80,0x85,0x21,0x73,0x07,0x94,0x0f,0xb0,0x23,0x38,0xec,0x78,0xb6,0x07,0xa1,0xd2,0xb0,0x82,0xf8,0x20,0xea,0x47,0x9d, + 0x04,0x20,0xdb,0xc9,0xb2,0xe1,0x4b,0x43,0xf1,0xc9,0x3d,0xea,0x51,0x9a,0x71,0x6b,0x4a,0x15,0xe7,0x3e,0xfe,0x8b,0x24,0x7a,0xaa,0xff,0x53,0xf7,0xfc,0x10,0xc6,0xcc,0x14,0x88,0x47,0x9d, + 0x04,0x20,0xe5,0x38,0xc1,0x67,0x52,0x2d,0x6f,0x2f,0xbe,0xc2,0xb3,0x45,0x2b,0x19,0x8c,0x10,0xf3,0x09,0xe5,0x37,0xfe,0x66,0x57,0x6f,0x7a,0x4d,0x78,0xf8,0x50,0x41,0xd6,0x90,0x47,0x9d, + 0x04,0x20,0xed,0x76,0x8d,0x28,0xb0,0x44,0x2c,0xff,0xf4,0xd9,0x6a,0x5c,0xf9,0xca,0x5f,0x81,0xa2,0x23,0x48,0x82,0x24,0x2e,0xfc,0xbe,0x0a,0x1e,0xf9,0x02,0x43,0x31,0x37,0xf5,0x47,0x9d, + 0x04,0x20,0xee,0x57,0x1b,0xab,0xd0,0xd7,0x27,0xca,0xe7,0xaa,0xe2,0x09,0xf3,0x4e,0x80,0x90,0xd2,0x0a,0x9d,0xc4,0x59,0xd0,0xdc,0xa1,0x97,0x09,0x4e,0x2f,0xfe,0x9f,0xa8,0x0a,0x47,0x9d, + 0x04,0x20,0xf7,0xe5,0xb0,0x17,0xdc,0x21,0xed,0xfb,0xb6,0xf5,0xe6,0xb4,0x0b,0xb3,0xf3,0x6c,0xb8,0x3f,0x2a,0x93,0x89,0x66,0x2f,0x05,0x30,0xdf,0xfa,0xdf,0xf2,0x1b,0x52,0x78,0x47,0x9d, + 0x04,0x20,0xf0,0x38,0xe6,0xe3,0xbb,0x4d,0x17,0x56,0xd0,0xae,0xf6,0xf4,0xa9,0x99,0x96,0xa9,0xe8,0xfc,0xfb,0x76,0x08,0xed,0x7f,0xb7,0xf0,0xa0,0xd3,0x8d,0xea,0x18,0x87,0x8c,0x47,0x9d, + 0x04,0x20,0xf1,0x67,0x83,0x6d,0xa7,0x17,0xa6,0xdd,0x1f,0x19,0x5c,0xbd,0x92,0x8e,0x45,0x0c,0x5c,0x2c,0x9d,0x57,0x98,0xe1,0x46,0x40,0x3d,0x87,0x30,0x02,0x9f,0x1d,0x88,0xde,0x47,0x9d, + 0x04,0x20,0xff,0x7b,0xca,0x5b,0xa3,0x4c,0x68,0x7d,0xf1,0x3d,0x18,0xef,0xb4,0x3e,0x39,0x34,0x0d,0x9c,0x3c,0x49,0x9b,0xe7,0x67,0x2c,0x78,0x61,0x42,0x67,0x62,0xd0,0x15,0xb5,0x47,0x9d, + 0x04,0x20,0xff,0xbc,0xb7,0x65,0x60,0x0d,0xc1,0x04,0xbc,0x30,0x95,0x67,0xe9,0x37,0x62,0xf7,0x5b,0xe3,0x2a,0xd6,0x3b,0xaf,0x8a,0x98,0xc9,0xfb,0x4b,0x66,0xb2,0xdc,0x13,0xc0,0x47,0x9d, + 0x04,0x20,0xf8,0x57,0x5e,0x80,0x2d,0xc4,0xc6,0x5e,0x7f,0x15,0x09,0xee,0xcc,0x29,0xf1,0x65,0x53,0x1c,0x41,0xc1,0xf7,0x51,0xa4,0xb0,0x76,0x86,0xe3,0x10,0x24,0x4d,0xd2,0x11,0x47,0x9d, + 0x04,0x20,0xfd,0x74,0x90,0x71,0x06,0xbf,0x2b,0xdc,0x69,0x31,0xf2,0x63,0x1f,0x18,0x17,0x71,0xd2,0x75,0xe4,0xf5,0x35,0x89,0xc0,0x55,0x71,0x05,0xc4,0x77,0x64,0xbf,0x20,0x36,0x47,0x9d, + 0x04,0x20,0xfe,0x4c,0x16,0x4f,0x37,0x75,0xb4,0xe2,0x54,0x7d,0x00,0x21,0x39,0x3a,0x2c,0xb7,0xe8,0x9a,0xf3,0x2f,0x8a,0xf6,0xe4,0x6b,0x7d,0xea,0x18,0xc3,0xc3,0x86,0x57,0xc5,0x47,0x9d, + 0x04,0x20,0x00,0xe5,0x30,0x05,0x39,0xf9,0x05,0xac,0x6d,0x33,0xe5,0xb4,0x3a,0xd1,0x8c,0x75,0xb3,0x5f,0x9b,0xcc,0xd1,0x76,0xa0,0x24,0x84,0x49,0x4b,0x40,0x67,0xee,0x4d,0x93,0x47,0x9d, + 0x04,0x20,0x01,0x25,0x8f,0x4c,0xb4,0x28,0x06,0xaa,0x4f,0xc5,0x5d,0x34,0x19,0x40,0xcd,0xb6,0xb9,0xad,0x52,0x3a,0xc3,0x52,0x05,0x9a,0x97,0x5e,0x69,0x9a,0x2a,0x66,0xde,0x48,0x47,0x9d, + 0x04,0x20,0x02,0xee,0xed,0xe8,0x3d,0x20,0xd1,0xb0,0xb7,0x44,0xd6,0xb9,0x08,0x9b,0x13,0x35,0xee,0xf4,0x0b,0x8a,0x6e,0x10,0xfa,0xbc,0x75,0x14,0xcb,0x28,0xd4,0x40,0x44,0x3a,0x47,0x9d, + 0x04,0x20,0x03,0x88,0x08,0xcf,0x7c,0xa3,0x1c,0xe6,0xd4,0x7a,0x50,0x61,0x1d,0x84,0x5c,0x95,0x89,0x4f,0x03,0x8c,0x91,0xf4,0x67,0xf2,0x4a,0xc4,0x2a,0x44,0x6e,0x47,0xc8,0xae,0x47,0x9d, + 0x04,0x20,0x03,0xe3,0x9a,0xa7,0xe7,0x30,0xa7,0x21,0x93,0x8c,0x52,0x76,0x4b,0x43,0x7d,0x35,0x70,0xa9,0x3d,0x35,0x54,0x11,0x83,0x1b,0xe1,0x7b,0x6d,0xd9,0xaa,0xb2,0x26,0x9b,0x47,0x9d, + 0x04,0x20,0x05,0x9e,0xaf,0x67,0x77,0x31,0xef,0xe7,0x65,0xd4,0x3b,0x86,0x2c,0x0c,0x10,0x9c,0x7d,0x4c,0xe4,0x2d,0xb7,0x05,0x12,0x51,0x17,0xfb,0x15,0x47,0xdd,0xd7,0x5b,0x0e,0x47,0x9d, + 0x04,0x20,0x06,0x30,0xd8,0x03,0x34,0x16,0x0f,0xa4,0x8f,0xb1,0x21,0xc4,0x53,0x19,0x87,0xa3,0x60,0xf1,0xdb,0x2b,0x09,0xf2,0x18,0x1b,0x1e,0x5a,0x78,0x29,0xe7,0x4e,0x12,0x7e,0x47,0x9d, + 0x04,0x20,0x0e,0x90,0xa0,0x77,0x60,0x80,0x95,0x5e,0x1a,0x5f,0xb3,0x1c,0x6d,0x87,0x11,0x20,0x35,0x52,0xe5,0x9d,0x7f,0xf0,0x4d,0xec,0x68,0x30,0xa4,0xca,0xd5,0xaf,0x0e,0x20,0x47,0x9d, + 0x04,0x20,0x09,0x3f,0x9b,0xec,0xe7,0xf0,0xd6,0x03,0xba,0x2b,0xac,0xa3,0x13,0x41,0x9c,0x70,0x0e,0x58,0x9a,0x0c,0xd4,0xc3,0x66,0x84,0x7c,0xa7,0x76,0xd9,0xd3,0xa5,0xff,0xba,0x47,0x9d, + 0x04,0x20,0x0a,0xe8,0xaf,0x6a,0xc9,0xd7,0x03,0x2a,0x8e,0xc7,0xe7,0xd9,0x47,0x77,0x87,0x3d,0xa4,0x09,0xa8,0xb3,0x44,0x2e,0x4a,0xcf,0xaa,0x08,0x3e,0x79,0x4d,0x2e,0x25,0xfd,0x47,0x9d, + 0x04,0x20,0x0d,0x02,0xcf,0x15,0x0e,0x79,0x72,0xab,0xc2,0x25,0xbf,0xab,0x07,0xa1,0xc4,0xe8,0x0e,0xb3,0xe2,0x81,0xcf,0x7e,0xe3,0x4a,0x10,0xc7,0x0e,0x0e,0xbd,0xad,0x32,0x20,0x47,0x9d, + 0x04,0x20,0x1e,0xc5,0x90,0x07,0x29,0x4f,0x7e,0xb3,0x46,0x51,0xe9,0x81,0x65,0x24,0x7f,0xae,0xcd,0x96,0xc6,0x00,0xfe,0x70,0x8c,0xc1,0x8a,0xe2,0xe3,0x80,0x48,0x40,0xab,0x10,0x47,0x9d, + 0x04,0x20,0x18,0xd3,0xc1,0x52,0xa1,0xde,0xaa,0x4e,0x88,0xf3,0x7f,0x92,0x09,0xcc,0x43,0x6b,0x59,0x76,0x3d,0xce,0x2a,0x66,0x7c,0xa3,0xf3,0x39,0xaf,0x73,0xcf,0xf8,0x83,0x89,0x47,0x9d, + 0x04,0x20,0x1c,0x0e,0x75,0xca,0x45,0xb3,0x0f,0xb3,0x7b,0x27,0xb5,0xde,0x22,0x86,0xde,0xe5,0xf3,0xd8,0x95,0x0a,0x11,0x96,0x86,0x84,0xee,0xbd,0x7a,0x04,0xd7,0x90,0xca,0x04,0x47,0x9d, + 0x04,0x20,0x1c,0x66,0x71,0x60,0x3c,0xbf,0x22,0x32,0x91,0x56,0xe9,0xbf,0x74,0xb8,0xd7,0x47,0xc1,0x07,0x2e,0x88,0x59,0xa8,0xb0,0x9a,0xd5,0x93,0x09,0xb6,0xdb,0x6e,0x40,0x6a,0x47,0x9d, + 0x04,0x20,0x1d,0x83,0xcf,0x89,0x90,0x06,0xa6,0x97,0xb2,0xa9,0x01,0x01,0x1f,0x98,0x62,0x04,0x65,0xa5,0x93,0x3e,0x6a,0x08,0x53,0xa3,0x90,0x2e,0xb5,0x02,0x1e,0x78,0x98,0x3d,0x47,0x9d, + 0x04,0x20,0x27,0xe6,0xa8,0x97,0xbc,0x69,0xb7,0x0e,0xd4,0x4d,0xe9,0x9b,0xff,0xe6,0xc9,0xb3,0x3f,0xc5,0xa8,0xa0,0xaf,0x19,0x61,0xd2,0xfb,0x7d,0x5c,0xdf,0x62,0xb0,0x36,0xe6,0x47,0x9d, + 0x04,0x20,0x24,0xe9,0x86,0x63,0x9f,0x96,0xe1,0x42,0x3e,0xa3,0x03,0x9d,0xfd,0x23,0xa4,0xeb,0x05,0xfa,0x3a,0xb1,0xcd,0x3a,0xce,0x24,0xbd,0x87,0x99,0x65,0xd9,0x19,0x75,0xf1,0x47,0x9d, + 0x04,0x20,0x28,0x50,0xc2,0x49,0xb8,0x3e,0x68,0x10,0xe6,0x02,0xdd,0x01,0x42,0xe5,0x41,0xc1,0x58,0xd5,0x5e,0xb8,0x7b,0xae,0x0a,0x90,0x14,0x0f,0xe3,0x97,0xe4,0xfa,0x2b,0xaf,0x47,0x9d, + 0x04,0x20,0x2e,0x43,0x1b,0x30,0xd6,0x62,0x9d,0xf8,0x50,0x8b,0x89,0xcb,0x49,0x2a,0x7b,0x42,0x40,0x10,0x0b,0xfe,0xcd,0xfe,0x6e,0xe8,0x65,0x89,0x2a,0xc3,0x8d,0x49,0xf2,0x2a,0x47,0x9d, + 0x04,0x20,0x36,0x3c,0xd4,0x1f,0x8f,0x63,0xfa,0x49,0x62,0xb5,0x69,0xd6,0x9f,0x42,0xaa,0xfe,0x54,0x14,0xd1,0xd2,0xb2,0xae,0x52,0xe1,0x08,0x7e,0xc6,0x15,0x56,0x45,0xbd,0xb3,0x47,0x9d, + 0x04,0x20,0x38,0x3d,0xac,0xa2,0x19,0x80,0xdc,0x61,0xff,0xb9,0x37,0xd6,0x53,0xf0,0xb0,0x89,0xb6,0x14,0x33,0x62,0x7c,0x33,0x8a,0x3d,0xdc,0xdd,0xba,0xfb,0x70,0xa4,0x6f,0x9b,0x47,0x9d, + 0x04,0x20,0x38,0x04,0x94,0x99,0x3a,0x60,0x61,0x08,0xcc,0xf9,0x43,0x18,0x8f,0x01,0xb5,0x43,0x7e,0x35,0xa5,0x27,0x2a,0xf6,0x85,0x78,0x81,0x36,0xed,0xb6,0xb0,0xd2,0xac,0x77,0x47,0x9d, + 0x04,0x20,0x38,0x54,0xfe,0xde,0xcf,0x83,0xb9,0x06,0x7f,0xaa,0x79,0x96,0x58,0x89,0x78,0x45,0x29,0x51,0x9a,0xbf,0x64,0xd1,0x02,0xe2,0x5f,0x74,0x15,0x2e,0x0d,0x8f,0x81,0x9e,0x47,0x9d, + 0x04,0x20,0x3c,0x0c,0xec,0x7b,0x47,0x74,0x12,0xcc,0xef,0xe1,0x88,0xcb,0x57,0x11,0xd5,0xd8,0x91,0x7e,0x95,0x17,0x0c,0x12,0xe2,0x46,0x7c,0xc0,0xe3,0xe3,0x92,0x26,0xeb,0x85,0x47,0x9d, + 0x04,0x20,0x3d,0x42,0xc6,0x66,0xb0,0x8b,0xcc,0xf9,0x6a,0xfd,0xa7,0x10,0xfe,0x2a,0x45,0xd9,0x3a,0xcd,0x15,0xa4,0x00,0xbf,0xde,0x1a,0x6d,0x3a,0x5b,0xa8,0xc8,0x95,0x6a,0x3c,0x47,0x9d, + 0x04,0x20,0x3d,0xb7,0x5c,0xbf,0x7a,0xb7,0x0f,0xe3,0x21,0xeb,0xa1,0x3c,0x8b,0xf3,0x9d,0x72,0x3d,0x69,0xfc,0xcc,0x22,0xa5,0x1c,0xb4,0x3f,0x98,0xcb,0x63,0xa8,0xc8,0x9f,0xcc,0x47,0x9d, + 0x04,0x20,0x40,0xc7,0x1f,0x78,0x96,0x51,0xc8,0xd4,0x54,0x0f,0x32,0x00,0xda,0x0d,0x2a,0xb4,0x04,0xff,0x0e,0xf3,0x94,0x8d,0xf4,0x5e,0x23,0x74,0xfa,0x0c,0xfa,0x87,0xfb,0x3b,0x47,0x9d, + 0x04,0x20,0x43,0x30,0x0d,0xf9,0x04,0xba,0x10,0x88,0x45,0x56,0xa5,0xee,0x22,0x1d,0xb5,0xe2,0x8d,0xd9,0x1e,0x68,0x8d,0x87,0xaa,0x9b,0x82,0x6a,0x71,0xe5,0x95,0x2b,0x79,0xf6,0x47,0x9d, + 0x04,0x20,0x45,0x0e,0x7a,0x38,0x4d,0x97,0xc8,0xd9,0xc7,0x2b,0xbc,0xae,0xa9,0xe3,0x3a,0x40,0x65,0xf3,0xc0,0xc8,0x31,0xab,0xe9,0x02,0x99,0x82,0xb2,0xa0,0x79,0x60,0x92,0xed,0x47,0x9d, + 0x04,0x20,0x4a,0x3b,0x66,0x48,0xee,0x55,0x05,0xed,0x91,0x14,0x5e,0x57,0x64,0xeb,0x23,0xab,0x56,0x83,0x56,0x36,0x2d,0x6a,0x2f,0xf8,0x28,0x11,0x37,0x3a,0x73,0x76,0x2e,0x6a,0x47,0x9d, + 0x04,0x20,0x4a,0x4b,0x4c,0x27,0xea,0x89,0xb2,0xa6,0x3e,0xf5,0x6e,0xc3,0xa4,0xe8,0xf6,0x5e,0x54,0xcc,0x93,0x64,0xce,0x36,0x82,0xd9,0x5f,0x1b,0x65,0x6b,0x02,0xc6,0x66,0x60,0x47,0x9d, + 0x04,0x20,0x4b,0x74,0xfe,0x48,0xc5,0x79,0xd5,0x77,0xba,0xdf,0x52,0x63,0x40,0xc4,0x30,0x04,0xff,0xf5,0x29,0x01,0xcc,0x15,0x5a,0x58,0xf7,0xc0,0x16,0x28,0x83,0x36,0x60,0xa0,0x47,0x9d, + 0x04,0x20,0x4c,0x6a,0x4c,0x67,0x94,0x97,0xa4,0xe3,0x78,0x9e,0x16,0x65,0x58,0xf5,0x95,0xa9,0xcd,0x94,0x9a,0xbc,0x40,0xc2,0x4e,0x20,0x71,0x72,0xd9,0x02,0x50,0x75,0x9f,0x41,0x47,0x9d, + 0x04,0x20,0x4c,0x85,0xc2,0xc6,0xb5,0x6a,0xf4,0x3d,0x84,0xf9,0xc3,0x8b,0x21,0x7f,0x57,0x7e,0x66,0x66,0x6b,0x6d,0x5f,0x0a,0xd0,0xf0,0x76,0x56,0x65,0x78,0xf8,0xa7,0x42,0x75,0x47,0x9d, + 0x04,0x20,0x52,0xf0,0xcb,0x8e,0xa9,0x27,0xd4,0x21,0x57,0x73,0xcb,0x40,0x17,0x6e,0x83,0x73,0xe4,0x59,0xa9,0x89,0x23,0x14,0x14,0x6a,0x91,0x85,0xd2,0xce,0x1c,0x85,0x05,0x06,0x47,0x9d, + 0x04,0x20,0x55,0x93,0x75,0x82,0x96,0x4c,0xc8,0x24,0x96,0x48,0xb3,0x9e,0x15,0xf5,0xf5,0x6f,0x5b,0xec,0xf6,0x39,0xff,0xa8,0xaf,0x3a,0x4c,0x5d,0x22,0x3d,0xd3,0x14,0xc3,0x05,0x47,0x9d, + 0x04,0x20,0x58,0xbf,0x47,0xe7,0x52,0x8d,0xb5,0x83,0xbc,0x55,0xb1,0xb8,0x02,0xe3,0xdc,0xc2,0xc4,0xe7,0x79,0x85,0xb2,0xe7,0x38,0x27,0xae,0xad,0x0c,0xb8,0x34,0xed,0x71,0xde,0x47,0x9d, + 0x04,0x20,0x59,0x76,0xc7,0xdc,0xb8,0x4d,0x1d,0x51,0x40,0x75,0x43,0x57,0xdd,0x3e,0xa3,0x63,0x06,0x0f,0x5e,0x18,0x29,0x10,0x08,0xac,0xa0,0x60,0xd2,0xf3,0x63,0x6e,0xa6,0x58,0x47,0x9d, 0x04,0x20,0x60,0xbe,0xae,0x7d,0xa3,0x4d,0x6a,0x71,0x1a,0x5d,0xe5,0x98,0x9c,0xde,0xa0,0x99,0x39,0x19,0xd3,0x01,0x0a,0x5d,0x1c,0x21,0x43,0x94,0x92,0x71,0x5d,0x77,0xd7,0xdf,0x47,0x9d, - 0x04,0x20,0x64,0x4e,0x86,0xa1,0x02,0xa1,0x8a,0xef,0xb0,0xd1,0xb5,0x77,0x69,0xb9,0x6a,0xdc,0xdf,0x35,0x8a,0xda,0xa4,0x3e,0x83,0xfa,0x50,0xe6,0xca,0x0e,0x2b,0x99,0x0a,0x17,0x47,0x9d, - 0x04,0x20,0xa2,0x28,0x3c,0x5a,0x5b,0x82,0x32,0x66,0x11,0xe5,0x71,0xff,0x6b,0x25,0x92,0x75,0xdd,0x7a,0x4f,0x90,0x8b,0x1d,0x34,0xa4,0xf1,0x6e,0xb9,0xfb,0xb5,0x2e,0x7c,0x7f,0x47,0x9d, - 0x04,0x20,0xc8,0xb5,0x6a,0xba,0x02,0x26,0x45,0x12,0xfb,0x93,0x8a,0x51,0xe4,0xb0,0xf3,0x94,0xb7,0xc0,0x74,0x72,0xeb,0x67,0x91,0x9e,0x04,0x36,0x6a,0x4b,0xef,0x0d,0x88,0xfe,0x47,0x9d, - 0x04,0x20,0xc8,0xfa,0xcd,0x8c,0xc3,0x6f,0x3c,0xd0,0x27,0x7e,0x7d,0xeb,0x51,0x01,0x65,0xb6,0x9e,0x02,0x09,0x64,0xf4,0x87,0x78,0x7b,0x8f,0x9d,0xaf,0x3b,0xa5,0xcc,0x56,0x2c,0x47,0x9d, + 0x04,0x20,0x62,0x42,0x6d,0x98,0xc4,0xa1,0x16,0xc1,0x7d,0x17,0x9e,0x37,0x94,0x88,0x58,0x76,0xa9,0x13,0x6a,0x98,0x39,0x22,0x58,0x13,0xf8,0x9a,0x99,0xa2,0x25,0x56,0x59,0x05,0x47,0x9d, + 0x04,0x20,0x6a,0xb1,0x07,0x66,0xbe,0x50,0xec,0x8e,0x6b,0x72,0xfc,0xb4,0xc2,0x67,0x26,0xa1,0x7a,0x10,0x15,0xd3,0xd0,0x36,0x7a,0xf0,0x83,0xd0,0xc3,0x59,0x85,0x68,0x90,0xd8,0x47,0x9d, + 0x04,0x20,0x6d,0x6b,0x07,0x72,0xf7,0x45,0x8c,0x1d,0xe3,0x5c,0xf2,0x58,0x20,0xd2,0x95,0x25,0x22,0x51,0xc3,0x59,0x96,0xc1,0xdc,0x7e,0xa4,0x82,0x89,0x0a,0xf5,0x25,0x38,0x1a,0x47,0x9d, + 0x04,0x20,0x77,0xaa,0x57,0x56,0x17,0xce,0xdd,0x79,0x64,0xaf,0x78,0xb2,0xf4,0x91,0x36,0x75,0x1c,0x1c,0xcd,0x31,0x35,0x4a,0xe4,0x8b,0x64,0x65,0x4e,0x06,0xec,0x2a,0xc4,0x95,0x47,0x9d, + 0x04,0x20,0x70,0x11,0x6d,0x2d,0xa7,0xc9,0x9f,0xd8,0xe3,0xe8,0xae,0x5a,0x4a,0x50,0xac,0x3f,0xd6,0x57,0xad,0xd0,0xa5,0x35,0x14,0xd7,0xb1,0x76,0x58,0xb0,0x90,0xa4,0x1c,0x9b,0x47,0x9d, + 0x04,0x20,0x72,0xdb,0xfc,0x04,0x48,0xee,0xec,0xae,0x1b,0xad,0xe6,0x9a,0x87,0xe1,0x17,0xae,0x13,0x70,0xae,0x83,0x90,0x62,0x59,0x30,0xed,0x44,0x43,0xe9,0xab,0xee,0xaa,0xd0,0x47,0x9d, + 0x04,0x20,0x74,0xd8,0x56,0x9c,0x5e,0xb4,0x62,0x12,0xea,0x6f,0x1e,0x3d,0x95,0x22,0x50,0x16,0xe3,0xba,0x45,0xb2,0xa3,0xad,0x26,0xb0,0x13,0x74,0xdd,0xec,0x22,0xe0,0x70,0x9a,0x47,0x9d, + 0x04,0x20,0x75,0x45,0xe9,0x01,0x21,0x81,0x44,0x1b,0x57,0x76,0x8c,0x3f,0x85,0xa9,0x74,0xf4,0x3c,0xf8,0xd5,0x12,0xcf,0x13,0x1e,0x2f,0x18,0x13,0x78,0xa3,0x51,0x59,0x49,0x49,0x47,0x9d, + 0x04,0x20,0x7b,0x3b,0xf1,0xa0,0x78,0xc9,0xba,0xb4,0xd2,0xad,0xed,0x0b,0x1e,0xd2,0x8d,0xd2,0x08,0xd1,0x51,0x5b,0x01,0x74,0x6c,0xc0,0x5d,0xcd,0x4c,0x53,0xf3,0xba,0x16,0xe3,0x47,0x9d, + 0x04,0x20,0x7b,0x11,0x26,0x1d,0xf6,0xa1,0xb7,0xc3,0x36,0x19,0x0d,0x27,0x10,0x36,0xb7,0xd5,0x93,0x60,0xc8,0xf8,0x82,0x79,0x46,0xe8,0x78,0xce,0x29,0x50,0x2b,0x44,0x3f,0x7a,0x47,0x9d, + 0x04,0x20,0x7b,0x96,0x08,0xa2,0x54,0x5c,0xd0,0x94,0x9c,0x0a,0xa7,0xb3,0xea,0x7d,0x38,0x0b,0x8e,0xee,0x91,0xe3,0x91,0xb9,0xc9,0xcc,0x81,0x28,0x2e,0xc3,0x02,0x9a,0xcf,0x65,0x47,0x9d, + 0x04,0x20,0x81,0x34,0x90,0x93,0x9c,0xf9,0xc9,0xe7,0x81,0xa5,0xab,0xf1,0x03,0xe1,0xb3,0x2f,0xf7,0xcf,0x19,0x94,0x2f,0x7a,0x1b,0xc5,0x83,0xc9,0x8c,0xc3,0x5b,0x09,0x96,0x14,0x47,0x9d, + 0x04,0x20,0x86,0x7d,0x81,0xf5,0x72,0x30,0xc0,0x91,0x4c,0x8e,0x04,0x48,0x47,0xce,0xcd,0x43,0xef,0x22,0x67,0x17,0xea,0x95,0x2f,0x51,0x4f,0x57,0x67,0x58,0x21,0xa5,0x2e,0x68,0x47,0x9d, + 0x04,0x20,0x8b,0xfc,0xf6,0xec,0x3b,0x71,0x25,0x88,0xe8,0xc8,0x72,0xc3,0x89,0x05,0x27,0x81,0x28,0x6b,0xb6,0x72,0xa7,0xc5,0x16,0xaa,0x67,0xe6,0x37,0xb7,0x2b,0x99,0xa8,0x97,0x47,0x9d, + 0x04,0x20,0xa2,0x46,0x23,0xd2,0xdb,0x3e,0x7a,0x65,0x05,0xd4,0x45,0x78,0xff,0x3e,0xc1,0x28,0xdb,0xfa,0xf1,0x5e,0x32,0xe9,0x6a,0x8f,0x96,0x45,0x15,0x01,0x7f,0x1f,0x75,0xbf,0x47,0x9d, + 0x04,0x20,0xa8,0xa6,0xbb,0x02,0x06,0x65,0x3b,0xb2,0x52,0x79,0xd1,0xc9,0x6a,0x01,0x42,0x43,0xca,0x69,0xe1,0x69,0x90,0xfe,0x2c,0xd2,0x7c,0x18,0xfd,0xab,0xc7,0x71,0x7f,0x8b,0x47,0x9d, + 0x04,0x20,0xa9,0x7b,0x48,0xf7,0x73,0xcb,0x07,0x37,0x4e,0xaa,0x68,0xc4,0x67,0x08,0xea,0x5b,0x68,0x0a,0xfe,0x14,0x82,0x6a,0xa1,0x79,0x82,0x9c,0x0b,0x52,0x17,0xd6,0x6b,0x1b,0x47,0x9d, + 0x04,0x20,0xb3,0x6e,0x64,0x99,0x26,0x7a,0xeb,0xd4,0x53,0x8c,0x94,0xb1,0x4a,0x37,0x02,0xaa,0xa9,0xc8,0x36,0x11,0xec,0xf9,0x0f,0x08,0xa3,0x9f,0x3e,0xaf,0xd2,0xd1,0x2a,0x0b,0x47,0x9d, + 0x04,0x20,0xb3,0xd4,0x15,0x0e,0x3f,0x12,0xed,0xfc,0xab,0x69,0x55,0x91,0x45,0xcf,0xc1,0x2f,0x35,0xa4,0xf7,0x94,0xff,0xd8,0xbe,0x44,0xc8,0x36,0xaf,0x0f,0xa0,0x4e,0x36,0x22,0x47,0x9d, + 0x04,0x20,0xb6,0x5e,0x39,0xeb,0x93,0x18,0x22,0xad,0x78,0x94,0x34,0x48,0x31,0x8d,0xd1,0x42,0xb7,0x38,0x58,0x49,0xea,0x00,0xb9,0xcf,0x58,0x43,0x38,0x22,0x22,0x88,0x87,0xde,0x47,0x9d, + 0x04,0x20,0xb9,0x80,0xf6,0xa1,0x4a,0x0a,0xc6,0xe3,0x19,0x27,0xe9,0x2d,0x75,0x60,0x14,0x3d,0x2c,0xda,0x97,0x77,0x54,0x9f,0x78,0xb8,0x9b,0x72,0x6a,0x58,0xab,0x8b,0x95,0x95,0x47,0x9d, + 0x04,0x20,0xba,0xab,0x91,0x9c,0x48,0x8b,0x9b,0x34,0x5c,0x30,0xf3,0xd2,0x78,0xd3,0xbf,0x09,0x79,0x23,0x4e,0x18,0x5d,0x5e,0x75,0x6b,0xa2,0x91,0x63,0x2c,0x75,0xfd,0x06,0x36,0x47,0x9d, + 0x04,0x20,0xbb,0x50,0xf0,0x50,0x8d,0xb7,0x6c,0xd4,0x87,0x59,0x0d,0xfd,0x5f,0x51,0x86,0xa8,0x43,0xb3,0x7a,0xe7,0x2f,0x54,0x97,0x7d,0xed,0xc3,0x7b,0x31,0xe6,0xb9,0x05,0xe7,0x47,0x9d, + 0x04,0x20,0xc0,0xc1,0xf5,0x59,0xe8,0x46,0xf6,0x9d,0x41,0xf0,0xde,0x8b,0x32,0xf9,0x6f,0xd6,0xb2,0xd5,0x36,0x56,0x43,0xd3,0x60,0x08,0x55,0x94,0x13,0xf5,0xef,0x7f,0x4d,0x4a,0x47,0x9d, + 0x04,0x20,0xce,0xd3,0xd2,0xba,0x56,0xa1,0xde,0xc9,0xc4,0xdb,0x50,0x7b,0xe9,0x4d,0x59,0x65,0x1c,0x02,0x4a,0xa3,0xea,0x73,0xb2,0x66,0x70,0xe7,0x85,0xc1,0x20,0x60,0x79,0x96,0x47,0x9d, + 0x04,0x20,0xc8,0x88,0xfe,0x71,0x5f,0xa3,0x6c,0x96,0x6a,0xd7,0x9e,0x38,0x84,0x9f,0x44,0xe1,0x6b,0xdc,0x98,0x31,0xad,0x96,0x29,0xe7,0x00,0x83,0x63,0x03,0xae,0x69,0x2e,0x63,0x47,0x9d, + 0x04,0x20,0xcb,0x2a,0x8c,0xe7,0xe5,0x1f,0x4e,0x3a,0x13,0xd6,0x9e,0xd7,0x68,0x51,0x83,0xf4,0x2d,0x3e,0x21,0x38,0x63,0x18,0xe9,0x97,0x27,0xff,0x45,0x48,0xc3,0x6c,0xca,0x59,0x47,0x9d, + 0x04,0x20,0xcb,0x80,0x0d,0xdf,0xf0,0xa6,0x28,0x84,0x98,0xc8,0x47,0x72,0xbe,0x53,0x04,0x43,0x9c,0x3a,0x0e,0x46,0x9c,0x75,0x4c,0x57,0x50,0x98,0xe5,0x25,0x80,0x5d,0xa2,0x91,0x47,0x9d, + 0x04,0x20,0xcb,0xaa,0x0a,0x5d,0x6e,0x8a,0xfa,0x49,0x5a,0x8c,0x0c,0x9d,0x7a,0xe9,0x9d,0xc6,0xe4,0xcd,0xc1,0x6a,0xff,0xbb,0xf1,0x89,0xf1,0xd5,0x16,0x3f,0xdc,0xbb,0xe9,0x1b,0x47,0x9d, }; #endif // BITCOIN_CHAINPARAMSSEEDS_H diff --git a/src/checkqueue.h b/src/checkqueue.h index bead6f0c6f..c4a64444e9 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -199,11 +199,12 @@ public: WITH_LOCK(m_mutex, m_request_stop = false); } + bool HasThreads() const { return !m_worker_threads.empty(); } + ~CCheckQueue() { assert(m_worker_threads.empty()); } - }; /** diff --git a/src/common/run_command.cpp b/src/common/run_command.cpp new file mode 100644 index 0000000000..6ad9f75b5d --- /dev/null +++ b/src/common/run_command.cpp @@ -0,0 +1,64 @@ +// 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. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <common/run_command.h> + +#include <tinyformat.h> +#include <univalue.h> + +#ifdef ENABLE_EXTERNAL_SIGNER +#if defined(__GNUC__) +// Boost 1.78 requires the following workaround. +// See: https://github.com/boostorg/process/issues/235 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +#include <boost/process.hpp> +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#endif // ENABLE_EXTERNAL_SIGNER + +UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) +{ +#ifdef ENABLE_EXTERNAL_SIGNER + namespace bp = boost::process; + + UniValue result_json; + bp::opstream stdin_stream; + bp::ipstream stdout_stream; + bp::ipstream stderr_stream; + + if (str_command.empty()) return UniValue::VNULL; + + bp::child c( + str_command, + bp::std_out > stdout_stream, + bp::std_err > stderr_stream, + bp::std_in < stdin_stream + ); + if (!str_std_in.empty()) { + stdin_stream << str_std_in << std::endl; + } + stdin_stream.pipe().close(); + + std::string result; + std::string error; + std::getline(stdout_stream, result); + std::getline(stderr_stream, error); + + c.wait(); + const int n_error = c.exit_code(); + if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error)); + if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); + + return result_json; +#else + throw std::runtime_error("Compiled without external signing support (required for external signing)."); +#endif // ENABLE_EXTERNAL_SIGNER +} diff --git a/src/common/run_command.h b/src/common/run_command.h new file mode 100644 index 0000000000..2fbdc071ee --- /dev/null +++ b/src/common/run_command.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef BITCOIN_COMMON_RUN_COMMAND_H +#define BITCOIN_COMMON_RUN_COMMAND_H + +#include <string> + +class UniValue; + +/** + * Execute a command which returns JSON, and parse the result. + * + * @param str_command The command to execute, including any arguments + * @param str_std_in string to pass to stdin + * @return parsed JSON + */ +UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); + +#endif // BITCOIN_COMMON_RUN_COMMAND_H diff --git a/src/compat/compat.h b/src/compat/compat.h index a8e5552c0a..cc37797577 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -109,14 +109,6 @@ typedef char* sockopt_arg_type; #define USE_POLL #endif -bool static inline IsSelectableSocket(const SOCKET& s) { -#if defined(USE_POLL) || defined(WIN32) - return true; -#else - return (s < FD_SETSIZE); -#endif -} - // MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0 #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index b2a31e3ba4..10bdbf31a8 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_CONSENSUS_CONSENSUS_H #define BITCOIN_CONSENSUS_CONSENSUS_H -#include <stdlib.h> +#include <cstdlib> #include <stdint.h> /** The maximum allowed size for a serialized block, in bytes (only for buffer size limits) */ diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 69fbbe9fa5..de16a77878 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_CHACHA20_H #define BITCOIN_CRYPTO_CHACHA20_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein https://cr.yp.to/chacha/chacha-20080128.pdf */ diff --git a/src/crypto/hkdf_sha256_32.h b/src/crypto/hkdf_sha256_32.h index fa1e42aec1..878b03a37f 100644 --- a/src/crypto/hkdf_sha256_32.h +++ b/src/crypto/hkdf_sha256_32.h @@ -7,8 +7,8 @@ #include <crypto/hmac_sha256.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A rfc5869 HKDF implementation with HMAC_SHA256 and fixed key output length of 32 bytes (L=32) */ class CHKDF_HMAC_SHA256_L32 diff --git a/src/crypto/hmac_sha256.h b/src/crypto/hmac_sha256.h index d31fda1dd1..9c25edd7c1 100644 --- a/src/crypto/hmac_sha256.h +++ b/src/crypto/hmac_sha256.h @@ -7,8 +7,8 @@ #include <crypto/sha256.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for HMAC-SHA-256. */ class CHMAC_SHA256 diff --git a/src/crypto/hmac_sha512.h b/src/crypto/hmac_sha512.h index 1ea9a3671e..6acce8992e 100644 --- a/src/crypto/hmac_sha512.h +++ b/src/crypto/hmac_sha512.h @@ -7,8 +7,8 @@ #include <crypto/sha512.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for HMAC-SHA-512. */ class CHMAC_SHA512 diff --git a/src/crypto/poly1305.h b/src/crypto/poly1305.h index 1598b013b9..c80faada7e 100644 --- a/src/crypto/poly1305.h +++ b/src/crypto/poly1305.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_POLY1305_H #define BITCOIN_CRYPTO_POLY1305_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #define POLY1305_KEYLEN 32 #define POLY1305_TAGLEN 16 diff --git a/src/crypto/ripemd160.h b/src/crypto/ripemd160.h index 38ea375c1f..f1d89b8407 100644 --- a/src/crypto/ripemd160.h +++ b/src/crypto/ripemd160.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_RIPEMD160_H #define BITCOIN_CRYPTO_RIPEMD160_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for RIPEMD-160. */ class CRIPEMD160 diff --git a/src/crypto/sha1.h b/src/crypto/sha1.h index 8b4568ee12..6ef0187efd 100644 --- a/src/crypto/sha1.h +++ b/src/crypto/sha1.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA1_H #define BITCOIN_CRYPTO_SHA1_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for SHA1. */ class CSHA1 diff --git a/src/crypto/sha256.h b/src/crypto/sha256.h index 028ee14345..24bd1f2e7e 100644 --- a/src/crypto/sha256.h +++ b/src/crypto/sha256.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA256_H #define BITCOIN_CRYPTO_SHA256_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #include <string> /** A hasher class for SHA-256. */ diff --git a/src/crypto/sha256_sse4.cpp b/src/crypto/sha256_sse4.cpp index f1a7fefea3..bc69703607 100644 --- a/src/crypto/sha256_sse4.cpp +++ b/src/crypto/sha256_sse4.cpp @@ -5,8 +5,8 @@ // This is a translation to GCC extended asm syntax from YASM code by Intel // (available at the bottom of this file). +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #if defined(__x86_64__) || defined(__amd64__) diff --git a/src/crypto/sha3.h b/src/crypto/sha3.h index 88d8c1204d..78608eae76 100644 --- a/src/crypto/sha3.h +++ b/src/crypto/sha3.h @@ -7,8 +7,8 @@ #include <span.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> //! The Keccak-f[1600] transform. void KeccakF(uint64_t (&st)[25]); diff --git a/src/crypto/sha512.cpp b/src/crypto/sha512.cpp index 85a7bbcb53..59b79609dd 100644 --- a/src/crypto/sha512.cpp +++ b/src/crypto/sha512.cpp @@ -30,7 +30,7 @@ void inline Round(uint64_t a, uint64_t b, uint64_t c, uint64_t& d, uint64_t e, u h = t1 + t2; } -/** Initialize SHA-256 state. */ +/** Initialize SHA-512 state. */ void inline Initialize(uint64_t* s) { s[0] = 0x6a09e667f3bcc908ull; diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index 21ca930c75..7356dff6d9 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA512_H #define BITCOIN_CRYPTO_SHA512_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for SHA-512. */ class CSHA512 diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 4dbc839941..7f45e35aef 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -128,7 +128,7 @@ static leveldb::Options GetOptions(size_t nCacheSize) } CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate) - : m_name{fs::PathToString(path.stem())} + : m_name{fs::PathToString(path.stem())}, m_path{path}, m_is_memory{fMemory} { penv = nullptr; readoptions.verify_checksums = true; diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 665eaa0e98..1052da01d5 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -39,6 +39,10 @@ public: class CDBWrapper; +namespace dbwrapper { + using leveldb::DestroyDB; +} + /** These should be considered an implementation detail of the specific database. */ namespace dbwrapper_private { @@ -219,6 +223,12 @@ private: std::vector<unsigned char> CreateObfuscateKey() const; + //! path to filesystem storage + const fs::path m_path; + + //! whether or not the database resides in memory + bool m_is_memory; + public: /** * @param[in] path Location in the filesystem where leveldb data will be stored. @@ -268,6 +278,14 @@ public: return WriteBatch(batch, fSync); } + //! @returns filesystem path to the on-disk data. + std::optional<fs::path> StoragePath() { + if (m_is_memory) { + return {}; + } + return m_path; + } + template <typename K> bool Exists(const K& key) const { diff --git a/src/external_signer.cpp b/src/external_signer.cpp index 094314e878..f255834830 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -3,10 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparams.h> +#include <common/run_command.h> #include <core_io.h> #include <psbt.h> #include <util/strencodings.h> -#include <util/system.h> #include <external_signer.h> #include <algorithm> @@ -81,6 +81,9 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str for (const auto& entry : input.hd_keypaths) { if (parsed_m_fingerprint == MakeUCharSpan(entry.second.fingerprint)) return true; } + for (const auto& entry : input.m_tap_bip32_paths) { + if (parsed_m_fingerprint == MakeUCharSpan(entry.second.second.fingerprint)) return true; + } return false; }; diff --git a/src/fs.cpp b/src/fs.cpp index 74b167e313..07cce269ed 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -126,7 +126,7 @@ bool FileLock::TryLock() if (hFile == INVALID_HANDLE_VALUE) { return false; } - _OVERLAPPED overlapped = {0}; + _OVERLAPPED overlapped = {}; if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) { reason = GetErrorReason(); return false; diff --git a/src/httpserver.cpp b/src/httpserver.cpp index e68436cc2c..1a19555f76 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -21,11 +21,11 @@ #include <util/threadnames.h> #include <util/translation.h> +#include <cstdio> +#include <cstdlib> #include <deque> #include <memory> #include <optional> -#include <stdio.h> -#include <stdlib.h> #include <string> #include <sys/types.h> @@ -320,7 +320,7 @@ static bool HTTPBindAddresses(struct evhttp* http) // Bind addresses for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) { - LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, i->second); + LogPrintf("Binding RPC on address %s port %i\n", i->first, i->second); evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle(http, i->first.empty() ? nullptr : i->first.c_str(), i->second); if (bind_handle) { CNetAddr addr; diff --git a/src/i2p.cpp b/src/i2p.cpp index f7d480988b..28be8009dc 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -325,6 +325,7 @@ void Session::DestGenerate(const Sock& sock) // https://geti2p.net/spec/common-structures#key-certificates // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations". // Use "7" because i2pd <2.24.0 does not recognize the textual form. + // If SIGNATURE_TYPE is not specified, then the default one is DSA_SHA1. const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); m_private_key = DecodeI2PBase64(reply.Get("PRIV")); @@ -378,7 +379,7 @@ void Session::CreateIfNotCreatedAlready() // in the reply in DESTINATION=. const Reply& reply = SendRequestAndGetReply( *sock, - strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT", session_id)); + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7", session_id)); m_private_key = DecodeI2PBase64(reply.Get("DESTINATION")); } else { diff --git a/src/index/base.cpp b/src/index/base.cpp index 1ebe89ef7c..3eea09b17d 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -18,6 +18,9 @@ #include <validation.h> // For g_chainman #include <warnings.h> +#include <string> +#include <utility> + using node::ReadBlockFromDisk; constexpr uint8_t DB_BEST_BLOCK{'B'}; @@ -62,8 +65,8 @@ void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator batch.Write(DB_BEST_BLOCK, locator); } -BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain) - : m_chain{std::move(chain)} {} +BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name) + : m_chain{std::move(chain)}, m_name{std::move(name)} {} BaseIndex::~BaseIndex() { @@ -295,6 +298,10 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const } interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex, block.get()); if (CustomAppend(block_info)) { + // Setting the best block index is intentionally the last step of this + // function, so BlockUntilSyncedToCurrentChain callers waiting for the + // best block index to be updated can rely on the block being fully + // processed, and the index object being safe to delete. SetBestBlockIndex(pindex); } else { FatalError("%s: Failed to write block %s to index", @@ -411,10 +418,17 @@ IndexSummary BaseIndex::GetSummary() const 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)); } + + // Intentionally set m_best_block_index as the last step in this function, + // after updating prune locks above, and after making any other references + // to *this, so the BlockUntilSyncedToCurrentChain function (which checks + // m_best_block_index as an optimization) can be used to wait for the last + // BlockConnected notification and safely assume that prune locks are + // updated and that the index object is safe to delete. + m_best_block_index = block; } diff --git a/src/index/base.h b/src/index/base.h index 5a484377e7..349178a535 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -10,9 +10,11 @@ #include <threadinterrupt.h> #include <validationinterface.h> +#include <string> + class CBlock; class CBlockIndex; -class CChainState; +class Chainstate; namespace interfaces { class Chain; } // namespace interfaces @@ -94,7 +96,8 @@ private: protected: std::unique_ptr<interfaces::Chain> m_chain; - CChainState* m_chainstate{nullptr}; + Chainstate* m_chainstate{nullptr}; + const std::string m_name; void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override; @@ -117,13 +120,13 @@ protected: virtual DB& GetDB() const = 0; /// Get the name of the index for display in logs. - virtual const char* GetName() const = 0; + const std::string& GetName() const LIFETIMEBOUND { return m_name; } /// Update the internal best block index as well as the prune lock. void SetBestBlockIndex(const CBlockIndex* block); public: - BaseIndex(std::unique_ptr<interfaces::Chain> chain); + BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name); /// 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 f4837f3456..292e11c874 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -97,7 +97,8 @@ static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes; BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe) - : BaseIndex(std::move(chain)), m_filter_type(filter_type) + : BaseIndex(std::move(chain), BlockFilterTypeName(filter_type) + " block filter index") + , m_filter_type(filter_type) { const std::string& filter_name = BlockFilterTypeName(filter_type); if (filter_name.empty()) throw std::invalid_argument("unknown filter_type"); @@ -105,7 +106,6 @@ BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, Blo fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name); fs::create_directories(path); - m_name = filter_name + " block filter index"; m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe); m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE); } diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h index a31f7e460e..9e69388dc8 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -12,6 +12,8 @@ #include <index/base.h> #include <util/hasher.h> +static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; + /** Interval between compact filter checkpoints. See BIP 157. */ static constexpr int CFCHECKPT_INTERVAL = 1000; @@ -26,7 +28,6 @@ class BlockFilterIndex final : public BaseIndex { private: BlockFilterType m_filter_type; - std::string m_name; std::unique_ptr<BaseIndex::DB> m_db; FlatFilePos m_next_filter_pos; @@ -52,8 +53,6 @@ protected: BaseIndex::DB& GetDB() const LIFETIMEBOUND override { return *m_db; } - const char* GetName() const LIFETIMEBOUND override { return m_name.c_str(); } - public: /** Constructs the index, which becomes available to be queried. */ explicit BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type, diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp index b9029e946a..d3559b1b75 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -6,6 +6,7 @@ #include <coins.h> #include <crypto/muhash.h> #include <index/coinstatsindex.h> +#include <kernel/coinstats.h> #include <node/blockstorage.h> #include <serialize.h> #include <txdb.h> @@ -104,7 +105,7 @@ struct DBHashKey { std::unique_ptr<CoinStatsIndex> g_coin_stats_index; CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe) - : BaseIndex(std::move(chain)) + : BaseIndex(std::move(chain), "coinstatsindex") { fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"}; fs::create_directories(path); @@ -322,13 +323,13 @@ static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockKey& block, D return db.Read(DBHashKey(block.hash), result); } -std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const +std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const { - CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()}; + CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()}; stats.index_used = true; DBVal entry; - if (!LookUpOne(*m_db, {block_index->GetBlockHash(), block_index->nHeight}, entry)) { + if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) { return std::nullopt; } diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index c4af223388..aa0d7f9fd5 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -5,11 +5,16 @@ #ifndef BITCOIN_INDEX_COINSTATSINDEX_H #define BITCOIN_INDEX_COINSTATSINDEX_H -#include <chain.h> #include <crypto/muhash.h> -#include <flatfile.h> #include <index/base.h> -#include <kernel/coinstats.h> + +class CBlockIndex; +class CDBBatch; +namespace kernel { +struct CCoinsStats; +} + +static constexpr bool DEFAULT_COINSTATSINDEX{false}; /** * CoinStatsIndex maintains statistics on the UTXO set. @@ -17,7 +22,6 @@ class CoinStatsIndex final : public BaseIndex { private: - std::string m_name; std::unique_ptr<BaseIndex::DB> m_db; MuHash3072 m_muhash; @@ -49,14 +53,12 @@ protected: BaseIndex::DB& GetDB() const override { return *m_db; } - const char* GetName() const override { return "coinstatsindex"; } - public: // Constructs the index, which becomes available to be queried. explicit CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); // Look up stats for a specific block using CBlockIndex - std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex* block_index) const; + std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex& block_index) const; }; /// The global UTXO set hash object. diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index b719aface8..a4fe1b611e 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -49,7 +49,7 @@ bool TxIndex::DB::WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_ } TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe) - : BaseIndex(std::move(chain)), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) + : BaseIndex(std::move(chain), "txindex"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) {} TxIndex::~TxIndex() = default; diff --git a/src/index/txindex.h b/src/index/txindex.h index be240c4582..4cea35045d 100644 --- a/src/index/txindex.h +++ b/src/index/txindex.h @@ -7,6 +7,8 @@ #include <index/base.h> +static constexpr bool DEFAULT_TXINDEX{false}; + /** * TxIndex is used to look up transactions included in the blockchain by hash. * The index is written to a LevelDB database and records the filesystem @@ -27,8 +29,6 @@ protected: BaseIndex::DB& GetDB() const override; - const char* GetName() const override { return "txindex"; } - public: /// Constructs the index, which becomes available to be queried. explicit TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); diff --git a/src/init.cpp b/src/init.cpp index 0ca94974bb..24659de3df 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -40,11 +40,13 @@ #include <node/blockstorage.h> #include <node/caches.h> #include <node/chainstate.h> +#include <node/chainstatemanager_args.h> #include <node/context.h> #include <node/interface_ui.h> #include <node/mempool_args.h> #include <node/mempool_persist_args.h> #include <node/miner.h> +#include <node/txreconciliation.h> #include <node/validation_cache_args.h> #include <policy/feerate.h> #include <policy/fees.h> @@ -263,7 +265,7 @@ void Shutdown(NodeContext& node) // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing if (node.chainman) { LOCK(cs_main); - for (CChainState* chainstate : node.chainman->GetAll()) { + for (Chainstate* chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); } @@ -294,7 +296,7 @@ void Shutdown(NodeContext& node) if (node.chainman) { LOCK(cs_main); - for (CChainState* chainstate : node.chainman->GetAll()) { + for (Chainstate* chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); chainstate->ResetCoinsViews(); @@ -476,7 +478,6 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); // TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from // https://github.com/bitcoin/bitcoin/pull/23542 have become widespread. argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); @@ -485,6 +486,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION); @@ -553,7 +555,10 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-maxtipage=<n>", + strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", + Ticks<std::chrono::seconds>(DEFAULT_MAX_TIP_AGE)), + ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-printpriority", strprintf("Log transaction fee rate in " + CURRENCY_UNIT + "/kvB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); @@ -566,6 +571,8 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, + OptionsCategory::NODE_RELAY); argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); @@ -927,21 +934,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb init::SetLoggingCategories(args); init::SetLoggingLevel(args); - fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); - fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); - - hashAssumeValid = uint256S(args.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); - - if (args.IsArgSet("-minimumchainwork")) { - const std::string minChainWorkStr = args.GetArg("-minimumchainwork", ""); - if (!IsHexNumber(minChainWorkStr)) { - return InitError(strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), minChainWorkStr)); - } - nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr)); - } else { - nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork); - } - // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg = args.GetIntArg("-prune", 0); if (nPruneArg < 0) { @@ -992,8 +984,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb if (args.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1) return InitError(Untranslated("Unknown rpcserialversion requested.")); - nMaxTipAge = args.GetIntArg("-maxtipage", DEFAULT_MAX_TIP_AGE); - if (args.GetBoolArg("-reindex-chainstate", false)) { // indexes that must be deactivated to prevent index corruption, see #24630 if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { @@ -1041,6 +1031,16 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb } #endif // USE_SYSCALL_SANDBOX + // Also report errors from parsing before daemonization + { + ChainstateManager::Options chainman_opts_dummy{ + .chainparams = chainparams, + }; + if (const auto error{ApplyArgsManOptions(args, chainman_opts_dummy)}) { + return InitError(*error); + } + } + return true; } @@ -1143,7 +1143,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Script verification uses %d additional threads\n", script_threads); if (script_threads >= 1) { - g_parallel_script_checks = true; StartScriptCheckWorkerThreads(script_threads); } @@ -1254,6 +1253,51 @@ 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>(FeeestPath(args)); + // Check port numbers + for (const std::string port_option : { + "-port", + "-rpcport", + }) { + if (args.IsArgSet(port_option)) { + const std::string port = args.GetArg(port_option, ""); + uint16_t n; + if (!ParseUInt16(port, &n) || n == 0) { + return InitError(InvalidPortErrMsg(port_option, port)); + } + } + } + + for (const std::string port_option : { + "-i2psam", + "-onion", + "-proxy", + "-rpcbind", + "-torcontrol", + "-whitebind", + "-zmqpubhashblock", + "-zmqpubhashtx", + "-zmqpubrawblock", + "-zmqpubrawtx", + "-zmqpubsequence", + }) { + for (const std::string& socket_addr : args.GetArgs(port_option)) { + std::string host_out; + uint16_t port_out{0}; + if (!SplitHostPort(socket_addr, port_out, host_out)) { + return InitError(InvalidPortErrMsg(port_option, socket_addr)); + } + } + } + + for (const std::string& socket_addr : args.GetArgs("-bind")) { + std::string host_out; + uint16_t port_out{0}; + std::string bind_socket_addr = socket_addr.substr(0, socket_addr.rfind('=')); + if (!SplitHostPort(bind_socket_addr, port_out, host_out)) { + return InitError(InvalidPortErrMsg("-bind", socket_addr)); + } + } + // 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")) { @@ -1284,6 +1328,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } if (!args.IsArgSet("-cjdnsreachable")) { + if (args.IsArgSet("-onlynet") && IsReachable(NET_CJDNS)) { + return InitError( + _("Outbound connections restricted to CJDNS (-onlynet=cjdns) but " + "-cjdnsreachable is not provided")); + } SetReachable(NET_CJDNS, false); } // Now IsReachable(NET_CJDNS) is true if: @@ -1324,6 +1373,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) onion_proxy = addrProxy; } + const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && IsReachable(NET_ONION)}; + // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses // -noonion (or -onion=0) disables connecting to .onion entirely // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none) @@ -1331,6 +1382,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 onion_proxy = Proxy{}; + if (onlynet_used_with_onion) { + return InitError( + _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is explicitly forbidden: -onion=0")); + } } else { CService addr; if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) { @@ -1343,11 +1399,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (onion_proxy.IsValid()) { SetProxy(NET_ONION, onion_proxy); } else { - if (args.IsArgSet("-onlynet") && IsReachable(NET_ONION)) { + // If -listenonion is set, then we will (try to) connect to the Tor control port + // later from the torcontrol thread and may retrieve the onion proxy from there. + const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)}; + if (onlynet_used_with_onion && listenonion_disabled) { return InitError( _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " - "reaching the Tor network is not provided (no -proxy= and no -onion= given) or " - "it is explicitly forbidden (-onion=0)")); + "reaching the Tor network is not provided: none of -proxy, -onion or " + "-listenonion is given")); } SetReachable(NET_ONION, false); } @@ -1372,6 +1431,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindex = args.GetBoolArg("-reindex", false); bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); + ChainstateManager::Options chainman_opts{ + .chainparams = chainparams, + .adjusted_time_callback = GetAdjustedTime, + }; + Assert(!ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction // cache size calculations CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size()); @@ -1408,10 +1472,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { node.mempool = std::make_unique<CTxMemPool>(mempool_opts); - const ChainstateManager::Options chainman_opts{ - .chainparams = chainparams, - .adjusted_time_callback = GetAdjustedTime, - }; node.chainman = std::make_unique<ChainstateManager>(chainman_opts); ChainstateManager& chainman = *node.chainman; @@ -1430,7 +1490,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) }; uiInterface.InitMessage(_("Loading block index…").translated); - const int64_t load_block_index_start_time = GetTimeMillis(); + const auto load_block_index_start_time{SteadyClock::now()}; auto catch_exceptions = [](auto&& f) { try { return f(); @@ -1449,7 +1509,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);}); if (status == node::ChainstateLoadStatus::SUCCESS) { fLoaded = true; - LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time)); } } @@ -1532,7 +1592,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (fPruneMode) { if (!fReindex) { LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { uiInterface.InitMessage(_("Pruning blockstore…").translated); chainstate->PruneAndFlush(); } @@ -1647,7 +1707,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const auto BadPortWarning = [](const char* prefix, uint16_t port) { return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and " - "thus it is unlikely that any Bitcoin Core peers connect to it. See " + "thus it is unlikely that any peer will connect to it. See " "doc/p2p-bad-ports.md for details and a full list."), prefix, port); @@ -1746,6 +1806,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } SetProxy(NET_I2P, Proxy{addr}); } else { + if (args.IsArgSet("-onlynet") && IsReachable(NET_I2P)) { + return InitError( + _("Outbound connections restricted to i2p (-onlynet=i2p) but " + "-i2psam is not provided")); + } SetReachable(NET_I2P, false); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 5fc0e540a9..7a3d88b18f 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H +#include <blockfilter.h> #include <primitives/transaction.h> // For CTransactionRef #include <util/settings.h> // For util::SettingsValue @@ -143,6 +144,13 @@ public: //! or one of its ancestors. virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0; + //! Returns whether a block filter index is available. + virtual bool hasBlockFilterIndex(BlockFilterType filter_type) = 0; + + //! Returns whether any of the elements match the block via a BIP 157 block filter + //! or std::nullopt if the block filter for this block couldn't be found. + virtual std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) = 0; + //! Return whether node has the block and optionally return block metadata //! or contents. virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0; diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp index 580590fde9..ee0d4123ce 100644 --- a/src/ipc/interfaces.cpp +++ b/src/ipc/interfaces.cpp @@ -12,11 +12,11 @@ #include <tinyformat.h> #include <util/system.h> +#include <cstdio> +#include <cstdlib> #include <functional> #include <memory> #include <stdexcept> -#include <stdio.h> -#include <stdlib.h> #include <string.h> #include <string> #include <unistd.h> diff --git a/src/ipc/process.cpp b/src/ipc/process.cpp index 9036b80c45..9474ad2c4a 100644 --- a/src/ipc/process.cpp +++ b/src/ipc/process.cpp @@ -10,10 +10,10 @@ #include <util/strencodings.h> #include <cstdint> +#include <cstdlib> #include <exception> #include <iostream> #include <stdexcept> -#include <stdlib.h> #include <string.h> #include <system_error> #include <unistd.h> diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 520d0e8e75..020ae24c11 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -5,13 +5,19 @@ #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H +#include <arith_uint256.h> +#include <uint256.h> #include <util/time.h> #include <cstdint> #include <functional> +#include <optional> class CChainParams; +static constexpr bool DEFAULT_CHECKPOINTS_ENABLED{true}; +static constexpr auto DEFAULT_MAX_TIP_AGE{24h}; + namespace kernel { /** @@ -22,6 +28,14 @@ namespace kernel { struct ChainstateManagerOpts { const CChainParams& chainparams; const std::function<NodeClock::time_point()> adjusted_time_callback{nullptr}; + std::optional<bool> check_block_index{}; + bool checkpoints_enabled{DEFAULT_CHECKPOINTS_ENABLED}; + //! If set, it will override the minimum work we will assume exists on some valid chain. + std::optional<arith_uint256> minimum_chain_work; + //! If set, it will override the block hash whose ancestors we will assume to have valid scripts without checking them. + std::optional<uint256> assumed_valid_block; + //! If the tip is older than this, the node is considered to be in initial block download. + std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE}; }; } // namespace kernel diff --git a/src/kernel/mempool_limits.h b/src/kernel/mempool_limits.h index e192e7e6cd..8d4495c3cb 100644 --- a/src/kernel/mempool_limits.h +++ b/src/kernel/mempool_limits.h @@ -24,6 +24,15 @@ struct MemPoolLimits { int64_t descendant_count{DEFAULT_DESCENDANT_LIMIT}; //! The maximum allowed size in virtual bytes of an entry and its descendants within a package. int64_t descendant_size_vbytes{DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1'000}; + + /** + * @return MemPoolLimits with all the limits set to the maximum + */ + static constexpr MemPoolLimits NoLimits() + { + int64_t no_limit{std::numeric_limits<int64_t>::max()}; + return {no_limit, no_limit, no_limit, no_limit}; + } }; } // namespace kernel diff --git a/src/kernel/mempool_persist.cpp b/src/kernel/mempool_persist.cpp index 1a1cf2bbdc..a14b2e6163 100644 --- a/src/kernel/mempool_persist.cpp +++ b/src/kernel/mempool_persist.cpp @@ -37,7 +37,7 @@ namespace kernel { static const uint64_t MEMPOOL_DUMP_VERSION = 1; -bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, CChainState& active_chainstate, FopenFn mockable_fopen_function) +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, FopenFn mockable_fopen_function) { if (load_path.empty()) return false; diff --git a/src/kernel/mempool_persist.h b/src/kernel/mempool_persist.h index 9a15ec6dca..ca4917e38b 100644 --- a/src/kernel/mempool_persist.h +++ b/src/kernel/mempool_persist.h @@ -7,7 +7,7 @@ #include <fs.h> -class CChainState; +class Chainstate; class CTxMemPool; namespace kernel { @@ -19,7 +19,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, /** Load the mempool from disk. */ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, - CChainState& active_chainstate, + Chainstate& active_chainstate, fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen); } // namespace kernel diff --git a/src/leveldb/util/env_windows.cc b/src/leveldb/util/env_windows.cc index 4dcba222a1..aafcdcc3be 100644 --- a/src/leveldb/util/env_windows.cc +++ b/src/leveldb/util/env_windows.cc @@ -194,7 +194,7 @@ class WindowsRandomAccessFile : public RandomAccessFile { Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { DWORD bytes_read = 0; - OVERLAPPED overlapped = {0}; + OVERLAPPED overlapped = {}; overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); overlapped.Offset = static_cast<DWORD>(offset); diff --git a/src/logging.cpp b/src/logging.cpp index a3f1d39be5..ed0c2a56a5 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -180,6 +180,8 @@ const CLogCategoryDesc LogCategories[] = #endif {BCLog::UTIL, "util"}, {BCLog::BLOCKSTORE, "blockstorage"}, + {BCLog::TXRECONCILIATION, "txreconciliation"}, + {BCLog::SCAN, "scan"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; @@ -280,6 +282,10 @@ std::string LogCategoryToStr(BCLog::LogFlags category) return "util"; case BCLog::LogFlags::BLOCKSTORE: return "blockstorage"; + case BCLog::LogFlags::TXRECONCILIATION: + return "txreconciliation"; + case BCLog::LogFlags::SCAN: + return "scan"; case BCLog::LogFlags::ALL: return "all"; } diff --git a/src/logging.h b/src/logging.h index fe91ee43a5..14a0f08f8d 100644 --- a/src/logging.h +++ b/src/logging.h @@ -66,6 +66,8 @@ namespace BCLog { #endif UTIL = (1 << 25), BLOCKSTORE = (1 << 26), + TXRECONCILIATION = (1 << 27), + SCAN = (1 << 28), ALL = ~(uint32_t)0, }; enum class Level { diff --git a/src/memusage.h b/src/memusage.h index a6e894129a..fd85a7956c 100644 --- a/src/memusage.h +++ b/src/memusage.h @@ -8,9 +8,8 @@ #include <indirectmap.h> #include <prevector.h> -#include <stdlib.h> - #include <cassert> +#include <cstdlib> #include <map> #include <memory> #include <set> diff --git a/src/net.cpp b/src/net.cpp index d1df393ad3..3c28b9eddf 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -91,13 +91,12 @@ static constexpr auto FEELER_SLEEP_WINDOW{1s}; /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, - BF_EXPLICIT = (1U << 0), - BF_REPORT_ERROR = (1U << 1), + BF_REPORT_ERROR = (1U << 0), /** * Do not call AddLocal() for our special addresses, e.g., for incoming * Tor connections, to prevent gossiping them over the network. */ - BF_DONT_ADVERTISE = (1U << 2), + BF_DONT_ADVERTISE = (1U << 1), }; // The set of sockets cannot be modified while waiting @@ -971,8 +970,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, return; } - if (!IsSelectableSocket(sock->Get())) - { + if (!sock->IsSelectable()) { LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); return; } @@ -1500,12 +1498,12 @@ void CConnman::ThreadDNSAddressSeed() void CConnman::DumpAddresses() { - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; DumpPeerAddresses(::gArgs, addrman); LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n", - addrman.size(), GetTimeMillis() - nStart); + addrman.size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } void CConnman::ProcessAddrFetch() @@ -1648,6 +1646,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) if (add_fixed_seeds_now) { std::vector<CAddress> seed_addrs{ConvertSeeds(Params().FixedSeeds())}; + // We will not make outgoing connections to peers that are unreachable + // (e.g. because of -onlynet configuration). + // Therefore, we do not add them to addrman in the first place. + // Note that if you change -onlynet setting from one network to another, + // peers.dat will contain only peers of unreachable networks and + // manual intervention will be needed (either delete peers.dat after + // configuration change or manually add some reachable peer using addnode), + // see <https://github.com/bitcoin/bitcoin/issues/26035> for details. seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(), [](const CAddress& addr) { return !IsReachable(addr); }), seed_addrs.end()); @@ -1983,8 +1989,12 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } } +Mutex NetEventsInterface::g_msgproc_mutex; + void CConnman::ThreadMessageHandler() { + LOCK(NetEventsInterface::g_msgproc_mutex); + SetSyscallSandboxPolicy(SyscallSandboxPolicy::MESSAGE_HANDLER); while (!flagInterruptMsgProc) { @@ -2006,10 +2016,7 @@ void CConnman::ThreadMessageHandler() if (flagInterruptMsgProc) return; // Send messages - { - LOCK(pnode->cs_sendProcessing); - m_msgproc->SendMessages(pnode); - } + m_msgproc->SendMessages(pnode); if (flagInterruptMsgProc) return; @@ -2222,9 +2229,6 @@ bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlag { const CService addr{MaybeFlipIPv6toCJDNS(addr_)}; - if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) { - return false; - } bilingual_str strError; if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && m_client_interface) { @@ -2244,13 +2248,13 @@ bool CConnman::InitBinds(const Options& options) { bool fBound = false; for (const auto& addrBind : options.vBinds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::None); + fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None); } for (const auto& addrBind : options.vWhiteBinds) { - fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); + fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags); } for (const auto& addr_bind : options.onion_binds) { - fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None); + fBound |= Bind(addr_bind, BF_DONT_ADVERTISE, NetPermissionFlags::None); } if (options.bind_on_any) { struct in_addr inaddr_any; @@ -164,6 +164,7 @@ bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); CService GetLocalAddress(const CNetAddr& addrPeer); +CService MaybeFlipIPv6toCJDNS(const CService& service); extern bool fDiscover; @@ -377,8 +378,6 @@ public: std::list<CNetMessage> vProcessMsg GUARDED_BY(cs_vProcessMsg); size_t nProcessQueueSize GUARDED_BY(cs_vProcessMsg){0}; - RecursiveMutex cs_sendProcessing; - uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; std::atomic<std::chrono::seconds> m_last_send{0s}; @@ -629,6 +628,9 @@ private: class NetEventsInterface { public: + /** Mutex for anything that is only accessed via the msg processing thread */ + static Mutex g_msgproc_mutex; + /** Initialize a peer (setup state, queue any initial messages) */ virtual void InitializeNode(CNode& node, ServiceFlags our_services) = 0; @@ -642,7 +644,7 @@ public: * @param[in] interrupt Interrupt condition for processing threads * @return True if there is more work to be done */ - virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; + virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0; /** * Send queued protocol messages to a given node. @@ -650,7 +652,7 @@ public: * @param[in] pnode The node which we are sending messages to. * @return True if there is more work to be done */ - virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0; + virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0; protected: diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 74700580ad..363f2fde71 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -20,6 +20,7 @@ #include <netbase.h> #include <netmessagemaker.h> #include <node/blockstorage.h> +#include <node/txreconciliation.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/settings.h> @@ -264,19 +265,14 @@ struct Peer { /** The feerate in the most recent BIP133 `feefilter` message sent to the peer. * It is *not* a p2p protocol violation for the peer to send us * transactions with a lower fee rate than this. See BIP133. */ - CAmount m_fee_filter_sent{0}; + CAmount m_fee_filter_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; /** Timestamp after which we will send the next BIP133 `feefilter` message * to the peer. */ - std::chrono::microseconds m_next_send_feefilter{0}; + std::chrono::microseconds m_next_send_feefilter GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; struct TxRelay { mutable RecursiveMutex m_bloom_filter_mutex; - /** Whether the peer wishes to receive transaction announcements. - * - * This is initially set based on the fRelay flag in the received - * `version` message. If initially set to false, it can only be flipped - * to true if we have offered the peer NODE_BLOOM services and it sends - * us a `filterload` or `filterclear` message. See BIP37. */ + /** Whether we relay transactions to this peer. */ bool m_relay_txs GUARDED_BY(m_bloom_filter_mutex){false}; /** A bloom filter for which transactions to announce to the peer. See BIP37. */ std::unique_ptr<CBloomFilter> m_bloom_filter PT_GUARDED_BY(m_bloom_filter_mutex) GUARDED_BY(m_bloom_filter_mutex){nullptr}; @@ -298,7 +294,7 @@ struct Peer { std::atomic<std::chrono::seconds> m_last_mempool_req{0s}; /** The next time after which we will send an `inv` message containing * transaction announcements to this peer. */ - std::chrono::microseconds m_next_inv_send_time{0}; + std::chrono::microseconds m_next_inv_send_time GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ std::atomic<CAmount> m_fee_filter_received{0}; @@ -319,7 +315,7 @@ struct Peer { }; /** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */ - std::vector<CAddress> m_addrs_to_send; + std::vector<CAddress> m_addrs_to_send GUARDED_BY(NetEventsInterface::g_msgproc_mutex); /** Probabilistic filter to track recent addr messages relayed with this * peer. Used to avoid relaying redundant addresses to this peer. * @@ -329,7 +325,7 @@ struct Peer { * * Presence of this filter must correlate with m_addr_relay_enabled. **/ - std::unique_ptr<CRollingBloomFilter> m_addr_known; + std::unique_ptr<CRollingBloomFilter> m_addr_known GUARDED_BY(NetEventsInterface::g_msgproc_mutex); /** Whether we are participating in address relay with this connection. * * We set this bool to true for outbound peers (other than @@ -346,7 +342,7 @@ struct Peer { * initialized.*/ std::atomic_bool m_addr_relay_enabled{false}; /** Whether a getaddr request to this peer is outstanding. */ - bool m_getaddr_sent{false}; + bool m_getaddr_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; /** Guards address sending timers. */ mutable Mutex m_addr_send_times_mutex; /** Time point to send the next ADDR message to this peer. */ @@ -357,12 +353,12 @@ struct Peer { * messages, indicating a preference to receive ADDRv2 instead of ADDR ones. */ std::atomic_bool m_wants_addrv2{false}; /** Whether this peer has already sent us a getaddr message. */ - bool m_getaddr_recvd{false}; + bool m_getaddr_recvd GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; /** Number of addresses that can be processed from this peer. Start at 1 to * permit self-announcement. */ - double m_addr_token_bucket{1.0}; + double m_addr_token_bucket GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1.0}; /** When m_addr_token_bucket was last updated */ - std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()}; + std::chrono::microseconds m_addr_token_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){GetTime<std::chrono::microseconds>()}; /** Total number of addresses that were dropped due to rate limiting. */ std::atomic<uint64_t> m_addr_rate_limited{0}; /** Total number of addresses that were processed (excludes rate-limited ones). */ @@ -372,7 +368,7 @@ struct Peer { std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans); /** Whether we've sent this peer a getheaders in response to an inv prior to initial-headers-sync completing */ - bool m_inv_triggered_getheaders_before_sync{false}; + bool m_inv_triggered_getheaders_before_sync GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; /** Protects m_getdata_requests **/ Mutex m_getdata_requests_mutex; @@ -380,7 +376,7 @@ struct Peer { std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); /** Time of the last getheaders message to this peer */ - NodeClock::time_point m_last_getheaders_timestamp{}; + NodeClock::time_point m_last_getheaders_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){}; /** Protects m_headers_sync **/ Mutex m_headers_sync_mutex; @@ -515,9 +511,9 @@ public: void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex); bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex); - bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); + bool SendMessages(CNode* pto) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, g_msgproc_mutex); /** Implement PeerManager */ void StartScheduledTasks(CScheduler& scheduler) override; @@ -532,12 +528,12 @@ public: void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); }; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); 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 */ - void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_msgproc_mutex); /** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */ void EvictExtraOutboundPeers(std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -601,7 +597,7 @@ private: void ProcessHeadersMessage(CNode& pfrom, Peer& peer, std::vector<CBlockHeader>&& headers, bool via_compact_block) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex); /** Various helpers for headers processing, invoked by ProcessHeadersMessage() */ /** Return true if headers are continuous and have valid proof-of-work (DoS points assigned on failure) */ bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer); @@ -610,7 +606,7 @@ private: /** Deal with state tracking and headers sync for peers that send the * occasional non-connecting header (this can happen due to BIP 130 headers * announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */ - void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers); + void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Return true if the headers connect to each other, false otherwise */ bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const; /** Try to continue a low-work headers sync that has already begun. @@ -633,7 +629,7 @@ private: */ bool IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom, std::vector<CBlockHeader>& headers) - EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex, !m_headers_presync_mutex); + EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex, !m_headers_presync_mutex, g_msgproc_mutex); /** Check work on a headers chain to be processed, and if insufficient, * initiate our anti-DoS headers sync mechanism. * @@ -649,7 +645,7 @@ private: bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlockIndex* chain_start_header, std::vector<CBlockHeader>& headers) - EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex, !m_headers_presync_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex); /** Return true if the given header is an ancestor of * m_chainman.m_best_header or our current tip */ @@ -659,7 +655,7 @@ private: * We don't issue a getheaders message if we have a recent one outstanding. * This returns true if a getheaders is actually sent, and false otherwise. */ - bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer); + bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Potentially fetch blocks from this peer upon receipt of a new headers tip */ void HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast); /** Update peer state based on received headers message */ @@ -683,10 +679,10 @@ private: void MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::microseconds now); /** Send `addr` messages on a regular schedule. */ - void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time); + void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Send a single `sendheaders` message, after we have completed headers sync with a peer. */ - void MaybeSendSendHeaders(CNode& node, Peer& peer); + void MaybeSendSendHeaders(CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Relay (gossip) an address to a few randomly chosen nodes. * @@ -695,10 +691,10 @@ private: * @param[in] fReachable Whether the address' network is reachable. We relay unreachable * addresses less. */ - void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex); /** Send `feefilter` message. */ - void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time); + void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); const CChainParams& m_chainparams; CConnman& m_connman; @@ -708,6 +704,7 @@ private: ChainstateManager& m_chainman; CTxMemPool& m_mempool; TxRequestTracker m_txrequest GUARDED_BY(::cs_main); + std::unique_ptr<TxReconciliationTracker> m_txreconciliation; /** The height of the best chain */ std::atomic<int> m_best_height{-1}; @@ -751,7 +748,7 @@ private: int nSyncStarted GUARDED_BY(cs_main) = 0; /** Hash of the last block we received via INV */ - uint256 m_last_block_inv_triggering_headers_sync{}; + uint256 m_last_block_inv_triggering_headers_sync GUARDED_BY(g_msgproc_mutex){}; /** * Sources of received blocks, saved to be able punish them when processing @@ -863,7 +860,7 @@ private: std::atomic_bool m_headers_presync_should_signal{false}; /** Height of the highest block announced using BIP 152 high-bandwidth mode. */ - int m_highest_fast_announce{0}; + int m_highest_fast_announce GUARDED_BY(::cs_main){0}; /** Have we requested this block from a peer */ bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -1010,7 +1007,10 @@ private: * @return True if address relay is enabled with peer * False if address relay is disallowed */ - bool SetupAddressRelay(const CNode& node, Peer& peer); + bool SetupAddressRelay(const CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + + void AddAddressKnown(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); }; const CNodeState* PeerManagerImpl::State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1036,13 +1036,13 @@ static bool IsAddrCompatible(const Peer& peer, const CAddress& addr) return peer.m_wants_addrv2 || addr.IsAddrV1Compatible(); } -static void AddAddressKnown(Peer& peer, const CAddress& addr) +void PeerManagerImpl::AddAddressKnown(Peer& peer, const CAddress& addr) { assert(peer.m_addr_known); peer.m_addr_known->insert(addr.GetKey()); } -static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) +void PeerManagerImpl::PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) { // Known checking here is only to save space from duplicates. // Before sending, we'll filter it again for known addresses that were @@ -1291,7 +1291,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(peer.m_id); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { // This peer has nothing interesting. return; } @@ -1380,7 +1380,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) CService addr_you = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? addr : CService(); uint64_t your_services{addr.nServices}; - const bool tx_relay = !m_ignore_incoming_txs && !pnode.IsBlockOnlyConn() && !pnode.IsFeelerConn(); + const bool tx_relay{!RejectIncomingTxs(pnode)}; m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, my_services, nTime, your_services, addr_you, // Together the pre-version-31402 serialization of CAddress "addrYou" (without nTime) my_services, CService(), // Together the pre-version-31402 serialization of CAddress "addrMe" (without nTime) @@ -1494,6 +1494,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) } WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); + if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid); m_num_preferred_download_peers -= state->fPreferredDownload; m_peers_downloading_from -= (state->nBlocksInFlight != 0); assert(m_peers_downloading_from >= 0); @@ -1778,6 +1779,11 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman, m_mempool(pool), m_ignore_incoming_txs(ignore_incoming_txs) { + // While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation. + // This argument can go away after Erlay support is complete. + if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) { + m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION); + } } void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler) @@ -2386,7 +2392,7 @@ arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold() // near our tip. near_chaintip_work = tip->nChainWork - std::min<arith_uint256>(144*GetBlockProof(*tip), tip->nChainWork); } - return std::max(near_chaintip_work, arith_uint256(nMinimumChainWork)); + return std::max(near_chaintip_work, m_chainman.MinimumChainWork()); } /** @@ -2559,14 +2565,22 @@ bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlo // Now a HeadersSyncState object for tracking this synchronization is created, // process the headers using it as normal. - return IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers); + if (!IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers)) { + // Something went wrong, reset the headers sync. + peer.m_headers_sync.reset(nullptr); + LOCK(m_headers_presync_mutex); + m_headers_presync_stats.erase(peer.m_id); + } } else { LogPrint(BCLog::NET, "Ignoring low-work chain (height=%u) from peer=%d\n", chain_start_header->nHeight + headers.size(), pfrom.GetId()); - // Since this is a low-work headers chain, no further processing is required. - headers = {}; - return true; } + + // The peer has not yet given us a chain that meets our work threshold, + // so we want to prevent further processing of the headers in any case. + headers = {}; + return true; } + return false; } @@ -2696,14 +2710,14 @@ void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) { // If the peer has no more headers to give us, then we know we have // their tip. - if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { // This peer has too little work on their headers chain to help // us sync -- disconnect if it is an outbound disconnection // candidate. - // Note: We compare their tip to nMinimumChainWork (rather than + // Note: We compare their tip to the minumum chain work (rather than // m_chainman.ActiveChain().Tip()) because we won't start block download // until we have a headers chain that has at least - // nMinimumChainWork, even if a peer has a chain past our tip, + // the minimum chain work, even if a peer has a chain past our tip, // as an anti-DoS measure. if (pfrom.IsOutboundOrBlockRelayConn()) { LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId()); @@ -2843,7 +2857,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, // If we don't have the last header, then this peer will have given us // something new (if these headers are valid). - bool received_new_header{last_received_header != nullptr}; + bool received_new_header{last_received_header == nullptr}; // Now process all the headers. BlockValidationState state; @@ -3135,6 +3149,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) { + AssertLockHeld(g_msgproc_mutex); + LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom.GetId()); PeerRef peer = GetPeerRef(pfrom.GetId()); @@ -3236,8 +3252,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2)); } - m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK)); - pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices); peer->m_their_services = nServices; pfrom.SetAddrLocal(addrMe); @@ -3247,10 +3261,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } peer->m_starting_height = starting_height; - // We only initialize the m_tx_relay data structure if: - // - this isn't an outbound block-relay-only connection; and - // - fRelay=true or we're offering NODE_BLOOM to this peer - // (NODE_BLOOM means that the peer may turn on tx relay later) + // We only initialize the Peer::TxRelay m_relay_txs data structure if: + // - this isn't an outbound block-relay-only connection, and + // - fRelay=true (the peer wishes to receive transaction announcements) + // or we're offering NODE_BLOOM to this peer. NODE_BLOOM means that + // the peer may turn on transaction relay later. if (!pfrom.IsBlockOnlyConn() && (fRelay || (peer->m_our_services & NODE_BLOOM))) { auto* const tx_relay = peer->SetTxRelay(); @@ -3261,6 +3276,25 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (fRelay) pfrom.m_relays_txs = true; } + if (greatest_common_version >= WTXID_RELAY_VERSION && m_txreconciliation) { + // Per BIP-330, we announce txreconciliation support if: + // - protocol version per the VERSION message supports WTXID_RELAY; + // - we intended to exchange transactions over this connection while establishing it + // and the peer indicated support for transaction relay in the VERSION message; + // - we are not in -blocksonly mode. + if (pfrom.m_relays_txs && !m_ignore_incoming_txs) { + const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId()); + // We suggest our txreconciliation role (initiator/responder) based on + // the connection direction. + m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL, + !pfrom.IsInboundConn(), + pfrom.IsInboundConn(), + TXRECONCILIATION_VERSION, recon_salt)); + } + } + + m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK)); + // Potentially mark this peer as a preferred download peer. { LOCK(cs_main); @@ -3388,6 +3422,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // they may wish to request compact blocks from us m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION)); } + + if (m_txreconciliation) { + if (!peer->m_wtxid_relay || !m_txreconciliation->IsPeerRegistered(pfrom.GetId())) { + // We could have optimistically pre-registered/registered the peer. In that case, + // we should forget about the reconciliation state here if this wasn't followed + // by WTXIDRELAY (since WTXIDRELAY can't be announced later). + m_txreconciliation->ForgetPeer(pfrom.GetId()); + } + } + pfrom.fSuccessfullyConnected = true; return; } @@ -3451,6 +3495,58 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + // Received from a peer demonstrating readiness to announce transactions via reconciliations. + // This feature negotiation must happen between VERSION and VERACK to avoid relay problems + // from switching announcement protocols after the connection is up. + if (msg_type == NetMsgType::SENDTXRCNCL) { + if (!m_txreconciliation) { + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl from peer=%d ignored, as our node does not have txreconciliation enabled\n", pfrom.GetId()); + return; + } + + if (pfrom.fSuccessfullyConnected) { + // Disconnect peers that send a SENDTXRCNCL message after VERACK. + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId()); + pfrom.fDisconnect = true; + return; + } + + if (!peer->GetTxRelay()) { + // Disconnect peers that send a SENDTXRCNCL message even though we indicated we don't + // support transaction relay. + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId()); + pfrom.fDisconnect = true; + return; + } + + bool is_peer_initiator, is_peer_responder; + uint32_t peer_txreconcl_version; + uint64_t remote_salt; + vRecv >> is_peer_initiator >> is_peer_responder >> peer_txreconcl_version >> remote_salt; + + if (m_txreconciliation->IsPeerRegistered(pfrom.GetId())) { + // A peer is already registered, meaning we already received SENDTXRCNCL from them. + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId()); + pfrom.fDisconnect = true; + return; + } + + const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(), + is_peer_initiator, is_peer_responder, + peer_txreconcl_version, + remote_salt); + + // If it's a protocol violation, disconnect. + // If the peer was not found (but something unexpected happened) or it was registered, + // nothing to be done. + if (result == ReconciliationRegisterResult::PROTOCOL_VIOLATION) { + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId()); + pfrom.fDisconnect = true; + return; + } + return; + } + if (!pfrom.fSuccessfullyConnected) { LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId()); return; @@ -3805,12 +3901,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Note that if we were to be on a chain that forks from the checkpointed // chain, then serving those headers to a peer that has seen the // checkpointed chain would cause that peer to disconnect us. Requiring - // that our chainwork exceed nMinimumChainWork is a protection against + // that our chainwork exceed the mimimum chain work is a protection against // being fed a bogus chain when we started up for the first time and // getting partitioned off the honest network for serving that chain to // others. if (m_chainman.ActiveTip() == nullptr || - (m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) { + (m_chainman.ActiveTip()->nChainWork < m_chainman.MinimumChainWork() && !pfrom.HasPermission(NetPermissionFlags::Download))) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n", pfrom.GetId()); // Just respond with an empty headers message, to tell the peer to // go away but not treat us as unresponsive. @@ -4274,7 +4370,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // (eg disk space). Because we only try to reconstruct blocks when // we're close to caught up (via the CanDirectFetch() requirement // above, combined with the behavior of not requesting blocks until - // we have a chain with at least nMinimumChainWork), and we ignore + // we have a chain with at least the minimum chain work), and we ignore // compact blocks with less work than our tip, it is safe to treat // reconstructed compact blocks as having been requested. ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true); @@ -4748,6 +4844,8 @@ bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer) bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc) { + AssertLockHeld(g_msgproc_mutex); + bool fMoreWork = false; PeerRef peer = GetPeerRef(pfrom->GetId()); @@ -5099,7 +5197,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros // Remove addr records that the peer already knows about, and add new // addrs to the m_addr_known filter on the same pass. - auto addr_already_known = [&peer](const CAddress& addr) { + auto addr_already_known = [&peer](const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) { bool ret = peer.m_addr_known->contains(addr.GetKey()); if (!ret) peer.m_addr_known->insert(addr.GetKey()); return ret; @@ -5138,7 +5236,7 @@ void PeerManagerImpl::MaybeSendSendHeaders(CNode& node, Peer& peer) LOCK(cs_main); CNodeState &state = *State(node.GetId()); if (state.pindexBestKnownBlock != nullptr && - state.pindexBestKnownBlock->nChainWork > nMinimumChainWork) { + state.pindexBestKnownBlock->nChainWork > m_chainman.MinimumChainWork()) { // Tell our peer we prefer to receive headers rather than inv's // We send this to non-NODE NETWORK peers as well, because even // non-NODE NETWORK peers can announce blocks (such as pruning @@ -5217,6 +5315,7 @@ bool PeerManagerImpl::RejectIncomingTxs(const CNode& peer) const { // block-relay-only peers may never send txs to us if (peer.IsBlockOnlyConn()) return true; + if (peer.IsFeelerConn()) return true; // In -blocksonly mode, peers need the 'relay' permission to send txs to us if (m_ignore_incoming_txs && !peer.HasPermission(NetPermissionFlags::Relay)) return true; return false; @@ -5240,6 +5339,8 @@ bool PeerManagerImpl::SetupAddressRelay(const CNode& node, Peer& peer) bool PeerManagerImpl::SendMessages(CNode* pto) { + AssertLockHeld(g_msgproc_mutex); + PeerRef peer = GetPeerRef(pto->GetId()); if (!peer) return false; const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); diff --git a/src/net_processing.h b/src/net_processing.h index 0a882b1e53..0d0842fa8e 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -84,7 +84,7 @@ 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; + const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 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; diff --git a/src/netbase.cpp b/src/netbase.cpp index a5f7bda875..8169b40ea6 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -304,8 +304,7 @@ enum class IntrRecvError { * read. * * @see This function can be interrupted by calling InterruptSocks5(bool). - * Sockets can be made non-blocking with SetSocketNonBlocking(const - * SOCKET&). + * Sockets can be made non-blocking with Sock::SetNonBlocking(). */ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const Sock& sock) { @@ -503,7 +502,7 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) // Ensure that waiting for I/O on this socket won't result in undefined // behavior. - if (!IsSelectableSocket(sock->Get())) { + if (!sock->IsSelectable()) { LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n"); return nullptr; } @@ -525,7 +524,7 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) } // Set the non-blocking option on the socket. - if (!SetSocketNonBlocking(sock->Get())) { + if (!sock->SetNonBlocking()) { LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError())); return nullptr; } @@ -717,21 +716,6 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out) return false; } -bool SetSocketNonBlocking(const SOCKET& hSocket) -{ -#ifdef WIN32 - u_long nOne = 1; - if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { -#else - int fFlags = fcntl(hSocket, F_GETFL, 0); - if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { -#endif - return false; - } - - return true; -} - void InterruptSocks5(bool interrupt) { interruptSocks5Recv = interrupt; diff --git a/src/netbase.h b/src/netbase.h index fadc8b418e..f7816f5d1d 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -221,8 +221,6 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT */ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); -/** Enable non-blocking mode for a socket */ -bool SetSocketNonBlocking(const SOCKET& hSocket); void InterruptSocks5(bool interrupt); /** diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 42b6b017fe..04d46f4361 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -524,6 +524,16 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize) void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo) { LOCK(cs_LastBlockFile); + + if (m_blockfile_info.size() < 1) { + // Return if we haven't loaded any blockfiles yet. This happens during + // chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which + // then calls FlushStateToDisk()), resulting in a call to this function before we + // have populated `m_blockfile_info` via LoadBlockIndexDB(). + return; + } + assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile); + FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize); if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) { AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error."); @@ -789,19 +799,24 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c return true; } -/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp) { unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION); FlatFilePos blockPos; - if (dbp != nullptr) { + const auto position_known {dbp != nullptr}; + if (position_known) { blockPos = *dbp; + } else { + // when known, blockPos.nPos points at the offset of the block data in the blk file. that already accounts for + // the serialization header present in the file (the 4 magic message start bytes + the 4 length bytes = 8 bytes = BLOCK_SERIALIZATION_HEADER_SIZE). + // we add BLOCK_SERIALIZATION_HEADER_SIZE only for new blocks since they will have the serialization header added when written to disk. + nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE); } - if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) { + if (!FindBlockPos(blockPos, nBlockSize, nHeight, active_chain, block.GetBlockTime(), position_known)) { error("%s: FindBlockPos failed", __func__); return FlatFilePos(); } - if (dbp == nullptr) { + if (!position_known) { if (!WriteBlockToDisk(block, blockPos, chainparams.MessageStart())) { AbortNode("Failed to write block"); return FlatFilePos(); @@ -882,7 +897,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile // We can't hold cs_main during ActivateBestChain even though we're accessing // the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve // the relevant pointers before the ABC call. - for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { + for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(state, nullptr)) { LogPrintf("Failed to connect best block (%s)\n", state.ToString()); diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 9b76371aae..29501c1959 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -26,7 +26,7 @@ class CBlockFileInfo; class CBlockUndo; class CChain; class CChainParams; -class CChainState; +class Chainstate; class ChainstateManager; struct CCheckpointData; struct FlatFilePos; @@ -44,6 +44,9 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB /** The maximum size of a blk?????.dat file (since 0.8) */ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB +/** Size of header written by WriteBlockToDisk before a serialized CBlock */ +static constexpr size_t BLOCK_SERIALIZATION_HEADER_SIZE = CMessageHeader::MESSAGE_START_SIZE + sizeof(unsigned int); + extern std::atomic_bool fImporting; extern std::atomic_bool fReindex; /** Pruning-related variables and constants */ @@ -75,12 +78,12 @@ struct PruneLockInfo { * Maintains a tree of blocks (stored in `m_block_index`) which is consulted * to determine where the most-work tip is. * - * This data is used mostly in `CChainState` - information about, e.g., + * This data is used mostly in `Chainstate` - information about, e.g., * candidate tips is not maintained here. */ class BlockManager { - friend CChainState; + friend Chainstate; friend ChainstateManager; private: @@ -171,6 +174,7 @@ public: bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** Store block on disk. If dbp is not nullptr, then it provides the known position of the block within a block file on disk. */ FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp); /** Calculate the amount of disk space the block & undo files currently use */ diff --git a/src/node/caches.cpp b/src/node/caches.cpp index f168332ee6..a39ad7aeb6 100644 --- a/src/node/caches.cpp +++ b/src/node/caches.cpp @@ -4,9 +4,9 @@ #include <node/caches.h> +#include <index/txindex.h> #include <txdb.h> #include <util/system.h> -#include <validation.h> namespace node { CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes) diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index c4dd9ba6c5..60acb614b4 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -4,9 +4,11 @@ #include <node/chainstate.h> +#include <arith_uint256.h> #include <chain.h> #include <coins.h> #include <consensus/params.h> +#include <logging.h> #include <node/blockstorage.h> #include <node/caches.h> #include <sync.h> @@ -21,6 +23,7 @@ #include <algorithm> #include <atomic> #include <cassert> +#include <limits> #include <memory> #include <vector> @@ -28,17 +31,17 @@ namespace node { ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, const ChainstateLoadOptions& options) { - auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; - if (!hashAssumeValid.IsNull()) { - LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); + if (!chainman.AssumedValidBlock().IsNull()) { + LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex()); } else { LogPrintf("Validating signatures for all blocks.\n"); } - LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex()); - if (nMinimumChainWork < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) { + LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex()); + if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) { LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex()); } if (nPruneTarget == std::numeric_limits<uint64_t>::max()) { @@ -48,10 +51,15 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize } LOCK(cs_main); - chainman.InitializeChainstate(options.mempool); chainman.m_total_coinstip_cache = cache_sizes.coins; chainman.m_total_coinsdb_cache = cache_sizes.coins_db; + // Load the fully validated chainstate. + chainman.InitializeChainstate(options.mempool); + + // Load a chain created from a UTXO snapshot, if any exist. + chainman.DetectSnapshotChainstate(options.mempool); + 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: @@ -98,12 +106,20 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; } + // Conservative value which is arbitrarily chosen, as it will ultimately be changed + // by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure + // that the sum of the two caches (40%) does not exceed the allowable amount + // during this temporary initialization state. + double init_cache_fraction = 0.2; + // At this point we're either in reindex or we've loaded a useful // block tree into BlockIndex()! - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { + LogPrintf("Initializing chainstate %s\n", chainstate->ToString()); + chainstate->InitCoinsDB( - /*cache_size_bytes=*/cache_sizes.coins_db, + /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction, /*in_memory=*/options.coins_db_in_memory, /*should_wipe=*/options.reindex || options.reindex_chainstate); @@ -125,7 +141,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize } // The on-disk coinsdb is now in a good state, create the cache - chainstate->InitCoinsCache(cache_sizes.coins); + chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction); assert(chainstate->CanFlushToDisk()); if (!is_coinsview_empty(chainstate)) { @@ -140,24 +156,29 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize if (!options.reindex) { auto chainstates{chainman.GetAll()}; if (std::any_of(chainstates.begin(), chainstates.end(), - [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { + [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), chainman.GetConsensus().SegwitHeight)}; }; } + // Now that chainstates are loaded and we're able to flush to + // disk, rebalance the coins caches to desired levels based + // on the condition of each chainstate. + chainman.MaybeRebalanceCaches(); + return {ChainstateLoadStatus::SUCCESS, {}}; } ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options) { - auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp new file mode 100644 index 0000000000..b0d929626b --- /dev/null +++ b/src/node/chainstatemanager_args.cpp @@ -0,0 +1,39 @@ +// 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 <node/chainstatemanager_args.h> + +#include <arith_uint256.h> +#include <tinyformat.h> +#include <uint256.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> +#include <validation.h> + +#include <chrono> +#include <optional> +#include <string> + +namespace node { +std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts) +{ + if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value; + + if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value; + + if (auto value{args.GetArg("-minimumchainwork")}) { + if (!IsHexNumber(*value)) { + return strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value); + } + opts.minimum_chain_work = UintToArith256(uint256S(*value)); + } + + if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value); + + if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value}; + + return std::nullopt; +} +} // namespace node diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h new file mode 100644 index 0000000000..6c46b998f2 --- /dev/null +++ b/src/node/chainstatemanager_args.h @@ -0,0 +1,19 @@ +// 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. + +#ifndef BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H +#define BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H + +#include <validation.h> + +#include <optional> + +class ArgsManager; +struct bilingual_str; + +namespace node { +std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts); +} // namespace node + +#endif // BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index aa7ddec770..979c625463 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -4,10 +4,12 @@ #include <addrdb.h> #include <banman.h> +#include <blockfilter.h> #include <chain.h> #include <chainparams.h> #include <deploymentstatus.h> #include <external_signer.h> +#include <index/blockfilterindex.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/handler.h> @@ -536,6 +538,20 @@ public: } return std::nullopt; } + bool hasBlockFilterIndex(BlockFilterType filter_type) override + { + return GetBlockFilterIndex(filter_type) != nullptr; + } + std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) override + { + const BlockFilterIndex* block_filter_index{GetBlockFilterIndex(filter_type)}; + if (!block_filter_index) return std::nullopt; + + BlockFilter filter; + const CBlockIndex* index{WITH_LOCK(::cs_main, return chainman().m_blockman.LookupBlockIndex(block_hash))}; + if (index == nullptr || !block_filter_index->LookupFilter(index, filter)) return std::nullopt; + return filter.GetFilter().MatchAny(filter_set); + } bool findBlock(const uint256& hash, const FoundBlock& block) override { WAIT_LOCK(cs_main, lock); @@ -660,8 +676,7 @@ public: std::string unused_error_string; LOCK(m_node.mempool->cs); return m_node.mempool->CalculateMemPoolAncestors( - entry, ancestors, limits.ancestor_count, limits.ancestor_size_vbytes, - limits.descendant_count, limits.descendant_size_vbytes, unused_error_string); + entry, ancestors, limits, unused_error_string); } CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override { diff --git a/src/node/miner.cpp b/src/node/miner.cpp index f04742deeb..e11ec5b0f1 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -62,7 +62,7 @@ BlockAssembler::Options::Options() nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options) +BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options) : chainparams{chainstate.m_chainman.GetParams()}, m_mempool(mempool), m_chainstate(chainstate) @@ -87,7 +87,7 @@ static BlockAssembler::Options DefaultOptions() return options; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool) +BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool) : BlockAssembler(chainstate, mempool, DefaultOptions()) {} void BlockAssembler::resetBlock() @@ -105,7 +105,7 @@ void BlockAssembler::resetBlock() std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) { - int64_t nTimeStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; resetBlock(); @@ -143,7 +143,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc addPackageTxs(*m_mempool, nPackagesSelected, nDescendantsUpdated); } - int64_t nTime1 = GetTimeMicros(); + const auto time_1{SteadyClock::now()}; m_last_block_num_txs = nBlockTx; m_last_block_weight = nBlockWeight; @@ -173,9 +173,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } - int64_t nTime2 = GetTimeMicros(); + const auto time_2{SteadyClock::now()}; - LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart)); + LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", + Ticks<MillisecondsDouble>(time_1 - time_start), nPackagesSelected, nDescendantsUpdated, + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<MillisecondsDouble>(time_2 - time_start)); return std::move(pblocktemplate); } @@ -257,13 +260,9 @@ static int UpdatePackagesForAdded(const CTxMemPool& mempool, modtxiter mit = mapModifiedTx.find(desc); if (mit == mapModifiedTx.end()) { CTxMemPoolModifiedEntry modEntry(desc); - modEntry.nSizeWithAncestors -= it->GetTxSize(); - modEntry.nModFeesWithAncestors -= it->GetModifiedFee(); - modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost(); - mapModifiedTx.insert(modEntry); - } else { - mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); + mit = mapModifiedTx.insert(modEntry).first; } + mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); } } return nDescendantsUpdated; @@ -396,9 +395,8 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele } CTxMemPool::setEntries ancestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false); onlyUnconfirmed(ancestors); ancestors.insert(iter); diff --git a/src/node/miner.h b/src/node/miner.h index 26454df3df..7269ce1186 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -148,7 +148,7 @@ private: const CChainParams& chainparams; const CTxMemPool* const m_mempool; - CChainState& m_chainstate; + Chainstate& m_chainstate; public: struct Options { @@ -157,8 +157,8 @@ public: CFeeRate blockMinFeeRate; }; - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool); - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options); + explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool); + explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 57162cd679..ca3fc0955d 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -59,7 +59,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) } // Check if it is final - if (!utxo.IsNull() && !PSBTInputSigned(input)) { + if (!PSBTInputSignedAndVerified(psbtx, i, &txdata)) { input_analysis.is_final = false; // Figure out what is missing diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp new file mode 100644 index 0000000000..974358fcda --- /dev/null +++ b/src/node/txreconciliation.cpp @@ -0,0 +1,184 @@ +// 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 <node/txreconciliation.h> + +#include <util/check.h> +#include <util/system.h> + +#include <unordered_map> +#include <variant> + + +namespace { + +/** Static salt component used to compute short txids for sketch construction, see BIP-330. */ +const std::string RECON_STATIC_SALT = "Tx Relay Salting"; +const HashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT); + +/** + * Salt (specified by BIP-330) constructed from contributions from both peers. It is used + * to compute transaction short IDs, which are then used to construct a sketch representing a set + * of transactions we want to announce to the peer. + */ +uint256 ComputeSalt(uint64_t salt1, uint64_t salt2) +{ + // According to BIP-330, salts should be combined in ascending order. + return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256(); +} + +/** + * Keeps track of txreconciliation-related per-peer state. + */ +class TxReconciliationState +{ +public: + /** + * TODO: This field is public to ignore -Wunused-private-field. Make private once used in + * the following commits. + * + * Reconciliation protocol assumes using one role consistently: either a reconciliation + * initiator (requesting sketches), or responder (sending sketches). This defines our role. + * + */ + bool m_we_initiate; + + /** + * TODO: These fields are public to ignore -Wunused-private-field. Make private once used in + * the following commits. + * + * These values are used to salt short IDs, which is necessary for transaction reconciliations. + */ + uint64_t m_k0, m_k1; + + TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {} +}; + +} // namespace + +/** Actual implementation for TxReconciliationTracker's data structure. */ +class TxReconciliationTracker::Impl +{ +private: + mutable Mutex m_txreconciliation_mutex; + + // Local protocol version + uint32_t m_recon_version; + + /** + * Keeps track of txreconciliation states of eligible peers. + * For pre-registered peers, the locally generated salt is stored. + * For registered peers, the locally generated salt is forgotten, and the state (including + * "full" salt) is stored instead. + */ + std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex); + +public: + explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {} + + uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex) + { + AssertLockNotHeld(m_txreconciliation_mutex); + LOCK(m_txreconciliation_mutex); + // We do not support txreconciliation salt/version updates. + assert(m_states.find(peer_id) == m_states.end()); + + LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id); + const uint64_t local_salt{GetRand(UINT64_MAX)}; + + // We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's + // safe to assume we don't have this record yet. + Assert(m_states.emplace(peer_id, local_salt).second); + return local_salt; + } + + ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator, + bool is_peer_recon_responder, uint32_t peer_recon_version, + uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex) + { + AssertLockNotHeld(m_txreconciliation_mutex); + LOCK(m_txreconciliation_mutex); + auto recon_state = m_states.find(peer_id); + + // A peer should be in the pre-registered state to proceed here. + if (recon_state == m_states.end()) return NOT_FOUND; + uint64_t* local_salt = std::get_if<uint64_t>(&recon_state->second); + // A peer is already registered. This should be checked by the caller. + Assume(local_salt); + + // If the peer supports the version which is lower than ours, we downgrade to the version + // it supports. For now, this only guarantees that nodes with future reconciliation + // versions have the choice of reconciling with this current version. However, they also + // have the choice to refuse supporting reconciliations if the common version is not + // satisfactory (e.g. too low). + const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)}; + // v1 is the lowest version, so suggesting something below must be a protocol violation. + if (recon_version < 1) return PROTOCOL_VIOLATION; + + // Must match SENDTXRCNCL logic. + const bool they_initiate = is_peer_recon_initiator && is_peer_inbound; + const bool we_initiate = !is_peer_inbound && is_peer_recon_responder; + + // If we ever announce support for both requesting and responding, this will need + // tie-breaking. For now, this is mutually exclusive because both are based on the + // inbound flag. + assert(!(they_initiate && we_initiate)); + + // The peer set both flags to false, we treat it as a protocol violation. + if (!(they_initiate || we_initiate)) return PROTOCOL_VIOLATION; + + LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Register peer=%d with the following params: " /* Continued */ + "we_initiate=%i, they_initiate=%i.\n", + peer_id, we_initiate, they_initiate); + + const uint256 full_salt{ComputeSalt(*local_salt, remote_salt)}; + recon_state->second = TxReconciliationState(we_initiate, full_salt.GetUint64(0), full_salt.GetUint64(1)); + return SUCCESS; + } + + void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex) + { + AssertLockNotHeld(m_txreconciliation_mutex); + LOCK(m_txreconciliation_mutex); + if (m_states.erase(peer_id)) { + LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Forget txreconciliation state of peer=%d\n", peer_id); + } + } + + bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex) + { + AssertLockNotHeld(m_txreconciliation_mutex); + LOCK(m_txreconciliation_mutex); + auto recon_state = m_states.find(peer_id); + return (recon_state != m_states.end() && + std::holds_alternative<TxReconciliationState>(recon_state->second)); + } +}; + +TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {} + +TxReconciliationTracker::~TxReconciliationTracker() = default; + +uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id) +{ + return m_impl->PreRegisterPeer(peer_id); +} + +ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound, + bool is_peer_recon_initiator, bool is_peer_recon_responder, + uint32_t peer_recon_version, uint64_t remote_salt) +{ + return m_impl->RegisterPeer(peer_id, is_peer_inbound, is_peer_recon_initiator, is_peer_recon_responder, + peer_recon_version, remote_salt); +} + +void TxReconciliationTracker::ForgetPeer(NodeId peer_id) +{ + m_impl->ForgetPeer(peer_id); +} + +bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const +{ + return m_impl->IsPeerRegistered(peer_id); +} diff --git a/src/node/txreconciliation.h b/src/node/txreconciliation.h new file mode 100644 index 0000000000..a4f0870914 --- /dev/null +++ b/src/node/txreconciliation.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef BITCOIN_NODE_TXRECONCILIATION_H +#define BITCOIN_NODE_TXRECONCILIATION_H + +#include <net.h> +#include <sync.h> + +#include <memory> +#include <tuple> + +/** Whether transaction reconciliation protocol should be enabled by default. */ +static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false}; +/** Supported transaction reconciliation protocol version */ +static constexpr uint32_t TXRECONCILIATION_VERSION{1}; + +enum ReconciliationRegisterResult { + NOT_FOUND = 0, + SUCCESS = 1, + PROTOCOL_VIOLATION = 2, +}; + +/** + * Transaction reconciliation is a way for nodes to efficiently announce transactions. + * This object keeps track of all txreconciliation-related communications with the peers. + * The high-level protocol is: + * 0. Txreconciliation protocol handshake. + * 1. Once we receive a new transaction, add it to the set instead of announcing immediately. + * 2. At regular intervals, a txreconciliation initiator requests a sketch from a peer, where a + * sketch is a compressed representation of short form IDs of the transactions in their set. + * 3. Once the initiator received a sketch from the peer, the initiator computes a local sketch, + * and combines the two sketches to attempt finding the difference in *sets*. + * 4a. If the difference was not larger than estimated, see SUCCESS below. + * 4b. If the difference was larger than estimated, initial txreconciliation fails. The initiator + * requests a larger sketch via an extension round (allowed only once). + * - If extension succeeds (a larger sketch is sufficient), see SUCCESS below. + * - If extension fails (a larger sketch is insufficient), see FAILURE below. + * + * SUCCESS. The initiator knows full symmetrical difference and can request what the initiator is + * missing and announce to the peer what the peer is missing. + * + * FAILURE. The initiator notifies the peer about the failure and announces all transactions from + * the corresponding set. Once the peer received the failure notification, the peer + * announces all transactions from their set. + + * This is a modification of the Erlay protocol (https://arxiv.org/abs/1905.10518) with two + * changes (sketch extensions instead of bisections, and an extra INV exchange round), both + * are motivated in BIP-330. + */ +class TxReconciliationTracker +{ +private: + class Impl; + const std::unique_ptr<Impl> m_impl; + +public: + explicit TxReconciliationTracker(uint32_t recon_version); + ~TxReconciliationTracker(); + + /** + * Step 0. Generates initial part of the state (salt) required to reconcile txs with the peer. + * The salt is used for short ID computation required for txreconciliation. + * The function returns the salt. + * A peer can't participate in future txreconciliations without this call. + * This function must be called only once per peer. + */ + uint64_t PreRegisterPeer(NodeId peer_id); + + /** + * Step 0. Once the peer agreed to reconcile txs with us, generate the state required to track + * ongoing reconciliations. Must be called only after pre-registering the peer and only once. + */ + ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator, + bool is_peer_recon_responder, uint32_t peer_recon_version, uint64_t remote_salt); + + /** + * Attempts to forget txreconciliation-related state of the peer (if we previously stored any). + * After this, we won't be able to reconcile transactions with the peer. + */ + void ForgetPeer(NodeId peer_id); + + /** + * Check if a peer is registered to reconcile transactions with us. + */ + bool IsPeerRegistered(NodeId peer_id) const; +}; + +#endif // BITCOIN_NODE_TXRECONCILIATION_H diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp new file mode 100644 index 0000000000..bab1b75211 --- /dev/null +++ b/src/node/utxo_snapshot.cpp @@ -0,0 +1,91 @@ +// 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 <node/utxo_snapshot.h> + +#include <fs.h> +#include <logging.h> +#include <streams.h> +#include <uint256.h> +#include <util/system.h> +#include <validation.h> + +#include <cstdio> +#include <optional> + +namespace node { + +bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate) +{ + AssertLockHeld(::cs_main); + assert(snapshot_chainstate.m_from_snapshot_blockhash); + + const std::optional<fs::path> chaindir = snapshot_chainstate.CoinsDB().StoragePath(); + assert(chaindir); // Sanity check that chainstate isn't in-memory. + const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME; + + FILE* file{fsbridge::fopen(write_to, "wb")}; + AutoFile afile{file}; + if (afile.IsNull()) { + LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n", + fs::PathToString(write_to)); + return false; + } + afile << *snapshot_chainstate.m_from_snapshot_blockhash; + + if (afile.fclose() != 0) { + LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n", + fs::PathToString(write_to)); + return false; + } + return true; +} + +std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir) +{ + if (!fs::exists(chaindir)) { + LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */ + "exists at path %s\n", fs::PathToString(chaindir)); + return std::nullopt; + } + const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME; + const std::string read_from_str = fs::PathToString(read_from); + + if (!fs::exists(read_from)) { + LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */ + "exists at path %s. Try deleting %s and calling loadtxoutset again?\n", + fs::PathToString(chaindir), read_from_str); + return std::nullopt; + } + + uint256 base_blockhash; + FILE* file{fsbridge::fopen(read_from, "rb")}; + AutoFile afile{file}; + if (afile.IsNull()) { + LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n", + read_from_str); + return std::nullopt; + } + afile >> base_blockhash; + + if (std::fgetc(afile.Get()) != EOF) { + LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str); + } else if (std::ferror(afile.Get())) { + LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str); + } + return base_blockhash; +} + +std::optional<fs::path> FindSnapshotChainstateDir() +{ + fs::path possible_dir = + gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX)); + + if (fs::exists(possible_dir)) { + return possible_dir; + } + return std::nullopt; +} + +} // namespace node diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h index 401d4baaeb..c94521792f 100644 --- a/src/node/utxo_snapshot.h +++ b/src/node/utxo_snapshot.h @@ -6,12 +6,18 @@ #ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H #define BITCOIN_NODE_UTXO_SNAPSHOT_H +#include <fs.h> #include <uint256.h> #include <serialize.h> +#include <validation.h> + +#include <optional> + +extern RecursiveMutex cs_main; namespace node { //! Metadata describing a serialized version of a UTXO set from which an -//! assumeutxo CChainState can be constructed. +//! assumeutxo Chainstate can be constructed. class SnapshotMetadata { public: @@ -33,6 +39,33 @@ public: SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); } }; + +//! The file in the snapshot chainstate dir which stores the base blockhash. This is +//! needed to reconstruct snapshot chainstates on init. +//! +//! Because we only allow loading a single snapshot at a time, there will only be one +//! chainstate directory with this filename present within it. +const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"}; + +//! Write out the blockhash of the snapshot base block that was used to construct +//! this chainstate. This value is read in during subsequent initializations and +//! used to reconstruct snapshot-based chainstates. +bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + +//! Read the blockhash of the snapshot base block that was used to construct the +//! chainstate. +std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + +//! Suffix appended to the chainstate (leveldb) dir when created based upon +//! a snapshot. +constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot"; + + +//! Return a path to the snapshot-based chainstate dir, if one exists. +std::optional<fs::path> FindSnapshotChainstateDir(); + } // namespace node #endif // BITCOIN_NODE_UTXO_SNAPSHOT_H diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 2b940be07e..ab5599a1b4 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -997,8 +997,9 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) return true; } -void CBlockPolicyEstimator::FlushUnconfirmed() { - int64_t startclear = GetTimeMicros(); +void CBlockPolicyEstimator::FlushUnconfirmed() +{ + const auto startclear{SteadyClock::now()}; LOCK(m_cs_fee_estimator); size_t num_entries = mapMemPoolTxs.size(); // Remove every entry in mapMemPoolTxs @@ -1006,24 +1007,41 @@ void CBlockPolicyEstimator::FlushUnconfirmed() { auto mi = mapMemPoolTxs.begin(); _removeTx(mi->first, false); // this calls erase() on mapMemPoolTxs } - int64_t endclear = GetTimeMicros(); - LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, (endclear - startclear)*0.000001); + const auto endclear{SteadyClock::now()}; + LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, Ticks<SecondsDouble>(endclear - startclear)); } -FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) +static std::set<double> MakeFeeSet(const CFeeRate& min_incremental_fee, + double max_filter_fee_rate, + double fee_filter_spacing) { - CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2); - feeset.insert(0); - for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_FILTER_FEERATE; bucketBoundary *= FEE_FILTER_SPACING) { - feeset.insert(bucketBoundary); + std::set<double> fee_set; + + const CAmount min_fee_limit{std::max(CAmount(1), min_incremental_fee.GetFeePerK() / 2)}; + fee_set.insert(0); + for (double bucket_boundary = min_fee_limit; + bucket_boundary <= max_filter_fee_rate; + bucket_boundary *= fee_filter_spacing) { + + fee_set.insert(bucket_boundary); } + + return fee_set; +} + +FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) + : m_fee_set{MakeFeeSet(minIncrementalFee, MAX_FILTER_FEERATE, FEE_FILTER_SPACING)} +{ } CAmount FeeFilterRounder::round(CAmount currentMinFee) { - std::set<double>::iterator it = feeset.lower_bound(currentMinFee); - if ((it != feeset.begin() && insecure_rand.rand32() % 3 != 0) || it == feeset.end()) { - it--; + AssertLockNotHeld(m_insecure_rand_mutex); + std::set<double>::iterator it = m_fee_set.lower_bound(currentMinFee); + if (it == m_fee_set.end() || + (it != m_fee_set.begin() && + WITH_LOCK(m_insecure_rand_mutex, return insecure_rand.rand32()) % 3 != 0)) { + --it; } return static_cast<CAmount>(*it); } diff --git a/src/policy/fees.h b/src/policy/fees.h index e4628bf853..204c4f2118 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -299,14 +299,15 @@ private: public: /** Create new FeeFilterRounder */ - explicit FeeFilterRounder(const CFeeRate& minIncrementalFee); + explicit FeeFilterRounder(const CFeeRate& min_incremental_fee); - /** Quantize a minimum fee for privacy purpose before broadcast. Not thread-safe due to use of FastRandomContext */ - CAmount round(CAmount currentMinFee); + /** Quantize a minimum fee for privacy purpose before broadcast. */ + CAmount round(CAmount currentMinFee) EXCLUSIVE_LOCKS_REQUIRED(!m_insecure_rand_mutex); private: - std::set<double> feeset; - FastRandomContext insecure_rand; + const std::set<double> m_fee_set; + Mutex m_insecure_rand_mutex; + FastRandomContext insecure_rand GUARDED_BY(m_insecure_rand_mutex); }; #endif // BITCOIN_POLICY_FEES_H diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 6098caced9..55f47f485b 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -36,10 +36,9 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) // If all the inputs have nSequence >= maxint-1, it still might be // signaled for RBF if any unconfirmed parents have signaled. - uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash()); - pool.CalculateMemPoolAncestors(entry, ancestors, noLimit, noLimit, noLimit, noLimit, dummy, false); + pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false); for (CTxMemPool::txiter it : ancestors) { if (SignalsOptInRBF(it->GetTx())) { diff --git a/src/prevector.h b/src/prevector.h index a52510930a..7df5a067a2 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -6,7 +6,7 @@ #define BITCOIN_PREVECTOR_H #include <assert.h> -#include <stdlib.h> +#include <cstdlib> #include <stdint.h> #include <string.h> diff --git a/src/protocol.cpp b/src/protocol.cpp index bdd1cc2aff..23c68b335b 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -44,6 +44,7 @@ const char *CFHEADERS="cfheaders"; const char *GETCFCHECKPT="getcfcheckpt"; const char *CFCHECKPT="cfcheckpt"; const char *WTXIDRELAY="wtxidrelay"; +const char *SENDTXRCNCL="sendtxrcncl"; } // namespace NetMsgType /** All known message types. Keep this in the same order as the list of @@ -84,6 +85,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::GETCFCHECKPT, NetMsgType::CFCHECKPT, NetMsgType::WTXIDRELAY, + NetMsgType::SENDTXRCNCL, }; const static std::vector<std::string> allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index b85dc0d820..17a363b1d3 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -258,6 +258,14 @@ extern const char* CFCHECKPT; * @since protocol version 70016 as described by BIP 339. */ extern const char* WTXIDRELAY; +/** + * Contains 2 1-byte bools, a 4-byte version number and an 8-byte salt. + * The 2 booleans indicate that a node is willing to participate in transaction + * reconciliation, respectively as an initiator or as a receiver. + * The salt is used to compute short txids needed for efficient + * txreconciliation, as described by BIP 330. + */ +extern const char* SENDTXRCNCL; }; // namespace NetMsgType /* Get a vector of all valid message types (see above) */ diff --git a/src/psbt.cpp b/src/psbt.cpp index 36fec74bc9..461987c503 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -4,6 +4,7 @@ #include <psbt.h> +#include <policy/policy.h> #include <util/check.h> #include <util/strencodings.h> @@ -218,8 +219,14 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } - if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) { - TaprootSpendData spenddata = m_tap_tree->GetSpendData(); + if (!m_tap_tree.empty() && m_tap_internal_key.IsFullyValid()) { + TaprootBuilder builder; + for (const auto& [depth, leaf_ver, script] : m_tap_tree) { + builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true); + } + assert(builder.IsComplete()); + builder.Finalize(m_tap_internal_key); + TaprootSpendData spenddata = builder.GetSpendData(); sigdata.tr_spenddata.internal_key = m_tap_internal_key; sigdata.tr_spenddata.Merge(spenddata); @@ -243,8 +250,8 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata) if (!sigdata.tr_spenddata.internal_key.IsNull()) { m_tap_internal_key = sigdata.tr_spenddata.internal_key; } - if (sigdata.tr_builder.has_value()) { - m_tap_tree = sigdata.tr_builder; + if (sigdata.tr_builder.has_value() && sigdata.tr_builder->HasScripts()) { + m_tap_tree = sigdata.tr_builder->GetTreeTuples(); } for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { m_tap_bip32_paths.emplace(pubkey, leaf_origin); @@ -265,13 +272,43 @@ void PSBTOutput::Merge(const PSBTOutput& output) if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key; - if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree; + if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree; } + bool PSBTInputSigned(const PSBTInput& input) { return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); } +bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata) +{ + CTxOut utxo; + assert(psbt.inputs.size() >= input_index); + const PSBTInput& input = psbt.inputs[input_index]; + + if (input.non_witness_utxo) { + // If we're taking our information from a non-witness UTXO, verify that it matches the prevout. + COutPoint prevout = psbt.tx->vin[input_index].prevout; + if (prevout.n >= input.non_witness_utxo->vout.size()) { + return false; + } + if (input.non_witness_utxo->GetHash() != prevout.hash) { + return false; + } + utxo = input.non_witness_utxo->vout[prevout.n]; + } else if (!input.witness_utxo.IsNull()) { + utxo = input.witness_utxo; + } else { + return false; + } + + if (txdata) { + return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL}); + } else { + return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, MissingDataBehavior::FAIL}); + } +} + size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) { size_t count = 0; for (const auto& input : psbt.inputs) { @@ -325,7 +362,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; - if (PSBTInputSigned(input)) { + if (PSBTInputSignedAndVerified(psbt, index, txdata)) { return true; } diff --git a/src/psbt.h b/src/psbt.h index d5c67802c7..37bf142366 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -713,7 +713,7 @@ struct PSBTOutput CScript witness_script; std::map<CPubKey, KeyOriginInfo> hd_keypaths; XOnlyPubKey m_tap_internal_key; - std::optional<TaprootBuilder> m_tap_tree; + std::vector<std::tuple<uint8_t, uint8_t, CScript>> m_tap_tree; std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::set<PSBTProprietary> m_proprietary; @@ -754,15 +754,11 @@ struct PSBTOutput } // Write taproot tree - if (m_tap_tree.has_value()) { + if (!m_tap_tree.empty()) { SerializeToVector(s, PSBT_OUT_TAP_TREE); std::vector<unsigned char> value; CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); - const auto& tuples = m_tap_tree->GetTreeTuples(); - for (const auto& tuple : tuples) { - uint8_t depth = std::get<0>(tuple); - uint8_t leaf_ver = std::get<1>(tuple); - CScript script = std::get<2>(tuple); + for (const auto& [depth, leaf_ver, script] : m_tap_tree) { s_value << depth; s_value << leaf_ver; s_value << script; @@ -858,10 +854,13 @@ struct PSBTOutput } else if (key.size() != 1) { throw std::ios_base::failure("Output Taproot tree key is more than one byte type"); } - m_tap_tree.emplace(); std::vector<unsigned char> tree_v; s >> tree_v; SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v); + if (s_tree.empty()) { + throw std::ios_base::failure("Output Taproot tree must not be empty"); + } + TaprootBuilder builder; while (!s_tree.empty()) { uint8_t depth; uint8_t leaf_ver; @@ -875,9 +874,10 @@ struct PSBTOutput if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) { throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version"); } - m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */); + m_tap_tree.push_back(std::make_tuple(depth, leaf_ver, script)); + builder.Add((int)depth, script, (int)leaf_ver, true /* track */); } - if (!m_tap_tree->IsComplete()) { + if (!builder.IsComplete()) { throw std::ios_base::failure("Output Taproot tree is malformed"); } break; @@ -931,11 +931,6 @@ struct PSBTOutput } } - // Finalize m_tap_tree so that all of the computed things are computed - if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) { - m_tap_tree->Finalize(m_tap_internal_key); - } - if (!found_sep) { throw std::ios_base::failure("Separator is missing at the end of an output map"); } @@ -1223,9 +1218,12 @@ std::string PSBTRoleName(PSBTRole role); /** Compute a PrecomputedTransactionData object from a psbt. */ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); -/** Checks whether a PSBTInput is already signed. */ +/** Checks whether a PSBTInput is already signed by checking for non-null finalized fields. */ bool PSBTInputSigned(const PSBTInput& input); +/** Checks whether a PSBTInput is already signed by doing script verification using final fields. */ +bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata); + /** Signs a PSBTInput, verifying that all provided data matches what is being signed. * * txdata should be the output of PrecomputePSBTData (which can be shared across diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index f6eee92cec..f522d78be4 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -431,7 +431,7 @@ void BitcoinGUI::createActions() bool wallet_name_ok; /*: Title of pop-up window shown when the user is attempting to -+ restore a wallet. */ + restore a wallet. */ QString title = tr("Restore Wallet"); //: Label of the input field where the name of the wallet is entered. QString label = tr("Wallet Name"); diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index 4489c00932..3df4d4d921 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -60,6 +60,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Error reading %s! Transaction data may be missing or incorrect. Rescanning " "wallet."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Address book data in wallet cannot be identified to belong to " +"migrated wallets"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), @@ -67,9 +70,18 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile version is not supported. This version of bitcoin-wallet " "only supports version 1 dumpfiles. Got dumpfile with version %s"), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Duplicate descriptors created during migration. Your wallet may be " +"corrupted."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and " "\"bech32\" address types"), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Transaction %s in wallet cannot be identified to belong to migrated " +"wallets"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Unable to produce descriptors for this legacy wallet. Make sure the " +"wallet is unlocked first"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Failed to rename invalid peers.dat file. Please move or delete it and try " "again."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -79,6 +91,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "File %s already exists. If you are sure this is what you want, move it out " "of the way first."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet " +"forbids connections to IPv4/IPv6"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay " "fee of %s to prevent stuck transactions)"), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -98,8 +113,11 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "be provided."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " -"reaching the Tor network is not provided (no -proxy= and no -onion= given) " -"or it is explicitly forbidden (-onion=0)"), +"reaching the Tor network is explicitly forbidden: -onion=0"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " +"reaching the Tor network is not provided: none of -proxy, -onion or -" +"listenonion is given"), QT_TRANSLATE_NOOP("bitcoin-core", "" "Please check that your computer's date and time are correct! If your clock " "is wrong, %s will not work properly."), @@ -156,6 +174,14 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or " "\"sqlite\"."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unrecognized descriptor found. Loading wallet %s\n" +"\n" +"The wallet might had been created on a newer version.\n" +"Please try running the latest software version.\n"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unsupported category-specific logging level -loglevel=%s. Expected -" +"loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Unsupported chainstate database format found. Please restart with -reindex-" "chainstate. This will rebuild the chainstate database."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -176,6 +202,12 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" QT_TRANSLATE_NOOP("bitcoin-core", "" "You need to rebuild the database using -reindex to go back to unpruned " "mode. This will redownload the entire blockchain"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"\n" +"Unable to cleanup failed migration"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"\n" +"Unable to restore backup of wallet."), QT_TRANSLATE_NOOP("bitcoin-core", "%s is set very high!"), QT_TRANSLATE_NOOP("bitcoin-core", "-maxmempool must be at least %d MB"), QT_TRANSLATE_NOOP("bitcoin-core", "A fatal internal error occurred, see debug.log for details"), @@ -203,15 +235,25 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Error loading block database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error opening block database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error reading from database, shutting down."), QT_TRANSLATE_NOOP("bitcoin-core", "Error reading next record from wallet database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not add watchonly tx to watchonly wallet"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not delete watchonly transactions"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Couldn't create cursor into database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is low for %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Dumpfile checksum does not match. Computed %s, expected %s"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Failed to create new watchonly wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got key that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got value that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Keypool ran out, please call keypoolrefill first"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Missing checksum"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: No %s addresses available."), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Not all watchonly txs could be deleted"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet already uses SQLite"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet is already a descriptor wallet"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to begin reading all records in the database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to make a backup of your wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to parse version %u as a uint32_t"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to read all records in the database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to remove watchonly address book data"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to write record to new wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to listen on any port. Use -listen=0 if you want this."), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to rescan the wallet during initialization"), @@ -283,11 +325,13 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate keys"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to open %s for writing"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to parse -maxuploadtarget: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to start HTTP server. See debug log for details."), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to unload the wallet before migrating"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown address type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown change type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown new rules activated (versionbit %i)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported global logging level -loglevel=%s. Valid values: %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."), QT_TRANSLATE_NOOP("bitcoin-core", "User Agent comment (%s) contains unsafe characters."), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying blocks…"), diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index ead977296a..33308cd68c 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1185,10 +1185,10 @@ <item row="6" column="0"> <widget class="QLabel" name="peerRelayTxesLabel"> <property name="toolTip"> - <string>Whether the peer requested us to relay transactions.</string> + <string>Whether we relay transactions to this peer (not available while the peer connection is being set up).</string> </property> <property name="text"> - <string>Wants Tx Relay</string> + <string>Transaction Relay</string> </property> </widget> </item> diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui index a1e94f99e6..9ab91f6aa9 100644 --- a/src/qt/forms/intro.ui +++ b/src/qt/forms/intro.ui @@ -203,7 +203,7 @@ <item> <widget class="QLabel" name="lblExplanation1"> <property name="text"> - <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</string> + <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 582f02132a..6be47e94fe 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -286,7 +286,7 @@ <item> <widget class="QLineEdit" name="externalSignerPath"> <property name="toolTip"> - <string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string> + <string>Full path to a %1 compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string> </property> </widget> </item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 5cc21dd40b..b9f0be41e3 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -982,7 +982,7 @@ void PrintSlotException( std::string description = sender->metaObject()->className(); description += "->"; description += receiver->metaObject()->className(); - PrintExceptionContinue(exception, description.c_str()); + PrintExceptionContinue(exception, description); } void ShowModalDialogAsynchronously(QDialog* dialog) diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 883d3fe24a..586240445e 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -751,7 +751,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <message> <location line="+8"/> <source>Restore Wallet</source> - <extracomment>Title of pop-up window shown when the user is attempting to + restore a wallet.</extracomment> + <extracomment>Title of pop-up window shown when the user is attempting to restore a wallet.</extracomment> <translation type="unfinished"></translation> </message> <message> @@ -1370,12 +1370,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+157"/> - <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+32"/> + <location line="+189"/> <source>Limit block chain storage to</source> <translation type="unfinished"></translation> </message> @@ -1395,7 +1390,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> + <location line="-10"/> + <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+20"/> <source>If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source> <translation type="unfinished"></translation> </message> @@ -5228,7 +5228,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Error: Dumpfile format record is incorrect. Got "%s", expected "format".</source> <translation type="unfinished"></translation> </message> @@ -5243,12 +5243,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+12"/> <source>Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> <translation type="unfinished"></translation> </message> @@ -5258,7 +5258,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)</source> <translation type="unfinished"></translation> </message> @@ -5288,12 +5288,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided (no -proxy= and no -onion= given) or it is explicitly forbidden (-onion=0)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+4"/> + <location line="+10"/> <source>Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.</source> <translation type="unfinished"></translation> </message> @@ -5378,7 +5373,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+11"/> <source>Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source> <translation type="unfinished"></translation> </message> @@ -5413,7 +5408,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+9"/> <source>%s is set very high!</source> <translation type="unfinished"></translation> </message> @@ -5448,12 +5443,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="-65"/> + <location line="-79"/> <source>The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-104"/> + <location line="-122"/> <source>%s request to listen on port %u. This port is considered "bad" and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source> <translation type="unfinished"></translation> </message> @@ -5488,12 +5483,73 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> + <location line="+9"/> + <source>Error: Address book data in wallet cannot be identified to belong to migrated wallets</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> + <source>Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> + <source>Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Failed to rename invalid peers.dat file. Please move or delete it and try again.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+114"/> + <location line="+9"/> + <source>Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+21"/> + <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+59"/> + <source>Unrecognized descriptor found. Loading wallet %s + +The wallet might had been created on a newer version. +Please try running the latest software version. +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+5"/> + <source>Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+24"/> + <source> +Unable to cleanup failed migration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source> +Unable to restore backup of wallet.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> <source>Config setting for %s only applied on %s network when in [%s] section.</source> <translation type="unfinished"></translation> </message> @@ -5594,6 +5650,16 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: Could not add watchonly tx to watchonly wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Could not delete watchonly transactions</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Couldn't create cursor into database</source> <translation type="unfinished"></translation> </message> @@ -5609,6 +5675,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: Failed to create new watchonly wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Got key that was not hex: %s</source> <translation type="unfinished"></translation> </message> @@ -5634,11 +5705,46 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: Not all watchonly txs could be deleted</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: This wallet already uses SQLite</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: This wallet is already a descriptor wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to begin reading all records in the database</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to make a backup of your wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Unable to parse version %u as a uint32_t</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> + <source>Error: Unable to read all records in the database</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to remove watchonly address book data</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Unable to write record to new wallet</source> <translation type="unfinished"></translation> </message> @@ -5994,6 +6100,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unable to unload the wallet before migrating</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unknown -blockfilterindex value %s.</source> <translation type="unfinished"></translation> </message> @@ -6019,6 +6130,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unsupported global logging level -loglevel=%s. Valid values: %s.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unsupported logging category %s=%s.</source> <translation type="unfinished"></translation> </message> diff --git a/src/qt/locale/bitcoin_en.xlf b/src/qt/locale/bitcoin_en.xlf index 7b2cfd30cf..a25ccfca72 100644 --- a/src/qt/locale/bitcoin_en.xlf +++ b/src/qt/locale/bitcoin_en.xlf @@ -661,7 +661,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <trans-unit id="_msg151"> <source xml:space="preserve">Restore Wallet</source> <context-group purpose="location"><context context-type="linenumber">435</context></context-group> - <note annotates="source" from="developer">Title of pop-up window shown when the user is attempting to + restore a wallet.</note> + <note annotates="source" from="developer">Title of pop-up window shown when the user is attempting to restore a wallet.</note> </trans-unit> <trans-unit id="_msg152"> <source xml:space="preserve">Wallet Name</source> @@ -1353,25 +1353,25 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> <trans-unit id="_msg298"> - <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source> - <context-group purpose="location"><context context-type="linenumber">206</context></context-group> - </trans-unit> - <trans-unit id="_msg299"> <source xml:space="preserve">Limit block chain storage to</source> <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> - <trans-unit id="_msg300"> + <trans-unit id="_msg299"> <source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</source> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg301"> + <trans-unit id="_msg300"> <source xml:space="preserve"> GB</source> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> - <trans-unit id="_msg302"> + <trans-unit id="_msg301"> <source xml:space="preserve">This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</source> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> + <trans-unit id="_msg302"> + <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source> + <context-group purpose="location"><context context-type="linenumber">206</context></context-group> + </trans-unit> <trans-unit id="_msg303"> <source xml:space="preserve">If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> @@ -4416,655 +4416,749 @@ Go to File > Open Wallet to load a wallet. </trans-unit> <trans-unit id="_msg965"> <source xml:space="preserve">Error: Dumpfile format record is incorrect. Got "%s", expected "format".</source> - <context-group purpose="location"><context context-type="linenumber">62</context></context-group> + <context-group purpose="location"><context context-type="linenumber">65</context></context-group> </trans-unit> <trans-unit id="_msg966"> <source xml:space="preserve">Error: Dumpfile identifier record is incorrect. Got "%s", expected "%s".</source> - <context-group purpose="location"><context context-type="linenumber">64</context></context-group> + <context-group purpose="location"><context context-type="linenumber">67</context></context-group> </trans-unit> <trans-unit id="_msg967"> <source xml:space="preserve">Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s</source> - <context-group purpose="location"><context context-type="linenumber">66</context></context-group> + <context-group purpose="location"><context context-type="linenumber">69</context></context-group> </trans-unit> <trans-unit id="_msg968"> <source xml:space="preserve">Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> - <context-group purpose="location"><context context-type="linenumber">69</context></context-group> + <context-group purpose="location"><context context-type="linenumber">75</context></context-group> </trans-unit> <trans-unit id="_msg969"> <source xml:space="preserve">Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> - <context-group purpose="location"><context context-type="linenumber">75</context></context-group> + <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> <trans-unit id="_msg970"> <source xml:space="preserve">File %s already exists. If you are sure this is what you want, move it out of the way first.</source> - <context-group purpose="location"><context context-type="linenumber">78</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> <trans-unit id="_msg971"> <source xml:space="preserve">Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)</source> - <context-group purpose="location"><context context-type="linenumber">81</context></context-group> + <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> <trans-unit id="_msg972"> <source xml:space="preserve">Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start.</source> - <context-group purpose="location"><context context-type="linenumber">84</context></context-group> + <context-group purpose="location"><context context-type="linenumber">99</context></context-group> </trans-unit> <trans-unit id="_msg973"> <source xml:space="preserve">More than one onion bind address is provided. Using %s for the automatically created Tor onion service.</source> - <context-group purpose="location"><context context-type="linenumber">88</context></context-group> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> <trans-unit id="_msg974"> <source xml:space="preserve">No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">91</context></context-group> + <context-group purpose="location"><context context-type="linenumber">106</context></context-group> </trans-unit> <trans-unit id="_msg975"> <source xml:space="preserve">No dump file provided. To use dump, -dumpfile=<filename> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">94</context></context-group> + <context-group purpose="location"><context context-type="linenumber">109</context></context-group> </trans-unit> <trans-unit id="_msg976"> <source xml:space="preserve">No wallet file format provided. To use createfromdump, -format=<format> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">96</context></context-group> + <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> <trans-unit id="_msg977"> - <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided (no -proxy= and no -onion= given) or it is explicitly forbidden (-onion=0)</source> - <context-group purpose="location"><context context-type="linenumber">99</context></context-group> - </trans-unit> - <trans-unit id="_msg978"> <source xml:space="preserve">Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.</source> - <context-group purpose="location"><context context-type="linenumber">103</context></context-group> + <context-group purpose="location"><context context-type="linenumber">121</context></context-group> </trans-unit> - <trans-unit id="_msg979"> + <trans-unit id="_msg978"> <source xml:space="preserve">Please contribute if you find %s useful. Visit %s for further information about the software.</source> - <context-group purpose="location"><context context-type="linenumber">106</context></context-group> + <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg980"> + <trans-unit id="_msg979"> <source xml:space="preserve">Prune configured below the minimum of %d MiB. Please use a higher number.</source> - <context-group purpose="location"><context context-type="linenumber">109</context></context-group> + <context-group purpose="location"><context context-type="linenumber">127</context></context-group> </trans-unit> - <trans-unit id="_msg981"> + <trans-unit id="_msg980"> <source xml:space="preserve">Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source> - <context-group purpose="location"><context context-type="linenumber">111</context></context-group> + <context-group purpose="location"><context context-type="linenumber">129</context></context-group> </trans-unit> - <trans-unit id="_msg982"> + <trans-unit id="_msg981"> <source xml:space="preserve">Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source> - <context-group purpose="location"><context context-type="linenumber">114</context></context-group> + <context-group purpose="location"><context context-type="linenumber">132</context></context-group> </trans-unit> - <trans-unit id="_msg983"> + <trans-unit id="_msg982"> <source xml:space="preserve">SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported</source> - <context-group purpose="location"><context context-type="linenumber">117</context></context-group> + <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg984"> + <trans-unit id="_msg983"> <source xml:space="preserve">The block database contains a block which appears to be from the future. This may be due to your computer's date and time being set incorrectly. Only rebuild the block database if you are sure that your computer's date and time are correct</source> - <context-group purpose="location"><context context-type="linenumber">123</context></context-group> + <context-group purpose="location"><context context-type="linenumber">141</context></context-group> </trans-unit> - <trans-unit id="_msg985"> + <trans-unit id="_msg984"> <source xml:space="preserve">The block index db contains a legacy 'txindex'. To clear the occupied disk space, run a full -reindex, otherwise ignore this error. This error message will not be displayed again.</source> - <context-group purpose="location"><context context-type="linenumber">128</context></context-group> + <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg986"> + <trans-unit id="_msg985"> <source xml:space="preserve">The transaction amount is too small to send after the fee has been deducted</source> - <context-group purpose="location"><context context-type="linenumber">132</context></context-group> + <context-group purpose="location"><context context-type="linenumber">150</context></context-group> </trans-unit> - <trans-unit id="_msg987"> + <trans-unit id="_msg986"> <source xml:space="preserve">This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet</source> - <context-group purpose="location"><context context-type="linenumber">134</context></context-group> + <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg988"> + <trans-unit id="_msg987"> <source xml:space="preserve">This is a pre-release test build - use at your own risk - do not use for mining or merchant applications</source> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">156</context></context-group> </trans-unit> - <trans-unit id="_msg989"> + <trans-unit id="_msg988"> <source xml:space="preserve">This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection.</source> - <context-group purpose="location"><context context-type="linenumber">141</context></context-group> + <context-group purpose="location"><context context-type="linenumber">159</context></context-group> </trans-unit> - <trans-unit id="_msg990"> + <trans-unit id="_msg989"> <source xml:space="preserve">This is the transaction fee you may discard if change is smaller than dust at this level</source> - <context-group purpose="location"><context context-type="linenumber">144</context></context-group> + <context-group purpose="location"><context context-type="linenumber">162</context></context-group> </trans-unit> - <trans-unit id="_msg991"> + <trans-unit id="_msg990"> <source xml:space="preserve">This is the transaction fee you may pay when fee estimates are not available.</source> - <context-group purpose="location"><context context-type="linenumber">147</context></context-group> + <context-group purpose="location"><context context-type="linenumber">165</context></context-group> </trans-unit> - <trans-unit id="_msg992"> + <trans-unit id="_msg991"> <source xml:space="preserve">Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.</source> - <context-group purpose="location"><context context-type="linenumber">149</context></context-group> + <context-group purpose="location"><context context-type="linenumber">167</context></context-group> </trans-unit> - <trans-unit id="_msg993"> + <trans-unit id="_msg992"> <source xml:space="preserve">Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.</source> - <context-group purpose="location"><context context-type="linenumber">152</context></context-group> + <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg994"> + <trans-unit id="_msg993"> <source xml:space="preserve">Unknown wallet file format "%s" provided. Please provide one of "bdb" or "sqlite".</source> - <context-group purpose="location"><context context-type="linenumber">155</context></context-group> + <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg995"> + <trans-unit id="_msg994"> <source xml:space="preserve">Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source> - <context-group purpose="location"><context context-type="linenumber">158</context></context-group> + <context-group purpose="location"><context context-type="linenumber">184</context></context-group> </trans-unit> - <trans-unit id="_msg996"> + <trans-unit id="_msg995"> <source xml:space="preserve">Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.</source> - <context-group purpose="location"><context context-type="linenumber">161</context></context-group> + <context-group purpose="location"><context context-type="linenumber">187</context></context-group> </trans-unit> - <trans-unit id="_msg997"> + <trans-unit id="_msg996"> <source xml:space="preserve">Warning: Dumpfile wallet format "%s" does not match command line specified format "%s".</source> - <context-group purpose="location"><context context-type="linenumber">165</context></context-group> + <context-group purpose="location"><context context-type="linenumber">191</context></context-group> </trans-unit> - <trans-unit id="_msg998"> + <trans-unit id="_msg997"> <source xml:space="preserve">Warning: Private keys detected in wallet {%s} with disabled private keys</source> - <context-group purpose="location"><context context-type="linenumber">168</context></context-group> + <context-group purpose="location"><context context-type="linenumber">194</context></context-group> </trans-unit> - <trans-unit id="_msg999"> + <trans-unit id="_msg998"> <source xml:space="preserve">Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.</source> - <context-group purpose="location"><context context-type="linenumber">170</context></context-group> + <context-group purpose="location"><context context-type="linenumber">196</context></context-group> </trans-unit> - <trans-unit id="_msg1000"> + <trans-unit id="_msg999"> <source xml:space="preserve">Witness data for blocks after height %d requires validation. Please restart with -reindex.</source> - <context-group purpose="location"><context context-type="linenumber">173</context></context-group> + <context-group purpose="location"><context context-type="linenumber">199</context></context-group> </trans-unit> - <trans-unit id="_msg1001"> + <trans-unit id="_msg1000"> <source xml:space="preserve">You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain</source> - <context-group purpose="location"><context context-type="linenumber">176</context></context-group> + <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> - <trans-unit id="_msg1002"> + <trans-unit id="_msg1001"> <source xml:space="preserve">%s is set very high!</source> - <context-group purpose="location"><context context-type="linenumber">179</context></context-group> + <context-group purpose="location"><context context-type="linenumber">211</context></context-group> </trans-unit> - <trans-unit id="_msg1003"> + <trans-unit id="_msg1002"> <source xml:space="preserve">-maxmempool must be at least %d MB</source> - <context-group purpose="location"><context context-type="linenumber">180</context></context-group> + <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg1004"> + <trans-unit id="_msg1003"> <source xml:space="preserve">A fatal internal error occurred, see debug.log for details</source> - <context-group purpose="location"><context context-type="linenumber">181</context></context-group> + <context-group purpose="location"><context context-type="linenumber">213</context></context-group> </trans-unit> - <trans-unit id="_msg1005"> + <trans-unit id="_msg1004"> <source xml:space="preserve">Cannot resolve -%s address: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">182</context></context-group> + <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg1006"> + <trans-unit id="_msg1005"> <source xml:space="preserve">Cannot set -forcednsseed to true when setting -dnsseed to false.</source> - <context-group purpose="location"><context context-type="linenumber">183</context></context-group> + <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg1007"> + <trans-unit id="_msg1006"> <source xml:space="preserve">Cannot set -peerblockfilters without -blockfilterindex.</source> - <context-group purpose="location"><context context-type="linenumber">184</context></context-group> + <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg1008"> + <trans-unit id="_msg1007"> <source xml:space="preserve">Cannot write to data directory '%s'; check permissions.</source> - <context-group purpose="location"><context context-type="linenumber">185</context></context-group> + <context-group purpose="location"><context context-type="linenumber">217</context></context-group> </trans-unit> - <trans-unit id="_msg1009"> + <trans-unit id="_msg1008"> <source xml:space="preserve">The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source> - <context-group purpose="location"><context context-type="linenumber">120</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg1010"> + <trans-unit id="_msg1009"> <source xml:space="preserve">%s request to listen on port %u. This port is considered "bad" and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source> <context-group purpose="location"><context context-type="linenumber">16</context></context-group> </trans-unit> - <trans-unit id="_msg1011"> + <trans-unit id="_msg1010"> <source xml:space="preserve">-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.</source> <context-group purpose="location"><context context-type="linenumber">23</context></context-group> </trans-unit> - <trans-unit id="_msg1012"> + <trans-unit id="_msg1011"> <source xml:space="preserve">-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.</source> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg1013"> + <trans-unit id="_msg1012"> <source xml:space="preserve">-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.</source> <context-group purpose="location"><context context-type="linenumber">31</context></context-group> </trans-unit> - <trans-unit id="_msg1014"> + <trans-unit id="_msg1013"> <source xml:space="preserve">Assumed-valid: last wallet synchronisation goes beyond available block data. You need to wait for the background validation chain to download more blocks.</source> <context-group purpose="location"><context context-type="linenumber">35</context></context-group> </trans-unit> - <trans-unit id="_msg1015"> + <trans-unit id="_msg1014"> <source xml:space="preserve">Cannot provide specific connections and have addrman find outgoing connections at the same time.</source> <context-group purpose="location"><context context-type="linenumber">43</context></context-group> </trans-unit> - <trans-unit id="_msg1016"> + <trans-unit id="_msg1015"> <source xml:space="preserve">Error loading %s: External signer wallet being loaded without external signer support compiled</source> <context-group purpose="location"><context context-type="linenumber">53</context></context-group> </trans-unit> + <trans-unit id="_msg1016"> + <source xml:space="preserve">Error: Address book data in wallet cannot be identified to belong to migrated wallets</source> + <context-group purpose="location"><context context-type="linenumber">62</context></context-group> + </trans-unit> <trans-unit id="_msg1017"> - <source xml:space="preserve">Failed to rename invalid peers.dat file. Please move or delete it and try again.</source> + <source xml:space="preserve">Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source> <context-group purpose="location"><context context-type="linenumber">72</context></context-group> </trans-unit> <trans-unit id="_msg1018"> - <source xml:space="preserve">Config setting for %s only applied on %s network when in [%s] section.</source> - <context-group purpose="location"><context context-type="linenumber">186</context></context-group> + <source xml:space="preserve">Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source> + <context-group purpose="location"><context context-type="linenumber">78</context></context-group> </trans-unit> <trans-unit id="_msg1019"> - <source xml:space="preserve">Copyright (C) %i-%i</source> - <context-group purpose="location"><context context-type="linenumber">187</context></context-group> + <source xml:space="preserve">Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source> + <context-group purpose="location"><context context-type="linenumber">81</context></context-group> </trans-unit> <trans-unit id="_msg1020"> - <source xml:space="preserve">Corrupted block database detected</source> - <context-group purpose="location"><context context-type="linenumber">188</context></context-group> + <source xml:space="preserve">Failed to rename invalid peers.dat file. Please move or delete it and try again.</source> + <context-group purpose="location"><context context-type="linenumber">84</context></context-group> </trans-unit> <trans-unit id="_msg1021"> - <source xml:space="preserve">Could not find asmap file %s</source> - <context-group purpose="location"><context context-type="linenumber">189</context></context-group> + <source xml:space="preserve">Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source> + <context-group purpose="location"><context context-type="linenumber">93</context></context-group> </trans-unit> <trans-unit id="_msg1022"> - <source xml:space="preserve">Could not parse asmap file %s</source> - <context-group purpose="location"><context context-type="linenumber">190</context></context-group> + <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source> + <context-group purpose="location"><context context-type="linenumber">114</context></context-group> </trans-unit> <trans-unit id="_msg1023"> - <source xml:space="preserve">Disk space is too low!</source> - <context-group purpose="location"><context context-type="linenumber">191</context></context-group> + <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source> + <context-group purpose="location"><context context-type="linenumber">117</context></context-group> </trans-unit> <trans-unit id="_msg1024"> - <source xml:space="preserve">Do you want to rebuild the block database now?</source> - <context-group purpose="location"><context context-type="linenumber">192</context></context-group> + <source xml:space="preserve">Unrecognized descriptor found. Loading wallet %s + +The wallet might had been created on a newer version. +Please try running the latest software version. +</source> + <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> <trans-unit id="_msg1025"> - <source xml:space="preserve">Done loading</source> - <context-group purpose="location"><context context-type="linenumber">193</context></context-group> + <source xml:space="preserve">Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s.</source> + <context-group purpose="location"><context context-type="linenumber">181</context></context-group> </trans-unit> <trans-unit id="_msg1026"> - <source xml:space="preserve">Dump file %s does not exist.</source> - <context-group purpose="location"><context context-type="linenumber">194</context></context-group> + <source xml:space="preserve"> +Unable to cleanup failed migration</source> + <context-group purpose="location"><context context-type="linenumber">205</context></context-group> </trans-unit> <trans-unit id="_msg1027"> - <source xml:space="preserve">Error creating %s</source> - <context-group purpose="location"><context context-type="linenumber">195</context></context-group> + <source xml:space="preserve"> +Unable to restore backup of wallet.</source> + <context-group purpose="location"><context context-type="linenumber">208</context></context-group> </trans-unit> <trans-unit id="_msg1028"> - <source xml:space="preserve">Error initializing block database</source> - <context-group purpose="location"><context context-type="linenumber">196</context></context-group> + <source xml:space="preserve">Config setting for %s only applied on %s network when in [%s] section.</source> + <context-group purpose="location"><context context-type="linenumber">218</context></context-group> </trans-unit> <trans-unit id="_msg1029"> - <source xml:space="preserve">Error initializing wallet database environment %s!</source> - <context-group purpose="location"><context context-type="linenumber">197</context></context-group> + <source xml:space="preserve">Copyright (C) %i-%i</source> + <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> <trans-unit id="_msg1030"> - <source xml:space="preserve">Error loading %s</source> - <context-group purpose="location"><context context-type="linenumber">198</context></context-group> + <source xml:space="preserve">Corrupted block database detected</source> + <context-group purpose="location"><context context-type="linenumber">220</context></context-group> </trans-unit> <trans-unit id="_msg1031"> - <source xml:space="preserve">Error loading %s: Private keys can only be disabled during creation</source> - <context-group purpose="location"><context context-type="linenumber">199</context></context-group> + <source xml:space="preserve">Could not find asmap file %s</source> + <context-group purpose="location"><context context-type="linenumber">221</context></context-group> </trans-unit> <trans-unit id="_msg1032"> - <source xml:space="preserve">Error loading %s: Wallet corrupted</source> - <context-group purpose="location"><context context-type="linenumber">200</context></context-group> + <source xml:space="preserve">Could not parse asmap file %s</source> + <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> <trans-unit id="_msg1033"> - <source xml:space="preserve">Error loading %s: Wallet requires newer version of %s</source> - <context-group purpose="location"><context context-type="linenumber">201</context></context-group> + <source xml:space="preserve">Disk space is too low!</source> + <context-group purpose="location"><context context-type="linenumber">223</context></context-group> </trans-unit> <trans-unit id="_msg1034"> - <source xml:space="preserve">Error loading block database</source> - <context-group purpose="location"><context context-type="linenumber">202</context></context-group> + <source xml:space="preserve">Do you want to rebuild the block database now?</source> + <context-group purpose="location"><context context-type="linenumber">224</context></context-group> </trans-unit> <trans-unit id="_msg1035"> - <source xml:space="preserve">Error opening block database</source> - <context-group purpose="location"><context context-type="linenumber">203</context></context-group> + <source xml:space="preserve">Done loading</source> + <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> <trans-unit id="_msg1036"> - <source xml:space="preserve">Error reading from database, shutting down.</source> - <context-group purpose="location"><context context-type="linenumber">204</context></context-group> + <source xml:space="preserve">Dump file %s does not exist.</source> + <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> <trans-unit id="_msg1037"> - <source xml:space="preserve">Error reading next record from wallet database</source> - <context-group purpose="location"><context context-type="linenumber">205</context></context-group> + <source xml:space="preserve">Error creating %s</source> + <context-group purpose="location"><context context-type="linenumber">227</context></context-group> </trans-unit> <trans-unit id="_msg1038"> - <source xml:space="preserve">Error: Couldn't create cursor into database</source> - <context-group purpose="location"><context context-type="linenumber">206</context></context-group> + <source xml:space="preserve">Error initializing block database</source> + <context-group purpose="location"><context context-type="linenumber">228</context></context-group> </trans-unit> <trans-unit id="_msg1039"> - <source xml:space="preserve">Error: Disk space is low for %s</source> - <context-group purpose="location"><context context-type="linenumber">207</context></context-group> + <source xml:space="preserve">Error initializing wallet database environment %s!</source> + <context-group purpose="location"><context context-type="linenumber">229</context></context-group> </trans-unit> <trans-unit id="_msg1040"> - <source xml:space="preserve">Error: Dumpfile checksum does not match. Computed %s, expected %s</source> - <context-group purpose="location"><context context-type="linenumber">208</context></context-group> + <source xml:space="preserve">Error loading %s</source> + <context-group purpose="location"><context context-type="linenumber">230</context></context-group> </trans-unit> <trans-unit id="_msg1041"> - <source xml:space="preserve">Error: Got key that was not hex: %s</source> - <context-group purpose="location"><context context-type="linenumber">209</context></context-group> + <source xml:space="preserve">Error loading %s: Private keys can only be disabled during creation</source> + <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> <trans-unit id="_msg1042"> - <source xml:space="preserve">Error: Got value that was not hex: %s</source> - <context-group purpose="location"><context context-type="linenumber">210</context></context-group> + <source xml:space="preserve">Error loading %s: Wallet corrupted</source> + <context-group purpose="location"><context context-type="linenumber">232</context></context-group> </trans-unit> <trans-unit id="_msg1043"> - <source xml:space="preserve">Error: Keypool ran out, please call keypoolrefill first</source> - <context-group purpose="location"><context context-type="linenumber">211</context></context-group> + <source xml:space="preserve">Error loading %s: Wallet requires newer version of %s</source> + <context-group purpose="location"><context context-type="linenumber">233</context></context-group> </trans-unit> <trans-unit id="_msg1044"> - <source xml:space="preserve">Error: Missing checksum</source> - <context-group purpose="location"><context context-type="linenumber">212</context></context-group> + <source xml:space="preserve">Error loading block database</source> + <context-group purpose="location"><context context-type="linenumber">234</context></context-group> </trans-unit> <trans-unit id="_msg1045"> - <source xml:space="preserve">Error: No %s addresses available.</source> - <context-group purpose="location"><context context-type="linenumber">213</context></context-group> + <source xml:space="preserve">Error opening block database</source> + <context-group purpose="location"><context context-type="linenumber">235</context></context-group> </trans-unit> <trans-unit id="_msg1046"> - <source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source> - <context-group purpose="location"><context context-type="linenumber">214</context></context-group> + <source xml:space="preserve">Error reading from database, shutting down.</source> + <context-group purpose="location"><context context-type="linenumber">236</context></context-group> </trans-unit> <trans-unit id="_msg1047"> - <source xml:space="preserve">Error: Unable to write record to new wallet</source> - <context-group purpose="location"><context context-type="linenumber">215</context></context-group> + <source xml:space="preserve">Error reading next record from wallet database</source> + <context-group purpose="location"><context context-type="linenumber">237</context></context-group> </trans-unit> <trans-unit id="_msg1048"> - <source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source> - <context-group purpose="location"><context context-type="linenumber">216</context></context-group> + <source xml:space="preserve">Error: Could not add watchonly tx to watchonly wallet</source> + <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> <trans-unit id="_msg1049"> - <source xml:space="preserve">Failed to rescan the wallet during initialization</source> - <context-group purpose="location"><context context-type="linenumber">217</context></context-group> + <source xml:space="preserve">Error: Could not delete watchonly transactions</source> + <context-group purpose="location"><context context-type="linenumber">239</context></context-group> </trans-unit> <trans-unit id="_msg1050"> - <source xml:space="preserve">Failed to verify database</source> - <context-group purpose="location"><context context-type="linenumber">218</context></context-group> + <source xml:space="preserve">Error: Couldn't create cursor into database</source> + <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> <trans-unit id="_msg1051"> - <source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source> - <context-group purpose="location"><context context-type="linenumber">219</context></context-group> + <source xml:space="preserve">Error: Disk space is low for %s</source> + <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> <trans-unit id="_msg1052"> - <source xml:space="preserve">Ignoring duplicate -wallet %s.</source> - <context-group purpose="location"><context context-type="linenumber">220</context></context-group> + <source xml:space="preserve">Error: Dumpfile checksum does not match. Computed %s, expected %s</source> + <context-group purpose="location"><context context-type="linenumber">242</context></context-group> </trans-unit> <trans-unit id="_msg1053"> - <source xml:space="preserve">Importing…</source> - <context-group purpose="location"><context context-type="linenumber">221</context></context-group> + <source xml:space="preserve">Error: Failed to create new watchonly wallet</source> + <context-group purpose="location"><context context-type="linenumber">243</context></context-group> </trans-unit> <trans-unit id="_msg1054"> - <source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source> - <context-group purpose="location"><context context-type="linenumber">222</context></context-group> + <source xml:space="preserve">Error: Got key that was not hex: %s</source> + <context-group purpose="location"><context context-type="linenumber">244</context></context-group> </trans-unit> <trans-unit id="_msg1055"> - <source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source> - <context-group purpose="location"><context context-type="linenumber">223</context></context-group> + <source xml:space="preserve">Error: Got value that was not hex: %s</source> + <context-group purpose="location"><context context-type="linenumber">245</context></context-group> </trans-unit> <trans-unit id="_msg1056"> - <source xml:space="preserve">Input not found or already spent</source> - <context-group purpose="location"><context context-type="linenumber">224</context></context-group> + <source xml:space="preserve">Error: Keypool ran out, please call keypoolrefill first</source> + <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> <trans-unit id="_msg1057"> - <source xml:space="preserve">Insufficient funds</source> - <context-group purpose="location"><context context-type="linenumber">225</context></context-group> + <source xml:space="preserve">Error: Missing checksum</source> + <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> <trans-unit id="_msg1058"> - <source xml:space="preserve">Invalid -i2psam address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">226</context></context-group> + <source xml:space="preserve">Error: No %s addresses available.</source> + <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> <trans-unit id="_msg1059"> - <source xml:space="preserve">Invalid -onion address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">227</context></context-group> + <source xml:space="preserve">Error: Not all watchonly txs could be deleted</source> + <context-group purpose="location"><context context-type="linenumber">249</context></context-group> </trans-unit> <trans-unit id="_msg1060"> - <source xml:space="preserve">Invalid -proxy address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">228</context></context-group> + <source xml:space="preserve">Error: This wallet already uses SQLite</source> + <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> <trans-unit id="_msg1061"> - <source xml:space="preserve">Invalid P2P permission: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">229</context></context-group> + <source xml:space="preserve">Error: This wallet is already a descriptor wallet</source> + <context-group purpose="location"><context context-type="linenumber">251</context></context-group> </trans-unit> <trans-unit id="_msg1062"> - <source xml:space="preserve">Invalid amount for -%s=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">230</context></context-group> + <source xml:space="preserve">Error: Unable to begin reading all records in the database</source> + <context-group purpose="location"><context context-type="linenumber">252</context></context-group> </trans-unit> <trans-unit id="_msg1063"> - <source xml:space="preserve">Invalid amount for -discardfee=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">231</context></context-group> + <source xml:space="preserve">Error: Unable to make a backup of your wallet</source> + <context-group purpose="location"><context context-type="linenumber">253</context></context-group> </trans-unit> <trans-unit id="_msg1064"> - <source xml:space="preserve">Invalid amount for -fallbackfee=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">232</context></context-group> + <source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source> + <context-group purpose="location"><context context-type="linenumber">254</context></context-group> </trans-unit> <trans-unit id="_msg1065"> - <source xml:space="preserve">Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> - <context-group purpose="location"><context context-type="linenumber">233</context></context-group> + <source xml:space="preserve">Error: Unable to read all records in the database</source> + <context-group purpose="location"><context context-type="linenumber">255</context></context-group> </trans-unit> <trans-unit id="_msg1066"> - <source xml:space="preserve">Invalid netmask specified in -whitelist: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">234</context></context-group> + <source xml:space="preserve">Error: Unable to remove watchonly address book data</source> + <context-group purpose="location"><context context-type="linenumber">256</context></context-group> </trans-unit> <trans-unit id="_msg1067"> - <source xml:space="preserve">Listening for incoming connections failed (listen returned error %s)</source> - <context-group purpose="location"><context context-type="linenumber">235</context></context-group> + <source xml:space="preserve">Error: Unable to write record to new wallet</source> + <context-group purpose="location"><context context-type="linenumber">257</context></context-group> </trans-unit> <trans-unit id="_msg1068"> - <source xml:space="preserve">Loading P2P addresses…</source> - <context-group purpose="location"><context context-type="linenumber">236</context></context-group> + <source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source> + <context-group purpose="location"><context context-type="linenumber">258</context></context-group> </trans-unit> <trans-unit id="_msg1069"> - <source xml:space="preserve">Loading banlist…</source> - <context-group purpose="location"><context context-type="linenumber">237</context></context-group> + <source xml:space="preserve">Failed to rescan the wallet during initialization</source> + <context-group purpose="location"><context context-type="linenumber">259</context></context-group> </trans-unit> <trans-unit id="_msg1070"> - <source xml:space="preserve">Loading block index…</source> - <context-group purpose="location"><context context-type="linenumber">238</context></context-group> + <source xml:space="preserve">Failed to verify database</source> + <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> <trans-unit id="_msg1071"> - <source xml:space="preserve">Loading wallet…</source> - <context-group purpose="location"><context context-type="linenumber">239</context></context-group> + <source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> <trans-unit id="_msg1072"> - <source xml:space="preserve">Missing amount</source> - <context-group purpose="location"><context context-type="linenumber">240</context></context-group> + <source xml:space="preserve">Ignoring duplicate -wallet %s.</source> + <context-group purpose="location"><context context-type="linenumber">262</context></context-group> </trans-unit> <trans-unit id="_msg1073"> - <source xml:space="preserve">Missing solving data for estimating transaction size</source> - <context-group purpose="location"><context context-type="linenumber">241</context></context-group> + <source xml:space="preserve">Importing…</source> + <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> <trans-unit id="_msg1074"> - <source xml:space="preserve">Need to specify a port with -whitebind: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">242</context></context-group> + <source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source> + <context-group purpose="location"><context context-type="linenumber">264</context></context-group> </trans-unit> <trans-unit id="_msg1075"> - <source xml:space="preserve">No addresses available</source> - <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + <source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source> + <context-group purpose="location"><context context-type="linenumber">265</context></context-group> </trans-unit> <trans-unit id="_msg1076"> - <source xml:space="preserve">Not enough file descriptors available.</source> - <context-group purpose="location"><context context-type="linenumber">244</context></context-group> + <source xml:space="preserve">Input not found or already spent</source> + <context-group purpose="location"><context context-type="linenumber">266</context></context-group> </trans-unit> <trans-unit id="_msg1077"> - <source xml:space="preserve">Prune cannot be configured with a negative value.</source> - <context-group purpose="location"><context context-type="linenumber">245</context></context-group> + <source xml:space="preserve">Insufficient funds</source> + <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> <trans-unit id="_msg1078"> - <source xml:space="preserve">Prune mode is incompatible with -txindex.</source> - <context-group purpose="location"><context context-type="linenumber">246</context></context-group> + <source xml:space="preserve">Invalid -i2psam address or hostname: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">268</context></context-group> </trans-unit> <trans-unit id="_msg1079"> - <source xml:space="preserve">Pruning blockstore…</source> - <context-group purpose="location"><context context-type="linenumber">247</context></context-group> + <source xml:space="preserve">Invalid -onion address or hostname: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">269</context></context-group> </trans-unit> <trans-unit id="_msg1080"> - <source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source> - <context-group purpose="location"><context context-type="linenumber">248</context></context-group> + <source xml:space="preserve">Invalid -proxy address or hostname: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">270</context></context-group> </trans-unit> <trans-unit id="_msg1081"> - <source xml:space="preserve">Replaying blocks…</source> - <context-group purpose="location"><context context-type="linenumber">249</context></context-group> + <source xml:space="preserve">Invalid P2P permission: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">271</context></context-group> </trans-unit> <trans-unit id="_msg1082"> - <source xml:space="preserve">Rescanning…</source> - <context-group purpose="location"><context context-type="linenumber">250</context></context-group> + <source xml:space="preserve">Invalid amount for -%s=<amount>: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">272</context></context-group> </trans-unit> <trans-unit id="_msg1083"> - <source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source> - <context-group purpose="location"><context context-type="linenumber">251</context></context-group> + <source xml:space="preserve">Invalid amount for -discardfee=<amount>: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">273</context></context-group> </trans-unit> <trans-unit id="_msg1084"> - <source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source> - <context-group purpose="location"><context context-type="linenumber">252</context></context-group> + <source xml:space="preserve">Invalid amount for -fallbackfee=<amount>: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> <trans-unit id="_msg1085"> - <source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source> - <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + <source xml:space="preserve">Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> + <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> <trans-unit id="_msg1086"> - <source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source> - <context-group purpose="location"><context context-type="linenumber">254</context></context-group> + <source xml:space="preserve">Invalid netmask specified in -whitelist: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">276</context></context-group> </trans-unit> <trans-unit id="_msg1087"> - <source xml:space="preserve">Section [%s] is not recognized.</source> - <context-group purpose="location"><context context-type="linenumber">255</context></context-group> + <source xml:space="preserve">Listening for incoming connections failed (listen returned error %s)</source> + <context-group purpose="location"><context context-type="linenumber">277</context></context-group> </trans-unit> <trans-unit id="_msg1088"> - <source xml:space="preserve">Signing transaction failed</source> - <context-group purpose="location"><context context-type="linenumber">256</context></context-group> + <source xml:space="preserve">Loading P2P addresses…</source> + <context-group purpose="location"><context context-type="linenumber">278</context></context-group> </trans-unit> <trans-unit id="_msg1089"> - <source xml:space="preserve">Specified -walletdir "%s" does not exist</source> - <context-group purpose="location"><context context-type="linenumber">257</context></context-group> + <source xml:space="preserve">Loading banlist…</source> + <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> <trans-unit id="_msg1090"> - <source xml:space="preserve">Specified -walletdir "%s" is a relative path</source> - <context-group purpose="location"><context context-type="linenumber">258</context></context-group> + <source xml:space="preserve">Loading block index…</source> + <context-group purpose="location"><context context-type="linenumber">280</context></context-group> </trans-unit> <trans-unit id="_msg1091"> - <source xml:space="preserve">Specified -walletdir "%s" is not a directory</source> - <context-group purpose="location"><context context-type="linenumber">259</context></context-group> + <source xml:space="preserve">Loading wallet…</source> + <context-group purpose="location"><context context-type="linenumber">281</context></context-group> </trans-unit> <trans-unit id="_msg1092"> - <source xml:space="preserve">Specified blocks directory "%s" does not exist.</source> - <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <source xml:space="preserve">Missing amount</source> + <context-group purpose="location"><context context-type="linenumber">282</context></context-group> </trans-unit> <trans-unit id="_msg1093"> - <source xml:space="preserve">Starting network threads…</source> - <context-group purpose="location"><context context-type="linenumber">261</context></context-group> + <source xml:space="preserve">Missing solving data for estimating transaction size</source> + <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> <trans-unit id="_msg1094"> - <source xml:space="preserve">The source code is available from %s.</source> - <context-group purpose="location"><context context-type="linenumber">262</context></context-group> + <source xml:space="preserve">Need to specify a port with -whitebind: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> <trans-unit id="_msg1095"> - <source xml:space="preserve">The specified config file %s does not exist</source> - <context-group purpose="location"><context context-type="linenumber">263</context></context-group> + <source xml:space="preserve">No addresses available</source> + <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> <trans-unit id="_msg1096"> - <source xml:space="preserve">The transaction amount is too small to pay the fee</source> - <context-group purpose="location"><context context-type="linenumber">264</context></context-group> + <source xml:space="preserve">Not enough file descriptors available.</source> + <context-group purpose="location"><context context-type="linenumber">286</context></context-group> </trans-unit> <trans-unit id="_msg1097"> - <source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source> - <context-group purpose="location"><context context-type="linenumber">265</context></context-group> + <source xml:space="preserve">Prune cannot be configured with a negative value.</source> + <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> <trans-unit id="_msg1098"> - <source xml:space="preserve">This is experimental software.</source> - <context-group purpose="location"><context context-type="linenumber">266</context></context-group> + <source xml:space="preserve">Prune mode is incompatible with -txindex.</source> + <context-group purpose="location"><context context-type="linenumber">288</context></context-group> </trans-unit> <trans-unit id="_msg1099"> - <source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source> - <context-group purpose="location"><context context-type="linenumber">267</context></context-group> + <source xml:space="preserve">Pruning blockstore…</source> + <context-group purpose="location"><context context-type="linenumber">289</context></context-group> </trans-unit> <trans-unit id="_msg1100"> - <source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source> - <context-group purpose="location"><context context-type="linenumber">268</context></context-group> + <source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source> + <context-group purpose="location"><context context-type="linenumber">290</context></context-group> </trans-unit> <trans-unit id="_msg1101"> - <source xml:space="preserve">Transaction amount too small</source> - <context-group purpose="location"><context context-type="linenumber">269</context></context-group> + <source xml:space="preserve">Replaying blocks…</source> + <context-group purpose="location"><context context-type="linenumber">291</context></context-group> </trans-unit> <trans-unit id="_msg1102"> - <source xml:space="preserve">Transaction amounts must not be negative</source> - <context-group purpose="location"><context context-type="linenumber">270</context></context-group> + <source xml:space="preserve">Rescanning…</source> + <context-group purpose="location"><context context-type="linenumber">292</context></context-group> </trans-unit> <trans-unit id="_msg1103"> - <source xml:space="preserve">Transaction change output index out of range</source> - <context-group purpose="location"><context context-type="linenumber">271</context></context-group> + <source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source> + <context-group purpose="location"><context context-type="linenumber">293</context></context-group> </trans-unit> <trans-unit id="_msg1104"> - <source xml:space="preserve">Transaction has too long of a mempool chain</source> - <context-group purpose="location"><context context-type="linenumber">272</context></context-group> + <source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source> + <context-group purpose="location"><context context-type="linenumber">294</context></context-group> </trans-unit> <trans-unit id="_msg1105"> - <source xml:space="preserve">Transaction must have at least one recipient</source> - <context-group purpose="location"><context context-type="linenumber">273</context></context-group> + <source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source> + <context-group purpose="location"><context context-type="linenumber">295</context></context-group> </trans-unit> <trans-unit id="_msg1106"> - <source xml:space="preserve">Transaction needs a change address, but we can't generate it.</source> - <context-group purpose="location"><context context-type="linenumber">274</context></context-group> + <source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source> + <context-group purpose="location"><context context-type="linenumber">296</context></context-group> </trans-unit> <trans-unit id="_msg1107"> - <source xml:space="preserve">Transaction too large</source> - <context-group purpose="location"><context context-type="linenumber">275</context></context-group> + <source xml:space="preserve">Section [%s] is not recognized.</source> + <context-group purpose="location"><context context-type="linenumber">297</context></context-group> </trans-unit> <trans-unit id="_msg1108"> - <source xml:space="preserve">Unable to allocate memory for -maxsigcachesize: '%s' MiB</source> - <context-group purpose="location"><context context-type="linenumber">276</context></context-group> + <source xml:space="preserve">Signing transaction failed</source> + <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> <trans-unit id="_msg1109"> - <source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source> - <context-group purpose="location"><context context-type="linenumber">277</context></context-group> + <source xml:space="preserve">Specified -walletdir "%s" does not exist</source> + <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> <trans-unit id="_msg1110"> - <source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source> - <context-group purpose="location"><context context-type="linenumber">278</context></context-group> + <source xml:space="preserve">Specified -walletdir "%s" is a relative path</source> + <context-group purpose="location"><context context-type="linenumber">300</context></context-group> </trans-unit> <trans-unit id="_msg1111"> - <source xml:space="preserve">Unable to create the PID file '%s': %s</source> - <context-group purpose="location"><context context-type="linenumber">279</context></context-group> + <source xml:space="preserve">Specified -walletdir "%s" is not a directory</source> + <context-group purpose="location"><context context-type="linenumber">301</context></context-group> </trans-unit> <trans-unit id="_msg1112"> - <source xml:space="preserve">Unable to find UTXO for external input</source> - <context-group purpose="location"><context context-type="linenumber">280</context></context-group> + <source xml:space="preserve">Specified blocks directory "%s" does not exist.</source> + <context-group purpose="location"><context context-type="linenumber">302</context></context-group> </trans-unit> <trans-unit id="_msg1113"> - <source xml:space="preserve">Unable to generate initial keys</source> - <context-group purpose="location"><context context-type="linenumber">281</context></context-group> + <source xml:space="preserve">Starting network threads…</source> + <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> <trans-unit id="_msg1114"> - <source xml:space="preserve">Unable to generate keys</source> - <context-group purpose="location"><context context-type="linenumber">282</context></context-group> + <source xml:space="preserve">The source code is available from %s.</source> + <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> <trans-unit id="_msg1115"> - <source xml:space="preserve">Unable to open %s for writing</source> - <context-group purpose="location"><context context-type="linenumber">283</context></context-group> + <source xml:space="preserve">The specified config file %s does not exist</source> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> <trans-unit id="_msg1116"> - <source xml:space="preserve">Unable to parse -maxuploadtarget: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">284</context></context-group> + <source xml:space="preserve">The transaction amount is too small to pay the fee</source> + <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> <trans-unit id="_msg1117"> - <source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source> - <context-group purpose="location"><context context-type="linenumber">285</context></context-group> + <source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source> + <context-group purpose="location"><context context-type="linenumber">307</context></context-group> </trans-unit> <trans-unit id="_msg1118"> - <source xml:space="preserve">Unknown -blockfilterindex value %s.</source> - <context-group purpose="location"><context context-type="linenumber">286</context></context-group> + <source xml:space="preserve">This is experimental software.</source> + <context-group purpose="location"><context context-type="linenumber">308</context></context-group> </trans-unit> <trans-unit id="_msg1119"> - <source xml:space="preserve">Unknown address type '%s'</source> - <context-group purpose="location"><context context-type="linenumber">287</context></context-group> + <source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source> + <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> <trans-unit id="_msg1120"> - <source xml:space="preserve">Unknown change type '%s'</source> - <context-group purpose="location"><context context-type="linenumber">288</context></context-group> + <source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source> + <context-group purpose="location"><context context-type="linenumber">310</context></context-group> </trans-unit> <trans-unit id="_msg1121"> - <source xml:space="preserve">Unknown network specified in -onlynet: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">289</context></context-group> + <source xml:space="preserve">Transaction amount too small</source> + <context-group purpose="location"><context context-type="linenumber">311</context></context-group> </trans-unit> <trans-unit id="_msg1122"> - <source xml:space="preserve">Unknown new rules activated (versionbit %i)</source> - <context-group purpose="location"><context context-type="linenumber">290</context></context-group> + <source xml:space="preserve">Transaction amounts must not be negative</source> + <context-group purpose="location"><context context-type="linenumber">312</context></context-group> </trans-unit> <trans-unit id="_msg1123"> - <source xml:space="preserve">Unsupported logging category %s=%s.</source> - <context-group purpose="location"><context context-type="linenumber">291</context></context-group> + <source xml:space="preserve">Transaction change output index out of range</source> + <context-group purpose="location"><context context-type="linenumber">313</context></context-group> </trans-unit> <trans-unit id="_msg1124"> - <source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source> - <context-group purpose="location"><context context-type="linenumber">292</context></context-group> + <source xml:space="preserve">Transaction has too long of a mempool chain</source> + <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> <trans-unit id="_msg1125"> - <source xml:space="preserve">Verifying blocks…</source> - <context-group purpose="location"><context context-type="linenumber">293</context></context-group> + <source xml:space="preserve">Transaction must have at least one recipient</source> + <context-group purpose="location"><context context-type="linenumber">315</context></context-group> </trans-unit> <trans-unit id="_msg1126"> - <source xml:space="preserve">Verifying wallet(s)…</source> - <context-group purpose="location"><context context-type="linenumber">294</context></context-group> + <source xml:space="preserve">Transaction needs a change address, but we can't generate it.</source> + <context-group purpose="location"><context context-type="linenumber">316</context></context-group> </trans-unit> <trans-unit id="_msg1127"> + <source xml:space="preserve">Transaction too large</source> + <context-group purpose="location"><context context-type="linenumber">317</context></context-group> + </trans-unit> + <trans-unit id="_msg1128"> + <source xml:space="preserve">Unable to allocate memory for -maxsigcachesize: '%s' MiB</source> + <context-group purpose="location"><context context-type="linenumber">318</context></context-group> + </trans-unit> + <trans-unit id="_msg1129"> + <source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source> + <context-group purpose="location"><context context-type="linenumber">319</context></context-group> + </trans-unit> + <trans-unit id="_msg1130"> + <source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source> + <context-group purpose="location"><context context-type="linenumber">320</context></context-group> + </trans-unit> + <trans-unit id="_msg1131"> + <source xml:space="preserve">Unable to create the PID file '%s': %s</source> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> + </trans-unit> + <trans-unit id="_msg1132"> + <source xml:space="preserve">Unable to find UTXO for external input</source> + <context-group purpose="location"><context context-type="linenumber">322</context></context-group> + </trans-unit> + <trans-unit id="_msg1133"> + <source xml:space="preserve">Unable to generate initial keys</source> + <context-group purpose="location"><context context-type="linenumber">323</context></context-group> + </trans-unit> + <trans-unit id="_msg1134"> + <source xml:space="preserve">Unable to generate keys</source> + <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + </trans-unit> + <trans-unit id="_msg1135"> + <source xml:space="preserve">Unable to open %s for writing</source> + <context-group purpose="location"><context context-type="linenumber">325</context></context-group> + </trans-unit> + <trans-unit id="_msg1136"> + <source xml:space="preserve">Unable to parse -maxuploadtarget: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">326</context></context-group> + </trans-unit> + <trans-unit id="_msg1137"> + <source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source> + <context-group purpose="location"><context context-type="linenumber">327</context></context-group> + </trans-unit> + <trans-unit id="_msg1138"> + <source xml:space="preserve">Unable to unload the wallet before migrating</source> + <context-group purpose="location"><context context-type="linenumber">328</context></context-group> + </trans-unit> + <trans-unit id="_msg1139"> + <source xml:space="preserve">Unknown -blockfilterindex value %s.</source> + <context-group purpose="location"><context context-type="linenumber">329</context></context-group> + </trans-unit> + <trans-unit id="_msg1140"> + <source xml:space="preserve">Unknown address type '%s'</source> + <context-group purpose="location"><context context-type="linenumber">330</context></context-group> + </trans-unit> + <trans-unit id="_msg1141"> + <source xml:space="preserve">Unknown change type '%s'</source> + <context-group purpose="location"><context context-type="linenumber">331</context></context-group> + </trans-unit> + <trans-unit id="_msg1142"> + <source xml:space="preserve">Unknown network specified in -onlynet: '%s'</source> + <context-group purpose="location"><context context-type="linenumber">332</context></context-group> + </trans-unit> + <trans-unit id="_msg1143"> + <source xml:space="preserve">Unknown new rules activated (versionbit %i)</source> + <context-group purpose="location"><context context-type="linenumber">333</context></context-group> + </trans-unit> + <trans-unit id="_msg1144"> + <source xml:space="preserve">Unsupported global logging level -loglevel=%s. Valid values: %s.</source> + <context-group purpose="location"><context context-type="linenumber">334</context></context-group> + </trans-unit> + <trans-unit id="_msg1145"> + <source xml:space="preserve">Unsupported logging category %s=%s.</source> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> + </trans-unit> + <trans-unit id="_msg1146"> + <source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source> + <context-group purpose="location"><context context-type="linenumber">336</context></context-group> + </trans-unit> + <trans-unit id="_msg1147"> + <source xml:space="preserve">Verifying blocks…</source> + <context-group purpose="location"><context context-type="linenumber">337</context></context-group> + </trans-unit> + <trans-unit id="_msg1148"> + <source xml:space="preserve">Verifying wallet(s)…</source> + <context-group purpose="location"><context context-type="linenumber">338</context></context-group> + </trans-unit> + <trans-unit id="_msg1149"> <source xml:space="preserve">Wallet needed to be rewritten: restart %s to complete</source> - <context-group purpose="location"><context context-type="linenumber">295</context></context-group> + <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> </group> </body></file> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 2b6711ca40..7c91eac37e 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -91,7 +91,9 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->thirdPartyTxUrls->setVisible(false); } -#ifndef ENABLE_EXTERNAL_SIGNER +#ifdef ENABLE_EXTERNAL_SIGNER + ui->externalSignerPath->setToolTip(ui->externalSignerPath->toolTip().arg(PACKAGE_NAME)); +#else //: "External signing" means using devices such as hardware wallets. ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)")); ui->externalSignerPath->setEnabled(false); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 295450d6b7..a07686ab2b 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1178,8 +1178,12 @@ void RPCConsole::updateDetailWidget() ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time)); ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time)); ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); - ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); - ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); + if (stats->nodeStats.nVersion) { + ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); + } + if (!stats->nodeStats.cleanSubVer.empty()) { + ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); + } ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /*prepend_direction=*/true)); ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network)); if (stats->nodeStats.m_permission_flags == NetPermissionFlags::None) { diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 4894cac905..331487b51d 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -17,7 +17,7 @@ #include <util/system.h> #include <util/strencodings.h> -#include <stdio.h> +#include <cstdio> #include <QCloseEvent> #include <QLabel> diff --git a/src/random.cpp b/src/random.cpp index f92e679a00..eab54630b1 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -21,7 +21,7 @@ #include <util/time.h> // for GetTimeMicros() #include <cmath> -#include <stdlib.h> +#include <cstdlib> #include <thread> #ifndef WIN32 diff --git a/src/rest.cpp b/src/rest.cpp index 7f00db2222..a10d8a433f 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -590,6 +590,48 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std: } } + +RPCHelpMan getdeploymentinfo(); + +static bool rest_deploymentinfo(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) +{ + if (!CheckWarmup(req)) return false; + + std::string hash_str; + const RESTResponseFormat rf = ParseDataFormat(hash_str, str_uri_part); + + switch (rf) { + case RESTResponseFormat::JSON: { + JSONRPCRequest jsonRequest; + jsonRequest.context = context; + jsonRequest.params = UniValue(UniValue::VARR); + + if (!hash_str.empty()) { + uint256 hash; + if (!ParseHashStr(hash_str, hash)) { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str); + } + + const ChainstateManager* chainman = GetChainman(context, req); + if (!chainman) return false; + if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(ParseHashV(hash_str, "blockhash")))) { + return RESTERR(req, HTTP_BAD_REQUEST, "Block not found"); + } + + jsonRequest.params.pushKV("blockhash", hash_str); + } + + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, getdeploymentinfo().HandleRequest(jsonRequest).write() + "\n"); + return true; + } + default: { + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); + } + } + +} + static bool rest_mempool(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) { if (!CheckWarmup(req)) @@ -939,6 +981,8 @@ static const struct { {"/rest/mempool/", rest_mempool}, {"/rest/headers/", rest_headers}, {"/rest/getutxos", rest_getutxos}, + {"/rest/deploymentinfo/", rest_deploymentinfo}, + {"/rest/deploymentinfo", rest_deploymentinfo}, {"/rest/blockhashbyheight/", rest_blockhash_by_height}, }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f57915e805..bc1028aec3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -459,6 +459,12 @@ static RPCHelpMan getblockfrompeer() throw JSONRPCError(RPC_MISC_ERROR, "Block header missing"); } + // Fetching blocks before the node has syncing past their height can prevent block files from + // being pruned, so we avoid it if the node is in prune mode. + if (node::fPruneMode && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) { + throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer"); + } + const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA); if (block_has_data) { throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded"); @@ -767,7 +773,7 @@ static RPCHelpMan pruneblockchain() ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; int heightParam = request.params[0].getInt<int>(); @@ -833,9 +839,9 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::B // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) { if (pindex) { - return g_coin_stats_index->LookUpStats(pindex); + return g_coin_stats_index->LookUpStats(*pindex); } else { - CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()))); return g_coin_stats_index->LookUpStats(block_index); } } @@ -856,7 +862,7 @@ static RPCHelpMan gettxoutsetinfo() "Note this call may take some time if you are not using coinstatsindex.\n", { {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, - {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", RPCArgOptions{.type_str={"", "string or numeric"}}}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -908,7 +914,7 @@ static RPCHelpMan gettxoutsetinfo() NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); CCoinsView* coins_view; @@ -1048,7 +1054,7 @@ static RPCHelpMan gettxout() fMempool = request.params[2].get_bool(); Coin coin; - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CCoinsViewCache* coins_view = &active_chainstate.CoinsTip(); if (fMempool) { @@ -1105,7 +1111,7 @@ static RPCHelpMan verifychain() ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, @@ -1233,7 +1239,7 @@ RPCHelpMan getblockchaininfo() const ArgsManager& args{EnsureAnyArgsman(request.context)}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; const int height{tip.nHeight}; @@ -1307,7 +1313,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& } } // anon namespace -static RPCHelpMan getdeploymentinfo() +RPCHelpMan getdeploymentinfo() { return RPCHelpMan{"getdeploymentinfo", "Returns an object containing various state info regarding deployments of consensus changes.", @@ -1328,7 +1334,7 @@ static RPCHelpMan getdeploymentinfo() { const ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - const CChainState& active_chainstate = chainman.ActiveChainstate(); + const Chainstate& active_chainstate = chainman.ActiveChainstate(); const CBlockIndex* blockindex; if (request.params[0].isNull()) { @@ -1726,13 +1732,13 @@ static RPCHelpMan getblockstats() "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", RPCArgOptions{.type_str={"", "string or numeric"}}}, {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, }, - "stats"}, + RPCArgOptions{.oneline_description="stats"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2019,6 +2025,40 @@ public: } }; +static const auto scan_action_arg_desc = RPCArg{ + "action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n" + "\"start\" for starting a scan\n" + "\"abort\" for aborting the current scan (returns true when abort was successful)\n" + "\"status\" for progress report (in %) of the current scan" +}; + +static const auto scan_objects_arg_desc = RPCArg{ + "scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" + "Every scan object is either a string descriptor or an object:", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", + { + {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, + {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"}, + }}, + }, + RPCArgOptions{.oneline_description="[scanobjects,...]"}, +}; + +static const auto scan_result_abort = RPCResult{ + "when action=='abort'", RPCResult::Type::BOOL, "success", + "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort" +}; +static const auto scan_result_status_none = RPCResult{ + "when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", "" +}; +static const auto scan_result_status_some = RPCResult{ + "when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", + {{RPCResult::Type::NUM, "progress", "Approximate percent complete"},} +}; + + static RPCHelpMan scantxoutset() { // scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S @@ -2038,21 +2078,8 @@ static RPCHelpMan scantxoutset() "In the latter case, a range needs to be specified by below if different from 1000.\n" "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n", { - {"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n" - "\"start\" for starting a scan\n" - "\"abort\" for aborting the current scan (returns true when abort was successful)\n" - "\"status\" for progress report (in %) of the current scan"}, - {"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" - "Every scan object is either a string descriptor or an object:", - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", - { - {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, - {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"}, - }}, - }, - "[scanobjects,...]"}, + scan_action_arg_desc, + scan_objects_arg_desc, }, { RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", { @@ -2069,17 +2096,15 @@ static RPCHelpMan scantxoutset() {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"}, {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"}, + {RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"}, {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, }}, }}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, }}, - RPCResult{"when action=='abort'", RPCResult::Type::BOOL, "success", "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"}, - RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::NUM, "progress", "Approximate percent complete"}, - }}, - RPCResult{"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""}, + scan_result_abort, + scan_result_status_some, + scan_result_status_none, }, RPCExamples{ HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") + @@ -2148,7 +2173,7 @@ static RPCHelpMan scantxoutset() { ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor()); tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); @@ -2172,6 +2197,7 @@ static RPCHelpMan scantxoutset() unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey)); unspent.pushKV("desc", descriptors[txo.scriptPubKey]); unspent.pushKV("amount", ValueFromAmount(txo.nValue)); + unspent.pushKV("coinbase", coin.IsCoinBase()); unspent.pushKV("height", (int32_t)coin.nHeight); unspents.push_back(unspent); @@ -2186,6 +2212,203 @@ static RPCHelpMan scantxoutset() }; } +/** RAII object to prevent concurrency issue when scanning blockfilters */ +static std::atomic<int> g_scanfilter_progress; +static std::atomic<int> g_scanfilter_progress_height; +static std::atomic<bool> g_scanfilter_in_progress; +static std::atomic<bool> g_scanfilter_should_abort_scan; +class BlockFiltersScanReserver +{ +private: + bool m_could_reserve{false}; +public: + explicit BlockFiltersScanReserver() = default; + + bool reserve() { + CHECK_NONFATAL(!m_could_reserve); + if (g_scanfilter_in_progress.exchange(true)) { + return false; + } + m_could_reserve = true; + return true; + } + + ~BlockFiltersScanReserver() { + if (m_could_reserve) { + g_scanfilter_in_progress = false; + } + } +}; + +static RPCHelpMan scanblocks() +{ + return RPCHelpMan{"scanblocks", + "\nReturn relevant blockhashes for given descriptors.\n" + "This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", + { + scan_action_arg_desc, + scan_objects_arg_desc, + RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"}, + RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"}, + RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"} + }, + { + scan_result_status_none, + RPCResult{"When action=='start'", RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::NUM, "from_height", "The height we started the scan from"}, + {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"}, + {RPCResult::Type::ARR, "relevant_blocks", "", {{RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},}}, + }, + }, + RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::NUM, "progress", "Approximate percent complete"}, + {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"}, + }, + }, + scan_result_abort, + }, + RPCExamples{ + HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") + + HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") + + HelpExampleCli("scanblocks", "status") + + HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") + + HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") + + HelpExampleRpc("scanblocks", "\"status\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue ret(UniValue::VOBJ); + if (request.params[0].get_str() == "status") { + BlockFiltersScanReserver reserver; + if (reserver.reserve()) { + // no scan in progress + return NullUniValue; + } + ret.pushKV("progress", g_scanfilter_progress.load()); + ret.pushKV("current_height", g_scanfilter_progress_height.load()); + return ret; + } else if (request.params[0].get_str() == "abort") { + BlockFiltersScanReserver reserver; + if (reserver.reserve()) { + // reserve was possible which means no scan was running + return false; + } + // set the abort flag + g_scanfilter_should_abort_scan = true; + return true; + } + else if (request.params[0].get_str() == "start") { + BlockFiltersScanReserver reserver; + if (!reserver.reserve()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); + } + const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()}; + + BlockFilterType filtertype; + if (!BlockFilterTypeByName(filtertype_name, filtertype)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype"); + } + + BlockFilterIndex* index = GetBlockFilterIndex(filtertype); + if (!index) { + throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name); + } + + NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); + + // set the start-height + const CBlockIndex* block = nullptr; + const CBlockIndex* stop_block = nullptr; + { + LOCK(cs_main); + CChain& active_chain = chainman.ActiveChain(); + block = active_chain.Genesis(); + stop_block = active_chain.Tip(); + if (!request.params[2].isNull()) { + block = active_chain[request.params[2].getInt<int>()]; + if (!block) { + throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height"); + } + } + if (!request.params[3].isNull()) { + stop_block = active_chain[request.params[3].getInt<int>()]; + if (!stop_block || stop_block->nHeight < block->nHeight) { + throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height"); + } + } + } + CHECK_NONFATAL(block); + + // loop through the scan objects, add scripts to the needle_set + GCSFilter::ElementSet needle_set; + for (const UniValue& scanobject : request.params[1].get_array().getValues()) { + FlatSigningProvider provider; + std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider); + for (const CScript& script : scripts) { + needle_set.emplace(script.begin(), script.end()); + } + } + UniValue blocks(UniValue::VARR); + const int amount_per_chunk = 10000; + const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range + std::vector<BlockFilter> filters; + const CBlockIndex* start_block = block; // for progress reporting + const int total_blocks_to_process = stop_block->nHeight - start_block->nHeight; + + g_scanfilter_should_abort_scan = false; + g_scanfilter_progress = 0; + g_scanfilter_progress_height = start_block->nHeight; + + while (block) { + node.rpc_interruption_point(); // allow a clean shutdown + if (g_scanfilter_should_abort_scan) { + LogPrintf("scanblocks RPC aborted at height %d.\n", block->nHeight); + break; + } + const CBlockIndex* next = nullptr; + { + LOCK(cs_main); + CChain& active_chain = chainman.ActiveChain(); + next = active_chain.Next(block); + if (block == stop_block) next = nullptr; + } + if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr) { + LogPrint(BCLog::RPC, "Fetching blockfilters from height %d to height %d.\n", start_index->nHeight, block->nHeight); + if (index->LookupFilterRange(start_index->nHeight, block, filters)) { + for (const BlockFilter& filter : filters) { + // compare the elements-set with each filter + if (filter.GetFilter().MatchAny(needle_set)) { + blocks.push_back(filter.GetBlockHash().GetHex()); + LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex()); + } + } + } + start_index = block; + + // update progress + int blocks_processed = block->nHeight - start_block->nHeight; + if (total_blocks_to_process > 0) { // avoid division by zero + g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed); + } else { + g_scanfilter_progress = 100; + } + g_scanfilter_progress_height = block->nHeight; + } + block = next; + } + ret.pushKV("from_height", start_block->nHeight); + ret.pushKV("to_height", g_scanfilter_progress_height.load()); + ret.pushKV("relevant_blocks", blocks); + } + else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + } + return ret; +}, + }; +} + static RPCHelpMan getblockfilter() { return RPCHelpMan{"getblockfilter", @@ -2328,7 +2551,7 @@ static RPCHelpMan dumptxoutset() UniValue CreateUTXOSnapshot( NodeContext& node, - CChainState& chainstate, + Chainstate& chainstate, AutoFile& afile, const fs::path& path, const fs::path& temppath) @@ -2421,6 +2644,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) {"blockchain", &verifychain}, {"blockchain", &preciousblock}, {"blockchain", &scantxoutset}, + {"blockchain", &scanblocks}, {"blockchain", &getblockfilter}, {"hidden", &invalidateblock}, {"hidden", &reconsiderblock}, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index a332fd4892..6cdb5fa48b 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -20,7 +20,7 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; -class CChainState; +class Chainstate; class UniValue; namespace node { struct NodeContext; @@ -54,7 +54,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], */ UniValue CreateUTXOSnapshot( node::NodeContext& node, - CChainState& chainstate, + Chainstate& chainstate, AutoFile& afile, const fs::path& path, const fs::path& tmppath); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 612dbbdacf..8688263ef5 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -83,6 +83,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 8, "fee_rate"}, { "sendmany", 9, "verbose" }, { "deriveaddresses", 1, "range" }, + { "scanblocks", 1, "scanobjects" }, + { "scanblocks", 2, "start_height" }, + { "scanblocks", 3, "stop_height" }, { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index aa047bdea8..e50bf00473 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -64,7 +64,6 @@ static RPCHelpMan estimatesmartfee() [&](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); @@ -157,7 +156,6 @@ static RPCHelpMan estimaterawfee() [&](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); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index db09a0c7b6..706d783942 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -168,7 +168,7 @@ static RPCHelpMan testmempoolaccept() NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); ChainstateManager& chainman = EnsureChainman(node); - CChainState& chainstate = chainman.ActiveChainstate(); + Chainstate& chainstate = chainman.ActiveChainstate(); const PackageMempoolAcceptResult package_result = [&] { LOCK(::cs_main); if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true); @@ -449,9 +449,8 @@ static RPCHelpMan getmempoolancestors() } CTxMemPool::setEntries setAncestors; - uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); + mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false); if (!fVerbose) { UniValue o(UniValue::VARR); @@ -605,8 +604,7 @@ static RPCHelpMan gettxspendingprevout() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheckArgument(request.params[0], UniValue::VARR); - const UniValue& output_params = request.params[0]; + const UniValue& output_params = request.params[0].get_array(); if (output_params.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing"); } @@ -810,7 +808,7 @@ static RPCHelpMan submitpackage() NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); - CChainState& chainstate = EnsureChainman(node).ActiveChainstate(); + Chainstate& chainstate = EnsureChainman(node).ActiveChainstate(); const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false)); // First catch any errors. diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 1ad704a490..98383fdaca 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -524,7 +524,7 @@ static RPCHelpMan getblocktemplate() {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"}, }}, }, - "\"template_request\""}, + RPCArgOptions{.oneline_description="\"template_request\""}}, }, { RPCResult{"If the proposal was accepted with mode=='proposal'", RPCResult::Type::NONE, "", ""}, @@ -598,7 +598,7 @@ static RPCHelpMan getblocktemplate() std::string strMode = "template"; UniValue lpval = NullUniValue; std::set<std::string> setClientRules; - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; if (!request.params[0].isNull()) { @@ -730,10 +730,10 @@ static RPCHelpMan getblocktemplate() // Update block static CBlockIndex* pindexPrev; - static int64_t nStart; + static int64_t time_start; static std::unique_ptr<CBlockTemplate> pblocktemplate; if (pindexPrev != active_chain.Tip() || - (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on pindexPrev = nullptr; @@ -741,7 +741,7 @@ static RPCHelpMan getblocktemplate() // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = active_chain.Tip(); - nStart = GetTime(); + time_start = GetTime(); // Create new block CScript scriptDummy = CScript() << OP_TRUE; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index e221b3462d..8d7f4e7f5b 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -114,7 +114,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, - {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"}, + {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether we relay transactions to this peer"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, {RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"}, @@ -123,9 +123,9 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::NUM, "bytesrecv", "The total bytes received"}, {RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"}, {RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"}, - {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "ping time (if available)"}, - {RPCResult::Type::NUM, "minping", /*optional=*/true, "minimum observed ping time (if any at all)"}, - {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "ping wait (if non-zero)"}, + {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "The last ping time in milliseconds (ms), if any"}, + {RPCResult::Type::NUM, "minping", /*optional=*/true, "The minimum observed ping time in milliseconds (ms), if any"}, + {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "The duration in milliseconds (ms) of an outstanding ping (if non-zero)"}, {RPCResult::Type::NUM, "version", "The peer version, such as 70001"}, {RPCResult::Type::STR, "subver", "The string version"}, {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"}, @@ -146,7 +146,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, }}, - {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"}, + {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"}, {RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "", { {RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n" @@ -200,6 +200,9 @@ static RPCHelpMan getpeerinfo() ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE}; obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); + if (fStateStats) { + obj.pushKV("relaytxes", statestats.m_relay_txs); + } obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time)); @@ -235,8 +238,6 @@ static RPCHelpMan getpeerinfo() heights.push_back(height); } obj.pushKV("inflight", heights); - obj.pushKV("relaytxes", statestats.m_relay_txs); - obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received)); obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled); obj.pushKV("addr_processed", statestats.m_addr_processed); obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); @@ -246,6 +247,7 @@ static RPCHelpMan getpeerinfo() permissions.push_back(permission); } obj.pushKV("permissions", permissions); + obj.pushKV("minfeefilter", fStateStats ? ValueFromAmount(statestats.m_fee_filter_received) : 0); UniValue sendPerMsgType(UniValue::VOBJ); for (const auto& i : stats.mapSendBytesPerMsgType) { @@ -947,7 +949,8 @@ static RPCHelpMan addpeeraddress() bool success{false}; if (LookupHost(addr_string, net_addr, false)) { - CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; + CService service{net_addr, port}; + CAddress address{MaybeFlipIPv6toCJDNS(service), ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; address.nTime = Now<NodeSeconds>(); // The source address is set equal to the address. This is equivalent to the peer // announcing itself. diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 744f809814..a980c609e8 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -273,7 +273,7 @@ static RPCHelpMan deriveaddresses() UniValue addresses(UniValue::VARR); - for (int i = range_begin; i <= range_end; ++i) { + for (int64_t i = range_begin; i <= range_end; ++i) { FlatSigningProvider provider; std::vector<CScript> scripts; if (!desc->Expand(i, key_provider, scripts, provider)) { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 7ffb499330..d654de1862 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -51,7 +51,7 @@ using node::GetTransaction; using node::NodeContext; using node::PSBTAnalysis; -static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate) +static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // @@ -1241,13 +1241,9 @@ static RPCHelpMan decodepsbt() } // Taproot tree - if (output.m_tap_tree.has_value()) { + if (!output.m_tap_tree.empty()) { UniValue tree(UniValue::VARR); - const auto& tuples = output.m_tap_tree->GetTreeTuples(); - for (const auto& tuple : tuples) { - uint8_t depth = std::get<0>(tuple); - uint8_t leaf_ver = std::get<1>(tuple); - CScript script = std::get<2>(tuple); + for (const auto& [depth, leaf_ver, script] : output.m_tap_tree) { UniValue elem(UniValue::VOBJ); elem.pushKV("depth", (int)depth); elem.pushKV("leaf_ver", (int)leaf_ver); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e9987d73be..1d7bd2eb94 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -21,8 +21,6 @@ #include <mutex> #include <unordered_map> -using SteadyClock = std::chrono::steady_clock; - static GlobalMutex g_rpc_warmup_mutex; static std::atomic<bool> g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true; @@ -170,7 +168,7 @@ static RPCHelpMan stop() // to the client (intended for testing) "\nRequest a graceful shutdown of " PACKAGE_NAME ".", { - {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true}, + {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", RPCArgOptions{.hidden=true}}, }, RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, RPCExamples{""}, @@ -468,8 +466,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler) { - try - { + try { RPCCommandExecution execution(request.strMethod); // Execute, convert arguments to array if necessary if (request.params.isObject()) { @@ -477,9 +474,9 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req } else { return command.actor(request, result, last_handler); } - } - catch (const std::exception& e) - { + } catch (const UniValue::type_error& e) { + throw JSONRPCError(RPC_TYPE_ERROR, e.what()); + } catch (const std::exception& e) { throw JSONRPCError(RPC_MISC_ERROR, e.what()); } } diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index dcf6c6bee1..cd8b49bfe1 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -66,7 +66,7 @@ static RPCHelpMan gettxoutproof() } } else { LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); // Loop through txids and try to find which block they're in. Exit loop once a block is found. for (const auto& tx : setTxids) { diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 24ab21a947..3e98e89791 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -50,7 +50,8 @@ void RPCTypeCheck(const UniValue& params, void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { if (!typeExpected.typeAny && value.type() != typeExpected.type) { - throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected.type), uvTypeName(value.type()))); + throw JSONRPCError(RPC_TYPE_ERROR, + strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type))); } } @@ -417,8 +418,8 @@ struct Sections { case RPCArg::Type::BOOL: { if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; - if (arg.m_type_str.size() != 0 && push_name) { - left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0); + if (arg.m_opts.type_str.size() != 0 && push_name) { + left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0); } else { left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } @@ -617,7 +618,7 @@ std::string RPCHelpMan::ToString() const ret += m_name; bool was_optional{false}; for (const auto& arg : m_args) { - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden const bool optional = arg.IsOptional(); ret += " "; if (optional) { @@ -638,7 +639,7 @@ std::string RPCHelpMan::ToString() const Sections sections; for (size_t i{0}; i < m_args.size(); ++i) { const auto& arg = m_args.at(i); - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden if (i == 0) ret += "\nArguments:\n"; @@ -703,8 +704,8 @@ std::string RPCArg::ToDescriptionString() const { std::string ret; ret += "("; - if (m_type_str.size() != 0) { - ret += m_type_str.at(1); + if (m_opts.type_str.size() != 0) { + ret += m_opts.type_str.at(1); } else { switch (m_type) { case Type::STR_HEX: @@ -990,7 +991,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const std::string RPCArg::ToString(const bool oneline) const { - if (oneline && !m_oneline_description.empty()) return m_oneline_description; + if (oneline && !m_opts.oneline_description.empty()) return m_opts.oneline_description; switch (m_type) { case Type::STR_HEX: diff --git a/src/rpc/util.h b/src/rpc/util.h index e883dc008e..9aa5df00b1 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -137,6 +137,12 @@ enum class OuterType { NONE, // Only set on first recursion }; +struct RPCArgOptions { + std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line + std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description. + bool hidden{false}; //!< For testing only +}; + struct RPCArg { enum class Type { OBJ, @@ -169,30 +175,25 @@ struct RPCArg { using DefaultHint = std::string; using Default = UniValue; using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>; + const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments) const Type m_type; - const bool m_hidden; const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts const Fallback m_fallback; const std::string m_description; - const std::string m_oneline_description; //!< Should be empty unless it is supposed to override the auto-generated summary line - const std::vector<std::string> m_type_str; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_type_str.at(0) will override the type of the value in a key-value pair, m_type_str.at(1) will override the type in the argument description. + const RPCArgOptions m_opts; RPCArg( const std::string name, const Type type, const Fallback fallback, const std::string description, - const std::string oneline_description = "", - const std::vector<std::string> type_str = {}, - const bool hidden = false) + RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, - m_hidden{hidden}, m_fallback{std::move(fallback)}, m_description{std::move(description)}, - m_oneline_description{std::move(oneline_description)}, - m_type_str{std::move(type_str)} + m_opts{std::move(opts)} { CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_USER_KEYS); } @@ -203,16 +204,13 @@ struct RPCArg { const Fallback fallback, const std::string description, const std::vector<RPCArg> inner, - const std::string oneline_description = "", - const std::vector<std::string> type_str = {}) + RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, - m_hidden{false}, m_inner{std::move(inner)}, m_fallback{std::move(fallback)}, m_description{std::move(description)}, - m_oneline_description{std::move(oneline_description)}, - m_type_str{std::move(type_str)} + m_opts{std::move(opts)} { CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_USER_KEYS); } @@ -227,7 +225,7 @@ struct RPCArg { /** * Return the type string of the argument. - * Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description). + * Set oneline to allow it to be overridden by a custom oneline type string (m_opts.oneline_description). */ std::string ToString(bool oneline) const; /** diff --git a/src/script/miniscript.h b/src/script/miniscript.h index f783d1dafc..6faf2624fd 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -13,8 +13,8 @@ #include <string> #include <vector> -#include <stdlib.h> #include <assert.h> +#include <cstdlib> #include <policy/policy.h> #include <primitives/transaction.h> @@ -276,6 +276,8 @@ struct StackSize { StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {}; }; +struct NoDupCheck {}; + } // namespace internal //! A node in a miniscript expression. @@ -301,8 +303,13 @@ private: const Type typ; //! Cached script length (computed by CalcScriptLen). const size_t scriptlen; - //! Whether a public key appears more than once in this node. - const bool duplicate_key; + //! Whether a public key appears more than once in this node. This value is initialized + //! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the + //! computation, requiring it to be done manually by invoking DuplicateKeyCheck(). + //! DuplicateKeyCheck(), or a non-NoDupCheck constructor, will compute has_duplicate_keys + //! for all subnodes as well. + mutable std::optional<bool> has_duplicate_keys; + //! Compute the length of the script for this miniscript (including children). size_t CalcScriptLen() const { @@ -654,6 +661,7 @@ public: return TreeEvalMaybe<std::string>(false, downfn, upfn); } +private: internal::Ops CalcOps() const { switch (fragment) { case Fragment::JUST_1: return {0, 0, {}}; @@ -777,11 +785,14 @@ public: assert(false); } - /** Check whether any key is repeated. +public: + /** Update duplicate key information in this Node. + * * This uses a custom key comparator provided by the context in order to still detect duplicates * for more complicated types. */ - template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const { + template<typename Ctx> void DuplicateKeyCheck(const Ctx& ctx) const + { // We cannot use a lambda here, as lambdas are non assignable, and the set operations // below require moving the comparators around. struct Comp { @@ -789,31 +800,55 @@ public: Comp(const Ctx& ctx) : ctx_ptr(&ctx) {} bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); } }; - using set = std::set<Key, Comp>; - auto upfn = [this, &ctx](const Node& node, Span<set> subs) -> std::optional<set> { - if (&node != this && node.duplicate_key) return {}; + // state in the recursive computation: + // - std::nullopt means "this node has duplicates" + // - an std::set means "this node has no duplicate keys, and they are: ...". + using keyset = std::set<Key, Comp>; + using state = std::optional<keyset>; + + auto upfn = [&ctx](const Node& node, Span<state> subs) -> state { + // If this node is already known to have duplicates, nothing left to do. + if (node.has_duplicate_keys.has_value() && *node.has_duplicate_keys) return {}; + + // Check if one of the children is already known to have duplicates. + for (auto& sub : subs) { + if (!sub.has_value()) { + node.has_duplicate_keys = true; + return {}; + } + } + // Start building the set of keys involved in this node and children. + // Start by keys in this node directly. size_t keys_count = node.keys.size(); - set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; - if (key_set.size() != keys_count) return {}; + keyset key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; + if (key_set.size() != keys_count) { + // It already has duplicates; bail out. + node.has_duplicate_keys = true; + return {}; + } - for (auto& sub: subs) { - keys_count += sub.size(); + // Merge the keys from the children into this set. + for (auto& sub : subs) { + keys_count += sub->size(); // Small optimization: std::set::merge is linear in the size of the second arg but // logarithmic in the size of the first. - if (key_set.size() < sub.size()) std::swap(key_set, sub); - key_set.merge(sub); - if (key_set.size() != keys_count) return {}; + if (key_set.size() < sub->size()) std::swap(key_set, *sub); + key_set.merge(*sub); + if (key_set.size() != keys_count) { + node.has_duplicate_keys = true; + return {}; + } } + node.has_duplicate_keys = false; return key_set; }; - return !TreeEvalMaybe<set>(upfn); + TreeEval<state>(upfn); } -public: //! Return the size of the script for this expression (faster than ToScript().size()). size_t ScriptSize() const { return scriptlen; } @@ -858,7 +893,7 @@ public: bool CheckTimeLocksMix() const { return GetType() << "k"_mst; } //! Check whether there is no duplicate key across this fragment and all its sub-fragments. - bool CheckDuplicateKey() const { return !duplicate_key; } + bool CheckDuplicateKey() const { return has_duplicate_keys && !*has_duplicate_keys; } //! Whether successful non-malleable satisfactions are guaranteed to be valid. bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); } @@ -872,13 +907,21 @@ public: //! Equality testing. bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; } - // Constructors with various argument combinations. - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + // Constructors with various argument combinations, which bypass the duplicate key check. + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + + // Constructors with various argument combinations, which do perform the duplicate key check. + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(arg), val) { DuplicateKeyCheck(ctx);} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, val) { DuplicateKeyCheck(ctx); } }; namespace internal { @@ -965,15 +1008,15 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co } /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */ -template<typename Key, typename Ctx> -void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) +template<typename Key> +void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) { NodeRef<Key> child = std::move(constructed.back()); constructed.pop_back(); if (reverse) { - constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(child), std::move(constructed.back()))); } else { - constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(constructed.back()), std::move(child))); } } @@ -987,6 +1030,18 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) { using namespace spanparsing; + // Account for the minimum script size for all parsed fragments so far. It "borrows" 1 + // script byte from all leaf nodes, counting it instead whenever a space for a recursive + // expression is added (through andor, and_*, or_*, thresh). This guarantees that all fragments + // increment the script_size by at least one, except for: + // - "0", "1": these leafs are only a single byte, so their subtracted-from increment is 0. + // This is not an issue however, as "space" for them has to be created by combinators, + // which do increment script_size. + // - "v:": the v wrapper adds nothing as in some cases it results in no opcode being added + // (instead transforming another opcode into its VERIFY form). However, the v: wrapper has + // to be interleaved with other fragments to be valid, so this is not a concern. + size_t script_size{1}; + // The two integers are used to hold state for thresh() std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse; std::vector<NodeRef<Key>> constructed; @@ -994,14 +1049,16 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); while (!to_parse.empty()) { + if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + // Get the current context we are decoding within auto [cur_context, n, k] = to_parse.back(); to_parse.pop_back(); switch (cur_context) { case ParseContext::WRAPPED_EXPR: { - int colon_index = -1; - for (int i = 1; i < (int)in.size(); ++i) { + std::optional<size_t> colon_index{}; + for (size_t i = 1; i < in.size(); ++i) { if (in[i] == ':') { colon_index = i; break; @@ -1009,106 +1066,131 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) if (in[i] < 'a' || in[i] > 'z') break; } // If there is no colon, this loop won't execute - for (int j = 0; j < colon_index; ++j) { + bool last_was_v{false}; + for (size_t j = 0; colon_index && j < *colon_index; ++j) { + if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; if (in[j] == 'a') { + script_size += 2; to_parse.emplace_back(ParseContext::ALT, -1, -1); } else if (in[j] == 's') { + script_size += 1; to_parse.emplace_back(ParseContext::SWAP, -1, -1); } else if (in[j] == 'c') { + script_size += 1; to_parse.emplace_back(ParseContext::CHECK, -1, -1); } else if (in[j] == 'd') { + script_size += 3; to_parse.emplace_back(ParseContext::DUP_IF, -1, -1); } else if (in[j] == 'j') { + script_size += 4; to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1); } else if (in[j] == 'n') { + script_size += 1; to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1); } else if (in[j] == 'v') { + // do not permit "...vv...:"; it's not valid, and also doesn't trigger early + // failure as script_size isn't incremented. + if (last_was_v) return {}; to_parse.emplace_back(ParseContext::VERIFY, -1, -1); } else if (in[j] == 'u') { + script_size += 4; to_parse.emplace_back(ParseContext::WRAP_U, -1, -1); } else if (in[j] == 't') { + script_size += 1; to_parse.emplace_back(ParseContext::WRAP_T, -1, -1); } else if (in[j] == 'l') { // The l: wrapper is equivalent to or_i(0,X) - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + script_size += 4; + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); to_parse.emplace_back(ParseContext::OR_I, -1, -1); } else { return {}; } + last_was_v = (in[j] == 'v'); } to_parse.emplace_back(ParseContext::EXPR, -1, -1); - in = in.subspan(colon_index + 1); + if (colon_index) in = in.subspan(*colon_index + 1); break; } case ParseContext::EXPR: { if (Const("0", in)) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); } else if (Const("1", in)) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); } else if (Const("pk(", in)) { auto res = ParseKeyEnd<Key, Ctx>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))))); in = in.subspan(key_size + 1); + script_size += 34; } else if (Const("pkh(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))))); in = in.subspan(key_size + 1); + script_size += 24; } else if (Const("pk_k(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))); in = in.subspan(key_size + 1); + script_size += 33; } else if (Const("pk_h(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))); in = in.subspan(key_size + 1); + script_size += 23; } else if (Const("sha256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 38; } else if (Const("ripemd160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 26; } else if (Const("hash256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 38; } else if (Const("hash160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 26; } else if (Const("after(", in)) { int arg_size = FindNextChar(in, ')'); if (arg_size < 1) return {}; int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num)); in = in.subspan(arg_size + 1); + script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("older(", in)) { int arg_size = FindNextChar(in, ')'); if (arg_size < 1) return {}; int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num)); in = in.subspan(arg_size + 1); + script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("multi(", in)) { // Get threshold int next_comma = FindNextChar(in, ','); @@ -1128,7 +1210,8 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) } if (keys.size() < 1 || keys.size() > 20) return {}; if (k < 1 || k > (int64_t)keys.size()) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k)); + script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k)); } else if (Const("thresh(", in)) { int next_comma = FindNextChar(in, ','); if (next_comma < 1) return {}; @@ -1138,6 +1221,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH to_parse.emplace_back(ParseContext::THRESH, 1, k); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 2 + (k > 16) + (k > 0x7f) + (k > 0x7fff) + (k > 0x7fffff); } else if (Const("andor(", in)) { to_parse.emplace_back(ParseContext::ANDOR, -1, -1); to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1); @@ -1146,21 +1230,29 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); to_parse.emplace_back(ParseContext::COMMA, -1, -1); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 5; } else { if (Const("and_n(", in)) { to_parse.emplace_back(ParseContext::AND_N, -1, -1); + script_size += 5; } else if (Const("and_b(", in)) { to_parse.emplace_back(ParseContext::AND_B, -1, -1); + script_size += 2; } else if (Const("and_v(", in)) { to_parse.emplace_back(ParseContext::AND_V, -1, -1); + script_size += 1; } else if (Const("or_b(", in)) { to_parse.emplace_back(ParseContext::OR_B, -1, -1); + script_size += 2; } else if (Const("or_c(", in)) { to_parse.emplace_back(ParseContext::OR_C, -1, -1); + script_size += 3; } else if (Const("or_d(", in)) { to_parse.emplace_back(ParseContext::OR_D, -1, -1); + script_size += 4; } else if (Const("or_i(", in)) { to_parse.emplace_back(ParseContext::OR_I, -1, -1); + script_size += 4; } else { return {}; } @@ -1172,69 +1264,70 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) break; } case ParseContext::ALT: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case ParseContext::SWAP: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case ParseContext::CHECK: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case ParseContext::DUP_IF: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case ParseContext::NON_ZERO: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case ParseContext::ZERO_NOTEQUAL: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case ParseContext::VERIFY: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + script_size += (constructed.back()->GetType() << "x"_mst); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case ParseContext::WRAP_U: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0))); break; } case ParseContext::WRAP_T: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1))); break; } case ParseContext::AND_B: { - BuildBack(ctx, Fragment::AND_B, constructed); + BuildBack(Fragment::AND_B, constructed); break; } case ParseContext::AND_N: { auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); break; } case ParseContext::AND_V: { - BuildBack(ctx, Fragment::AND_V, constructed); + BuildBack(Fragment::AND_V, constructed); break; } case ParseContext::OR_B: { - BuildBack(ctx, Fragment::OR_B, constructed); + BuildBack(Fragment::OR_B, constructed); break; } case ParseContext::OR_C: { - BuildBack(ctx, Fragment::OR_C, constructed); + BuildBack(Fragment::OR_C, constructed); break; } case ParseContext::OR_D: { - BuildBack(ctx, Fragment::OR_D, constructed); + BuildBack(Fragment::OR_D, constructed); break; } case ParseContext::OR_I: { - BuildBack(ctx, Fragment::OR_I, constructed); + BuildBack(Fragment::OR_I, constructed); break; } case ParseContext::ANDOR: { @@ -1242,7 +1335,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); break; } case ParseContext::THRESH: { @@ -1251,6 +1344,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) in = in.subspan(1); to_parse.emplace_back(ParseContext::THRESH, n+1, k); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 2; } else if (in[0] == ')') { if (k > n) return {}; in = in.subspan(1); @@ -1261,7 +1355,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); } std::reverse(subs.begin(), subs.end()); - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); } else { return {}; } @@ -1282,8 +1376,11 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // Sanity checks on the produced miniscript assert(constructed.size() == 1); + assert(constructed[0]->ScriptSize() == script_size); if (in.size() > 0) return {}; - return std::move(constructed.front()); + const NodeRef<Key> tl_node = std::move(constructed.front()); + tl_node->DuplicateKeyCheck(ctx); + return tl_node; } /** Decode a script into opcode/push pairs. @@ -1394,12 +1491,12 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) // Constants if (in[0].first == OP_1) { ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); break; } if (in[0].first == OP_0) { ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); break; } // Public keys @@ -1407,14 +1504,14 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end()); if (!key) return {}; ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(*key)))); break; } if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) { auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end()); if (!key) return {}; in += 5; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(*key)))); break; } // Time locks @@ -1422,31 +1519,31 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (*num < 1 || *num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, *num)); break; } if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (num < 1 || num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, *num)); break; } // Hashes if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) { if (in[2].first == OP_SHA256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, in[1].second)); in += 7; break; } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, in[1].second)); in += 7; break; } @@ -1467,7 +1564,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (!k || *k < 1 || *k > *n) return {}; in += 3 + *n; std::reverse(keys.begin(), keys.end()); - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), *k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), *k)); break; } /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather @@ -1562,63 +1659,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) case DecodeContext::SWAP: { if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case DecodeContext::ALT: { if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case DecodeContext::CHECK: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case DecodeContext::DUP_IF: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case DecodeContext::VERIFY: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case DecodeContext::NON_ZERO: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case DecodeContext::ZERO_NOTEQUAL: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case DecodeContext::AND_V: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true); + BuildBack(Fragment::AND_V, constructed, /*reverse=*/true); break; } case DecodeContext::AND_B: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true); + BuildBack(Fragment::AND_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_B: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_C: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_C, constructed, /*reverse=*/true); break; } case DecodeContext::OR_D: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_D, constructed, /*reverse=*/true); break; } case DecodeContext::ANDOR: { @@ -1628,7 +1725,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) NodeRef<Key> right = std::move(constructed.back()); constructed.pop_back(); NodeRef<Key> mid = std::move(constructed.back()); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); break; } case DecodeContext::THRESH_W: { @@ -1652,7 +1749,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) constructed.pop_back(); subs.push_back(std::move(sub)); } - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); break; } case DecodeContext::ENDIF: { @@ -1702,7 +1799,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (in >= last) return {}; if (in[0].first == OP_IF) { ++in; - BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_I, constructed, /*reverse=*/true); } else if (in[0].first == OP_NOTIF) { ++in; to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); @@ -1717,6 +1814,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) } if (constructed.size() != 1) return {}; const NodeRef<Key> tl_node = std::move(constructed.front()); + tl_node->DuplicateKeyCheck(ctx); // Note that due to how ComputeType works (only assign the type to the node if the // subs' types are valid) this would fail if any node of tree is badly typed. if (!tl_node->IsValidTopLevel()) return {}; @@ -1733,6 +1831,8 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& template<typename Ctx> inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { using namespace internal; + // A too large Script is necessarily invalid, don't bother parsing it. + if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; auto decomposed = DecomposeScript(script); if (!decomposed) return {}; auto it = decomposed->begin(); diff --git a/src/script/standard.h b/src/script/standard.h index 1e6769782a..966a52b2c7 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -315,6 +315,8 @@ public: TaprootSpendData GetSpendData() const; /** Returns a vector of tuples representing the depth, leaf version, and script */ std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const; + /** Returns true if there are any tapscripts */ + bool HasScripts() const { return !m_branch.empty(); } }; /** Given a TaprootSpendData and the output key, reconstruct its script tree. diff --git a/src/streams.h b/src/streams.h index f14d347380..0178df1c49 100644 --- a/src/streams.h +++ b/src/streams.h @@ -13,11 +13,11 @@ #include <algorithm> #include <assert.h> +#include <cstdio> #include <ios> #include <limits> #include <optional> #include <stdint.h> -#include <stdio.h> #include <string.h> #include <string> #include <utility> @@ -269,7 +269,6 @@ public: // Stream subset // bool eof() const { return size() == 0; } - CDataStream* rdbuf() { return this; } int in_avail() const { return size(); } void SetType(int n) { nType = n; } @@ -488,12 +487,14 @@ public: AutoFile(const AutoFile&) = delete; AutoFile& operator=(const AutoFile&) = delete; - void fclose() + int fclose() { + int retval{0}; if (file) { - ::fclose(file); + retval = ::fclose(file); file = nullptr; } + return retval; } /** Get wrapped FILE* with transfer of ownership. diff --git a/src/support/cleanse.h b/src/support/cleanse.h index 8c1210a114..b1227770c7 100644 --- a/src/support/cleanse.h +++ b/src/support/cleanse.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_SUPPORT_CLEANSE_H #define BITCOIN_SUPPORT_CLEANSE_H -#include <stdlib.h> +#include <cstdlib> /** Secure overwrite a buffer (possibly containing secret data) with zero-bytes. The write * operation will not be optimized out by the compiler. */ diff --git a/src/sync.h b/src/sync.h index 7ec4b668ac..1f4e191214 100644 --- a/src/sync.h +++ b/src/sync.h @@ -111,7 +111,7 @@ public: return PARENT::try_lock(); } - using UniqueLock = std::unique_lock<PARENT>; + using unique_lock = std::unique_lock<PARENT>; #ifdef __clang__ //! For negative capabilities in the Clang Thread Safety Analysis. //! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in conjunction @@ -147,11 +147,13 @@ inline void AssertLockNotHeldInline(const char* name, const char* file, int line inline void AssertLockNotHeldInline(const char* name, const char* file, int line, GlobalMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); } #define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs) -/** Wrapper around std::unique_lock style lock for Mutex. */ -template <typename Mutex, typename Base = typename Mutex::UniqueLock> -class SCOPED_LOCKABLE UniqueLock : public Base +/** Wrapper around std::unique_lock style lock for MutexType. */ +template <typename MutexType> +class SCOPED_LOCKABLE UniqueLock : public MutexType::unique_lock { private: + using Base = typename MutexType::unique_lock; + void Enter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex()); @@ -165,15 +167,15 @@ private: bool TryEnter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex(), true); - Base::try_lock(); - if (!Base::owns_lock()) { - LeaveCritical(); + if (Base::try_lock()) { + return true; } - return Base::owns_lock(); + LeaveCritical(); + return false; } public: - UniqueLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock) + UniqueLock(MutexType& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock) { if (fTry) TryEnter(pszName, pszFile, nLine); @@ -181,7 +183,7 @@ public: Enter(pszName, pszFile, nLine); } - UniqueLock(Mutex* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn) + UniqueLock(MutexType* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn) { if (!pmutexIn) return; @@ -241,29 +243,24 @@ public: #define REVERSE_LOCK(g) typename std::decay<decltype(g)>::type::reverse_lock UNIQUE_NAME(revlock)(g, #g, __FILE__, __LINE__) -template<typename MutexArg> -using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>; - // When locking a Mutex, require negative capability to ensure the lock // is not already held inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; } inline Mutex* MaybeCheckNotHeld(Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; } -// When locking a GlobalMutex, just check it is not locked in the surrounding scope -inline GlobalMutex& MaybeCheckNotHeld(GlobalMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } -inline GlobalMutex* MaybeCheckNotHeld(GlobalMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } - -// When locking a RecursiveMutex, it's okay to already hold the lock -// but check that it is not known to be locked in the surrounding scope anyway -inline RecursiveMutex& MaybeCheckNotHeld(RecursiveMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } -inline RecursiveMutex* MaybeCheckNotHeld(RecursiveMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } +// When locking a GlobalMutex or RecursiveMutex, just check it is not +// locked in the surrounding scope. +template <typename MutexType> +inline MutexType& MaybeCheckNotHeld(MutexType& m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; } +template <typename MutexType> +inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; } -#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) +#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \ - DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \ - DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__); -#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true) -#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) + UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \ + UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__) +#define TRY_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true) +#define WAIT_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define ENTER_CRITICAL_SECTION(cs) \ { \ diff --git a/src/test/banman_tests.cpp b/src/test/banman_tests.cpp index 27ce9ad638..ecf60834ce 100644 --- a/src/test/banman_tests.cpp +++ b/src/test/banman_tests.cpp @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(file) " { \"version\": 1, \"ban_created\": 0, \"banned_until\": 778, \"address\": \"1.0.0.0/8\" }" "] }", }; - assert(WriteBinaryFile(banlist_path + ".json", entries_write)); + BOOST_REQUIRE(WriteBinaryFile(banlist_path + ".json", entries_write)); { // The invalid entries will be dropped, but the valid one remains ASSERT_DEBUG_LOG("Dropping entry with unparseable address or subnet (aaaaaaaaa) from ban list"); @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(file) BanMan banman{banlist_path, /*client_interface=*/nullptr, /*default_ban_time=*/0}; banmap_t entries_read; banman.GetBanned(entries_read); - assert(entries_read.size() == 1); + BOOST_CHECK_EQUAL(entries_read.size(), 1); } } } diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index c8e8cdeeb3..0157e25071 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -4,13 +4,13 @@ #include <boost/test/unit_test.hpp> -#include <stdlib.h> - #include <chain.h> #include <rpc/blockchain.h> #include <test/util/setup_common.h> #include <util/string.h> +#include <cstdlib> + /* Equality between doubles is imprecise. Comparison should be done * with a small threshold of tolerance, rather than exact equality. */ diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 78b82b9b20..1c13c0a909 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -7,6 +7,7 @@ #include <consensus/merkle.h> #include <pow.h> #include <streams.h> +#include <test/util/txmempool.h> #include <test/util/setup_common.h> diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp new file mode 100644 index 0000000000..dd7c32cc46 --- /dev/null +++ b/src/test/blockmanager_tests.cpp @@ -0,0 +1,42 @@ +// 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 <chainparams.h> +#include <node/blockstorage.h> +#include <node/context.h> +#include <validation.h> + +#include <boost/test/unit_test.hpp> +#include <test/util/setup_common.h> + +using node::BlockManager; +using node::BLOCK_SERIALIZATION_HEADER_SIZE; + +// use BasicTestingSetup here for the data directory configuration, setup, and cleanup +BOOST_FIXTURE_TEST_SUITE(blockmanager_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos) +{ + const auto params {CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN)}; + BlockManager blockman {}; + CChain chain {}; + // simulate adding a genesis block normally + BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, *params, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE); + // simulate what happens during reindex + // simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file + // the block is found at offset 8 because there is an 8 byte serialization header + // consisting of 4 magic bytes + 4 length bytes before each block in a well-formed blk file. + FlatFilePos pos{0, BLOCK_SERIALIZATION_HEADER_SIZE}; + BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, *params, &pos).nPos, BLOCK_SERIALIZATION_HEADER_SIZE); + // now simulate what happens after reindex for the first new block processed + // the actual block contents don't matter, just that it's a block. + // verify that the write position is at offset 0x12d. + // this is a check to make sure that https://github.com/bitcoin/bitcoin/issues/21379 does not recur + // 8 bytes (for serialization header) + 285 (for serialized genesis block) = 293 + // add another 8 bytes for the second block's serialization header and we get 293 + 8 = 301 + FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1, chain, *params, nullptr)}; + BOOST_CHECK_EQUAL(actual.nPos, BLOCK_SERIALIZATION_HEADER_SIZE + ::GetSerializeSize(params->GenesisBlock(), CLIENT_VERSION) + BLOCK_SERIALIZATION_HEADER_SIZE); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 132c4e53e7..8a2b0792fd 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -5,6 +5,7 @@ #include <chainparams.h> #include <index/coinstatsindex.h> #include <interfaces/chain.h> +#include <kernel/coinstats.h> #include <test/util/setup_common.h> #include <test/util/validation.h> #include <util/time.h> @@ -38,7 +39,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) } // CoinStatsIndex should not be found before it is started. - BOOST_CHECK(!coin_stats_index.LookUpStats(block_index)); + BOOST_CHECK(!coin_stats_index.LookUpStats(*block_index)); // BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex // is started. @@ -54,10 +55,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } - BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*genesis_block_index)); // Check that CoinStatsIndex updates with new blocks. - BOOST_CHECK(coin_stats_index.LookUpStats(block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*block_index)); const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; std::vector<CMutableTransaction> noTxns; @@ -71,21 +72,27 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); new_block_index = m_node.chainman->ActiveChain().Tip(); } - BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*new_block_index)); BOOST_CHECK(block_index != new_block_index); + // It is not safe to stop and destroy the index until it finishes handling + // the last BlockConnected notification. The BlockUntilSyncedToCurrentChain() + // call above is sufficient to ensure this, but the + // SyncWithValidationInterfaceQueue() call below is also needed to ensure + // TSAN always sees the test thread waiting for the notification thread, and + // avoid potential false positive reports. + SyncWithValidationInterfaceQueue(); + // Shutdown sequence (c.f. Shutdown() in init.cpp) coin_stats_index.Stop(); - - // Rest of shutdown sequence and destructors happen in ~TestingSetup() } // Test shutdown between BlockConnected and ChainStateFlushed notifications, // make sure index is not corrupted and is able to reload. BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup) { - CChainState& chainstate = Assert(m_node.chainman)->ActiveChainstate(); + Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate(); const CChainParams& params = Params(); { CoinStatsIndex index{interfaces::MakeChain(m_node), 1 << 20}; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 7889156d51..7150698e64 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -45,6 +45,8 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { + LOCK(NetEventsInterface::g_msgproc_mutex); + ConnmanTestMsg& connman = static_cast<ConnmanTestMsg&>(*m_node.connman); // Disable inactivity checks for this test to avoid interference connman.SetPeerConnectTimeout(99999s); @@ -80,10 +82,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) } // Test starts here - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders + { LOCK(dummyNode1.cs_vSend); BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); @@ -93,20 +93,14 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) int64_t nStartTime = GetTime(); // Wait 21 minutes SetMockTime(nStartTime+21*60); - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders { LOCK(dummyNode1.cs_vSend); BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); } // Wait 3 more minutes SetMockTime(nStartTime+24*60); - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in disconnect - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in disconnect BOOST_CHECK(dummyNode1.fDisconnect == true); peerman.FinalizeNode(dummyNode1); @@ -274,6 +268,8 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) BOOST_AUTO_TEST_CASE(peer_discouragement) { + LOCK(NetEventsInterface::g_msgproc_mutex); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), @@ -308,10 +304,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[0]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[0]); peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged - { - LOCK(nodes[0]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[0])); - } + BOOST_CHECK(peerLogic->SendMessages(nodes[0])); + BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged @@ -330,10 +324,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[1]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[1]); peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1); - { - LOCK(nodes[1]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[1])); - } + BOOST_CHECK(peerLogic->SendMessages(nodes[1])); // [0] is still discouraged/disconnected. BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); @@ -341,10 +332,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_CHECK(!banman->IsDiscouraged(addr[1])); BOOST_CHECK(!nodes[1]->fDisconnect); peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold - { - LOCK(nodes[1]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[1])); - } + BOOST_CHECK(peerLogic->SendMessages(nodes[1])); // Expect both [0] and [1] to be discouraged/disconnected now. BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); @@ -367,10 +355,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[2]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[2]); peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD); - { - LOCK(nodes[2]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[2])); - } + BOOST_CHECK(peerLogic->SendMessages(nodes[2])); BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[1])); BOOST_CHECK(banman->IsDiscouraged(addr[2])); @@ -386,6 +371,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_AUTO_TEST_CASE(DoS_bantime) { + LOCK(NetEventsInterface::g_msgproc_mutex); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), @@ -411,10 +398,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) dummyNode.fSuccessfullyConnected = true; peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD); - { - LOCK(dummyNode.cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); - } + BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); BOOST_CHECK(banman->IsDiscouraged(addr)); peerLogic->FinalizeNode(dummyNode); diff --git a/src/test/fuzz/bitdeque.cpp b/src/test/fuzz/bitdeque.cpp index 01af8320b5..634a3de346 100644 --- a/src/test/fuzz/bitdeque.cpp +++ b/src/test/fuzz/bitdeque.cpp @@ -2,11 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util/bitdeque.h> - #include <random.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/util.h> +#include <util/bitdeque.h> #include <deque> #include <vector> @@ -54,7 +53,8 @@ FUZZ_TARGET_INIT(bitdeque, InitRandData) --initlen; } - while (provider.remaining_bytes()) { + LIMITED_WHILE(provider.remaining_bytes() > 0, 900) + { { assert(deq.size() == bitdeq.size()); auto it = deq.begin(); @@ -538,5 +538,4 @@ FUZZ_TARGET_INIT(bitdeque, InitRandData) } ); } - } diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index c52fca5fe8..f05248ab47 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -12,6 +12,7 @@ #include <key_io.h> #include <memusage.h> #include <netbase.h> +#include <policy/policy.h> #include <policy/settings.h> #include <pow.h> #include <protocol.h> diff --git a/src/test/fuzz/mempool_utils.h b/src/test/fuzz/mempool_utils.h deleted file mode 100644 index bfe12e30ba..0000000000 --- a/src/test/fuzz/mempool_utils.h +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -#ifndef BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H -#define BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H - -#include <validation.h> - -class DummyChainState final : public CChainState -{ -public: - void SetMempool(CTxMemPool* mempool) - { - m_mempool = mempool; - } -}; - -#endif // BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index 637ba503c6..a3d57dbdd5 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -8,6 +8,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> #include <txmempool.h> diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp index 507ce57ec0..eba03da773 100644 --- a/src/test/fuzz/pow.cpp +++ b/src/test/fuzz/pow.cpp @@ -114,7 +114,7 @@ FUZZ_TARGET_INIT(pow_transition, initialize_pow) auto current_block{std::make_unique<CBlockIndex>(header)}; current_block->pprev = blocks.empty() ? nullptr : blocks.back().get(); current_block->nHeight = height; - blocks.emplace_back(std::move(current_block)).get(); + blocks.emplace_back(std::move(current_block)); } auto last_block{blocks.back().get()}; unsigned int new_nbits{GetNextWorkRequired(last_block, nullptr, consensus_params)}; diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 272c9e6cdc..5a4df735da 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -73,6 +73,8 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); + LOCK(NetEventsInterface::g_msgproc_mutex); + const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE).c_str()}; if (!LIMIT_TO_MESSAGE_TYPE.empty() && random_message_type != LIMIT_TO_MESSAGE_TYPE) { return; @@ -92,10 +94,7 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE GetTime<std::chrono::microseconds>(), std::atomic<bool>{false}); } catch (const std::ios_base::failure&) { } - { - LOCK(p2p_node.cs_sendProcessing); - g_setup->m_node.peerman->SendMessages(&p2p_node); - } + g_setup->m_node.peerman->SendMessages(&p2p_node); SyncWithValidationInterfaceQueue(); g_setup->m_node.connman->StopNodes(); } @@ -131,6 +130,7 @@ FUZZ_TARGET_MSG(pong); FUZZ_TARGET_MSG(sendaddrv2); FUZZ_TARGET_MSG(sendcmpct); FUZZ_TARGET_MSG(sendheaders); +FUZZ_TARGET_MSG(sendtxrcncl); FUZZ_TARGET_MSG(tx); FUZZ_TARGET_MSG(verack); FUZZ_TARGET_MSG(version); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index 12e682416c..1df1717ec3 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -40,6 +40,8 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); + LOCK(NetEventsInterface::g_msgproc_mutex); + std::vector<CNode*> peers; const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3); for (int i = 0; i < num_peers_to_add; ++i) { @@ -70,10 +72,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) connman.ProcessMessagesOnce(random_node); } catch (const std::ios_base::failure&) { } - { - LOCK(random_node.cs_sendProcessing); - g_setup->m_node.peerman->SendMessages(&random_node); - } + g_setup->m_node.peerman->SendMessages(&random_node); } SyncWithValidationInterfaceQueue(); g_setup->m_node.connman->StopNodes(); diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp index 1a06ae886e..678fc7a5aa 100644 --- a/src/test/fuzz/rbf.cpp +++ b/src/test/fuzz/rbf.cpp @@ -9,7 +9,9 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> +#include <test/util/txmempool.h> #include <txmempool.h> #include <cstdint> diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 26913a41d2..f32046e69f 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -151,6 +151,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "preciousblock", "pruneblockchain", "reconsiderblock", + "scanblocks", "scantxoutset", "sendrawtransaction", "setmocktime", diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 3191367870..46ca8e47e9 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -8,11 +8,12 @@ #include <node/miner.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <test/fuzz/mempool_utils.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/mining.h> #include <test/util/script.h> #include <test/util/setup_common.h> +#include <test/util/txmempool.h> #include <util/rbf.h> #include <validation.h> #include <validationinterface.h> @@ -85,7 +86,7 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); } -void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate) +void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate) { WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); { @@ -108,7 +109,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh SyncWithValidationInterfaceQueue(); } -void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chainstate) +void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) { const auto time = ConsumeTime(fuzzed_data_provider, chainstate.m_chain.Tip()->GetMedianTimePast() + 1, diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index 943f3f5fdf..55060f31cf 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -45,7 +45,7 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage) // if true, allow duplicate input when constructing tx const bool duplicate_input = fuzzed_data_provider.ConsumeBool(); - LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) + LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) { // construct transaction const CTransactionRef tx = [&] { diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 38626d4bcf..d495a6bfe3 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -16,7 +16,7 @@ #include <memory> FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) - : m_fuzzed_data_provider{fuzzed_data_provider} + : m_fuzzed_data_provider{fuzzed_data_provider}, m_selectable{fuzzed_data_provider.ConsumeBool()} { m_socket = fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET); } @@ -254,6 +254,24 @@ int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const return 0; } +bool FuzzedSock::SetNonBlocking() const +{ + constexpr std::array setnonblocking_errnos{ + EBADF, + EPERM, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, setnonblocking_errnos); + return false; + } + return true; +} + +bool FuzzedSock::IsSelectable() const +{ + return m_selectable; +} + bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { constexpr std::array wait_errnos{ @@ -307,8 +325,8 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept { // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime. - static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z")}; - static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z")}; + static const int64_t time_min{946684801}; // 2000-01-01T00:00:01Z + static const int64_t time_max{4133980799}; // 2100-12-31T23:59:59Z return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max)); } @@ -478,21 +496,6 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no return tx_destination; } -CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept -{ - // Avoid: - // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long' - // - // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK() - const CAmount fee = std::min<CAmount>(ConsumeMoney(fuzzed_data_provider), std::numeric_limits<CAmount>::max() / static_cast<CAmount>(100000)); - assert(MoneyRange(fee)); - const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); - const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); - const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST); - return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; -} - bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept { for (const CTxIn& tx_in : tx.vin) { diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 6d652c922b..dfe4855326 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -23,7 +23,6 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/util/net.h> -#include <txmempool.h> #include <uint256.h> #include <version.h> @@ -48,6 +47,13 @@ class FuzzedSock : public Sock */ mutable std::optional<uint8_t> m_peek_data; + /** + * Whether to pretend that the socket is select(2)-able. This is randomly set in the + * constructor. It should remain constant so that repeated calls to `IsSelectable()` + * return the same value. + */ + const bool m_selectable; + public: explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider); @@ -73,6 +79,10 @@ public: int GetSockName(sockaddr* name, socklen_t* name_len) const override; + bool SetNonBlocking() const override; + + bool IsSelectable() const override; + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override; bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override; @@ -213,8 +223,6 @@ template <typename WeakEnumType, size_t size> return UintToArith256(ConsumeUInt256(fuzzed_data_provider)); } -[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept; - [[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept; template <typename T> @@ -328,7 +336,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N } inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, node_id_in); } -void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept; +void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex); class FuzzedFileProvider { diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp new file mode 100644 index 0000000000..d0053f77d2 --- /dev/null +++ b/src/test/fuzz/util/mempool.cpp @@ -0,0 +1,27 @@ +// 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 <consensus/amount.h> +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> +#include <txmempool.h> + +#include <limits> + +CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept +{ + // Avoid: + // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long' + // + // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK() + const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/std::numeric_limits<CAmount>::max() / CAmount{100'000})}; + assert(MoneyRange(fee)); + const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); + const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); + const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST); + return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; +} diff --git a/src/test/fuzz/util/mempool.h b/src/test/fuzz/util/mempool.h new file mode 100644 index 0000000000..4304e5294e --- /dev/null +++ b/src/test/fuzz/util/mempool.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H +#define BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H + +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <txmempool.h> +#include <validation.h> + +class DummyChainState final : public Chainstate +{ +public: + void SetMempool(CTxMemPool* mempool) + { + m_mempool = mempool; + } +}; + +[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept; + +#endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index 8241dff189..9a90de8911 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -9,9 +9,10 @@ #include <node/mempool_persist_args.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <test/fuzz/mempool_utils.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> +#include <test/util/txmempool.h> #include <txmempool.h> #include <util/time.h> #include <validation.h> diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 8c745b07b9..393311e4e4 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <policy/policy.h> +#include <test/util/txmempool.h> #include <txmempool.h> #include <util/system.h> #include <util/time.h> @@ -203,7 +204,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) CTxMemPool::setEntries setAncestorsCalculated; std::string dummy; - BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); + BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2'000'000LL).FromTx(tx7), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx7), setAncestors); @@ -261,7 +262,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx10.vout[0].nValue = 10 * COIN; setAncestorsCalculated.clear(); - BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); + BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(4).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx10), setAncestors); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 9f5fb17b60..9bc29e3599 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <chainparams.h> #include <coins.h> #include <consensus/consensus.h> #include <consensus/merkle.h> @@ -10,6 +9,7 @@ #include <node/miner.h> #include <policy/policy.h> #include <script/standard.h> +#include <test/util/txmempool.h> #include <timedata.h> #include <txmempool.h> #include <uint256.h> @@ -30,15 +30,24 @@ 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) + void TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool TestSequenceLocks(const CTransaction& tx, CTxMemPool& tx_mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); + CCoinsViewMemPool view_mempool{&m_node.chainman->ActiveChainstate().CoinsTip(), tx_mempool}; return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx); } - BlockAssembler AssemblerForTest(const CChainParams& params); + CTxMemPool& MakeMempool() + { + // Delete the previous mempool to ensure with valgrind that the old + // pointer is not accessed, when the new one should be accessed + // instead. + m_node.mempool.reset(); + m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node)); + return *m_node.mempool; + } + BlockAssembler AssemblerForTest(CTxMemPool& tx_mempool); }; } // namespace miner_tests @@ -46,13 +55,13 @@ BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); -BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) +BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool) { BlockAssembler::Options options; options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}; + return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options}; } constexpr static struct { @@ -89,8 +98,10 @@ static CBlockIndex CreateBlockIndex(int nHeight, CBlockIndex* active_chain_tip) // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. -void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; @@ -105,21 +116,21 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co 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)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx); @@ -129,7 +140,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.hash = hashHighFeeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).FromTx(tx)); size_t freeTxSize = ::GetSerializeSize(tx, PROTOCOL_VERSION); // Calculate a fee on child transaction that will put the package just @@ -139,8 +150,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.hash = hashFreeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx); @@ -150,11 +161,11 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - m_node.mempool->removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); + tx_mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse + 2).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx); @@ -167,7 +178,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 100000000; tx.vout[1].nValue = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx.vin[0].prevout.hash = hashFreeTx2; @@ -175,8 +186,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { @@ -188,13 +199,13 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co // as well. tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee - m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(10000).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } -void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) +void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) { uint256 hash; CMutableTransaction tx; @@ -202,172 +213,205 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C entry.nFee = 11; entry.nHeight = 11; - // Just to make sure we can still make simple blocks - auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); - BOOST_CHECK(pblocktemplate); - - const CAmount BLOCKSUBSIDY = 50*COIN; + const CAmount BLOCKSUBSIDY = 50 * COIN; const CAmount LOWFEE = CENT; const CAmount HIGHFEE = COIN; - const CAmount HIGHERFEE = 4*COIN; + const CAmount HIGHERFEE = 4 * COIN; - // block sigops > limit: 1000 CHECKMULTISIG + 1 - tx.vin.resize(1); - // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG - tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].prevout.n = 0; - tx.vout.resize(1); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 1001; ++i) { - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // Just to make sure we can still make simple blocks + auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); + + // block sigops > limit: 1000 CHECKMULTISIG + 1 + tx.vin.resize(1); + // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG + tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vout.resize(1); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 1001; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); } - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); - m_node.mempool->clear(); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 1001; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + } - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 1001; ++i) { - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // block size > limit + tx.vin[0].scriptSig = CScript(); + // 18 * (520char + DROP) + OP_1 = 9433 bytes + std::vector<unsigned char> vchData(520); + for (unsigned int i = 0; i < 18; ++i) { + tx.vin[0].scriptSig << vchData << OP_DROP; + } + tx.vin[0].scriptSig << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 128; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); - - // block size > limit - tx.vin[0].scriptSig = CScript(); - // 18 * (520char + DROP) + OP_1 = 9433 bytes - std::vector<unsigned char> vchData(520); - for (unsigned int i = 0; i < 18; ++i) - tx.vin[0].scriptSig << vchData << OP_DROP; - tx.vin[0].scriptSig << OP_1; - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 128; ++i) + { - tx.vout[0].nValue -= LOWFEE; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // orphan in tx_mempool, template creation fails hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); - - // orphan in *m_node.mempool, template creation fails - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - m_node.mempool->clear(); - // child with higher feerate than parent - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vin[0].prevout.hash = txFirst[1]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - tx.vin[0].prevout.hash = hash; - tx.vin.resize(2); - tx.vin[1].scriptSig = CScript() << OP_1; - tx.vin[1].prevout.hash = txFirst[0]->GetHash(); - tx.vin[1].prevout.n = 0; - tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); - // coinbase in *m_node.mempool, template creation fails - tx.vin.resize(1); - tx.vin[0].prevout.SetNull(); - tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; - tx.vout[0].nValue = 0; - hash = tx.GetHash(); - // give it a fee so it'll get mined - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - // Should throw bad-cb-multiple - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); - m_node.mempool->clear(); + // child with higher feerate than parent + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vin[0].prevout.hash = txFirst[1]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + tx.vin.resize(2); + tx.vin[1].scriptSig = CScript() << OP_1; + tx.vin[1].prevout.hash = txFirst[0]->GetHash(); + tx.vin[1].prevout.n = 0; + tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + } - // double spend txn pair in *m_node.mempool, template creation fails - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; - tx.vout[0].scriptPubKey = CScript() << OP_1; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - tx.vout[0].scriptPubKey = CScript() << OP_2; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - m_node.mempool->clear(); - - // subsidy changing - int nHeight = m_node.chainman->ActiveChain().Height(); - // Create an actual 209999-long block chain (without valid blocks). - while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { - CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); - CBlockIndex* next = new CBlockIndex(); - next->phashBlock = new uint256(InsecureRand256()); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); - next->pprev = prev; - next->nHeight = prev->nHeight + 1; - next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(*next); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // coinbase in tx_mempool, template creation fails + tx.vin.resize(1); + tx.vin[0].prevout.SetNull(); + tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; + tx.vout[0].nValue = 0; + hash = tx.GetHash(); + // give it a fee so it'll get mined + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + // Should throw bad-cb-multiple + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - // Extend to a 210000-long block chain. - while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { - CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); - CBlockIndex* next = new CBlockIndex(); - next->phashBlock = new uint256(InsecureRand256()); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); - next->pprev = prev; - next->nHeight = prev->nHeight + 1; - next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(*next); + + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // double spend txn pair in tx_mempool, template creation fails + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; + tx.vout[0].scriptPubKey = CScript() << OP_1; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vout[0].scriptPubKey = CScript() << OP_2; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - // invalid p2sh txn in *m_node.mempool, template creation fails - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].prevout.n = 0; - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE; - 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)); - tx.vin[0].prevout.hash = hash; - tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - // Should throw block-validation-failed - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); - m_node.mempool->clear(); - - // Delete the dummy blocks again. - while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { - CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); - m_node.chainman->ActiveChain().SetTip(*Assert(del->pprev)); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); - delete del->phashBlock; - delete del; + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // subsidy changing + int nHeight = m_node.chainman->ActiveChain().Height(); + // Create an actual 209999-long block chain (without valid blocks). + while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* next = new CBlockIndex(); + next->phashBlock = new uint256(InsecureRand256()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); + next->pprev = prev; + next->nHeight = prev->nHeight + 1; + next->BuildSkip(); + m_node.chainman->ActiveChain().SetTip(*next); + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + // Extend to a 210000-long block chain. + while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* next = new CBlockIndex(); + next->phashBlock = new uint256(InsecureRand256()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); + next->pprev = prev; + next->nHeight = prev->nHeight + 1; + next->BuildSkip(); + m_node.chainman->ActiveChain().SetTip(*next); + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + + // invalid p2sh txn in tx_mempool, template creation fails + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = BLOCKSUBSIDY - LOWFEE; + CScript script = CScript() << OP_0; + tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + // Should throw block-validation-failed + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); + + // Delete the dummy blocks again. + while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { + CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); + m_node.chainman->ActiveChain().SetTip(*Assert(del->pprev)); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); + delete del->phashBlock; + delete del; + } } + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + // non-final txs in mempool SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); const int flags{LOCKTIME_VERIFY_SEQUENCE}; @@ -388,9 +432,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail { CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); @@ -402,9 +446,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail const int SEQUENCE_LOCK_TIME = 512; // Sequence locks pass 512 seconds later for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) @@ -425,9 +469,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights[0] = baseheight + 3; tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block // absolute time locked @@ -436,9 +480,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later // mempool-dependent transactions (not added) @@ -447,15 +491,16 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.nLockTime = 0; tx.vin[0].nSequence = 0; BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass tx.vin[0].nSequence = 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG; - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); // None of the of the absolute height/time locked tx should have made // it into the template because we still check IsFinalTx in CreateNewBlock, @@ -470,12 +515,15 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C m_node.chainman->ActiveChain().Tip()->nHeight++; SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); } -void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + TestMemPoolEntryHelper entry; // Test that a tx below min fee but prioritised is included @@ -487,29 +535,29 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c 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_mempool.addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_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)); + tx_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); + tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_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); + tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_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 @@ -520,21 +568,21 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c 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_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + tx_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_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + tx_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)); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); - auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + auto pblocktemplate = AssemblerForTest(tx_mempool).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); @@ -553,15 +601,12 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c 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; - + CTxMemPool& tx_mempool{*m_node.mempool}; // Simple block creation, nothing special yet: - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); // We can't make transactions until we have inputs // Therefore, load 110 blocks :) @@ -593,23 +638,18 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) } LOCK(cs_main); - LOCK(m_node.mempool->cs); - TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight); + TestBasicMining(scriptPubKey, txFirst, baseheight); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); - m_node.mempool->clear(); - TestPackageSelection(chainparams, scriptPubKey, txFirst); + TestPackageSelection(scriptPubKey, txFirst); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); - m_node.mempool->clear(); - - TestPrioritisedMining(chainparams, scriptPubKey, txFirst); - fCheckpointsEnabled = true; + TestPrioritisedMining(scriptPubKey, txFirst); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp index 95e8476b77..9387c01e73 100644 --- a/src/test/miniscript_tests.cpp +++ b/src/test/miniscript_tests.cpp @@ -296,6 +296,9 @@ BOOST_AUTO_TEST_CASE(fixed_tests) // Same when the duplicates are on different levels in the tree const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER); BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey()); + // Sanity check the opposite is true, too. An otherwise sane Miniscript with no duplicate keys is sane. + const auto ms_nondup = miniscript::FromString("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", CONVERTER); + BOOST_CHECK(ms_nondup && ms_nondup->CheckDuplicateKey() && ms_nondup->IsSane()); // Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons: // 1. It can be spent without a signature // 2. It contains timelock mixes diff --git a/src/test/minisketch_tests.cpp b/src/test/minisketch_tests.cpp index 9c53ace633..81f2aad623 100644 --- a/src/test/minisketch_tests.cpp +++ b/src/test/minisketch_tests.cpp @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(minisketch_test) Minisketch sketch_c = std::move(sketch_ar); sketch_c.Merge(sketch_br); auto dec = sketch_c.Decode(errors); - BOOST_CHECK(dec.has_value()); + BOOST_REQUIRE(dec.has_value()); auto sols = std::move(*dec); std::sort(sols.begin(), sols.end()); for (uint32_t i = 0; i < a_not_b; ++i) BOOST_CHECK_EQUAL(sols[i], start_a + i); diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index f6642d3218..f24509dd97 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -805,6 +805,8 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) { + LOCK(NetEventsInterface::g_msgproc_mutex); + // Tests the following scenario: // * -bind=3.4.5.6:20001 is specified // * we make an outbound connection to a peer @@ -840,7 +842,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) const int64_t time{0}; const CNetMsgMaker msg_maker{PROTOCOL_VERSION}; - // Force CChainState::IsInitialBlockDownload() to return false. + // Force Chainstate::IsInitialBlockDownload() to return false. // Otherwise PushAddress() isn't called by PeerManager::ProcessMessage(). TestChainState& chainstate = *static_cast<TestChainState*>(&m_node.chainman->ActiveChainstate()); @@ -889,10 +891,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) } }; - { - LOCK(peer.cs_sendProcessing); - m_node.peerman->SendMessages(&peer); - } + m_node.peerman->SendMessages(&peer); BOOST_CHECK(sent); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index c2d2fa37b4..0e1e9ae211 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -84,12 +84,12 @@ BOOST_AUTO_TEST_CASE(netbase_properties) } -bool static TestSplitHost(const std::string& test, const std::string& host, uint16_t port) +bool static TestSplitHost(const std::string& test, const std::string& host, uint16_t port, bool validPort=true) { std::string hostOut; uint16_t portOut{0}; - SplitHostPort(test, portOut, hostOut); - return hostOut == host && port == portOut; + bool validPortOut = SplitHostPort(test, portOut, hostOut); + return hostOut == host && portOut == port && validPortOut == validPort; } BOOST_AUTO_TEST_CASE(netbase_splithost) @@ -109,6 +109,23 @@ BOOST_AUTO_TEST_CASE(netbase_splithost) BOOST_CHECK(TestSplitHost(":8333", "", 8333)); BOOST_CHECK(TestSplitHost("[]:8333", "", 8333)); BOOST_CHECK(TestSplitHost("", "", 0)); + BOOST_CHECK(TestSplitHost(":65535", "", 65535)); + BOOST_CHECK(TestSplitHost(":65536", ":65536", 0, false)); + BOOST_CHECK(TestSplitHost(":-1", ":-1", 0, false)); + BOOST_CHECK(TestSplitHost("[]:70001", "[]:70001", 0, false)); + BOOST_CHECK(TestSplitHost("[]:-1", "[]:-1", 0, false)); + BOOST_CHECK(TestSplitHost("[]:-0", "[]:-0", 0, false)); + BOOST_CHECK(TestSplitHost("[]:0", "", 0, false)); + BOOST_CHECK(TestSplitHost("[]:1/2", "[]:1/2", 0, false)); + BOOST_CHECK(TestSplitHost("[]:1E2", "[]:1E2", 0, false)); + BOOST_CHECK(TestSplitHost("127.0.0.1:65536", "127.0.0.1:65536", 0, false)); + BOOST_CHECK(TestSplitHost("127.0.0.1:0", "127.0.0.1", 0, false)); + BOOST_CHECK(TestSplitHost("127.0.0.1:", "127.0.0.1:", 0, false)); + BOOST_CHECK(TestSplitHost("127.0.0.1:1/2", "127.0.0.1:1/2", 0, false)); + BOOST_CHECK(TestSplitHost("127.0.0.1:1E2", "127.0.0.1:1E2", 0, false)); + BOOST_CHECK(TestSplitHost("www.bitcoincore.org:65536", "www.bitcoincore.org:65536", 0, false)); + BOOST_CHECK(TestSplitHost("www.bitcoincore.org:0", "www.bitcoincore.org", 0, false)); + BOOST_CHECK(TestSplitHost("www.bitcoincore.org:", "www.bitcoincore.org:", 0, false)); } bool static TestParse(std::string src, std::string canon) diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 3f66a8fc46..b652dc44c0 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -4,6 +4,7 @@ #include <policy/fees.h> #include <policy/policy.h> +#include <test/util/txmempool.h> #include <txmempool.h> #include <uint256.h> #include <util/time.h> diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp index c489ac04e9..d4487cd941 100644 --- a/src/test/raii_event_tests.cpp +++ b/src/test/raii_event_tests.cpp @@ -4,8 +4,8 @@ #include <event2/event.h> +#include <cstdlib> #include <map> -#include <stdlib.h> #include <support/events.h> diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp index c88cd36688..d362c85560 100644 --- a/src/test/rbf_tests.cpp +++ b/src/test/rbf_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <policy/rbf.h> #include <random.h> +#include <test/util/txmempool.h> #include <txmempool.h> #include <util/system.h> #include <util/time.h> diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index 55486db6b2..d78f62972f 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <coins.h> #include <consensus/consensus.h> #include <consensus/tx_verify.h> #include <key.h> diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index f160bb08a5..11f4be7fef 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <test/util/setup_common.h> -#include <util/system.h> +#include <common/run_command.h> #include <univalue.h> #ifdef ENABLE_EXTERNAL_SIGNER diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index 62c7ddb673..643d9221fe 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -69,11 +69,16 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) } } + // It is not safe to stop and destroy the index until it finishes handling + // the last BlockConnected notification. The BlockUntilSyncedToCurrentChain() + // call above is sufficient to ensure this, but the + // SyncWithValidationInterfaceQueue() call below is also needed to ensure + // TSAN always sees the test thread waiting for the notification thread, and + // avoid potential false positive reports. + SyncWithValidationInterfaceQueue(); + // shutdown sequence (c.f. Shutdown() in init.cpp) txindex.Stop(); - - // Let scheduler events finish running to avoid accessing any memory related to txindex after it is destructed - SyncWithValidationInterfaceQueue(); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txreconciliation_tests.cpp b/src/test/txreconciliation_tests.cpp new file mode 100644 index 0000000000..bd74998002 --- /dev/null +++ b/src/test/txreconciliation_tests.cpp @@ -0,0 +1,86 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/txreconciliation.h> + +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(txreconciliation_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(RegisterPeerTest) +{ + TxReconciliationTracker tracker(1); + const uint64_t salt = 0; + + // Prepare a peer for reconciliation. + tracker.PreRegisterPeer(0); + + // Both roles are false, don't register. + BOOST_CHECK(tracker.RegisterPeer(/*peer_id=*/0, /*is_peer_inbound=*/true, + /*is_peer_recon_initiator=*/false, + /*is_peer_recon_responder=*/false, + /*peer_recon_version=*/1, salt) == + ReconciliationRegisterResult::PROTOCOL_VIOLATION); + + // Invalid roles for the given connection direction. + BOOST_CHECK(tracker.RegisterPeer(0, true, false, true, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION); + BOOST_CHECK(tracker.RegisterPeer(0, false, true, false, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION); + + // Invalid version. + BOOST_CHECK(tracker.RegisterPeer(0, true, true, false, 0, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION); + + // Valid registration. + BOOST_REQUIRE(!tracker.IsPeerRegistered(0)); + BOOST_REQUIRE(tracker.RegisterPeer(0, true, true, false, 1, salt) == ReconciliationRegisterResult::SUCCESS); + BOOST_CHECK(tracker.IsPeerRegistered(0)); + + // Reconciliation version is higher than ours, should be able to register. + BOOST_REQUIRE(!tracker.IsPeerRegistered(1)); + tracker.PreRegisterPeer(1); + BOOST_REQUIRE(tracker.RegisterPeer(1, true, true, false, 2, salt) == ReconciliationRegisterResult::SUCCESS); + BOOST_CHECK(tracker.IsPeerRegistered(1)); + + // Do not register if there were no pre-registration for the peer. + BOOST_REQUIRE(tracker.RegisterPeer(100, true, true, false, 1, salt) == ReconciliationRegisterResult::NOT_FOUND); + BOOST_CHECK(!tracker.IsPeerRegistered(100)); +} + +BOOST_AUTO_TEST_CASE(ForgetPeerTest) +{ + TxReconciliationTracker tracker(1); + NodeId peer_id0 = 0; + + // Removing peer after pre-registring works and does not let to register the peer. + tracker.PreRegisterPeer(peer_id0); + tracker.ForgetPeer(peer_id0); + BOOST_CHECK(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::NOT_FOUND); + + // Removing peer after it is registered works. + tracker.PreRegisterPeer(peer_id0); + BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0)); + BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::SUCCESS); + BOOST_CHECK(tracker.IsPeerRegistered(peer_id0)); + tracker.ForgetPeer(peer_id0); + BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0)); +} + +BOOST_AUTO_TEST_CASE(IsPeerRegisteredTest) +{ + TxReconciliationTracker tracker(1); + NodeId peer_id0 = 0; + + BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0)); + tracker.PreRegisterPeer(peer_id0); + BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0)); + + BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::SUCCESS); + BOOST_CHECK(tracker.IsPeerRegistered(peer_id0)); + + tracker.ForgetPeer(peer_id0); + BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h index 13e0e684b8..0ca63810f3 100644 --- a/src/test/util/chainstate.h +++ b/src/test/util/chainstate.h @@ -7,25 +7,38 @@ #include <clientversion.h> #include <fs.h> +#include <logging.h> #include <node/context.h> #include <node/utxo_snapshot.h> #include <rpc/blockchain.h> +#include <test/util/setup_common.h> #include <validation.h> #include <univalue.h> -#include <boost/test/unit_test.hpp> - const auto NoMalleation = [](AutoFile& file, node::SnapshotMetadata& meta){}; /** * Create and activate a UTXO snapshot, optionally providing a function to * malleate the snapshot. + * + * If `reset_chainstate` is true, reset the original chainstate back to the genesis + * block. This allows us to simulate more realistic conditions in which a snapshot is + * loaded into an otherwise mostly-uninitialized datadir. It also allows us to test + * conditions that would otherwise cause shutdowns based on the IBD chainstate going + * past the snapshot it generated. */ template<typename F = decltype(NoMalleation)> static bool -CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F malleation = NoMalleation) +CreateAndActivateUTXOSnapshot( + TestingSetup* fixture, + F malleation = NoMalleation, + bool reset_chainstate = false, + bool in_memory_chainstate = false) { + node::NodeContext& node = fixture->m_node; + fs::path root = fixture->m_path_root; + // Write out a snapshot to the test's tempdir. // int height; @@ -36,8 +49,8 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma UniValue result = CreateUTXOSnapshot( node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path); - BOOST_TEST_MESSAGE( - "Wrote UTXO snapshot to " << fs::PathToString(snapshot_path.make_preferred()) << ": " << result.write()); + LogPrintf( + "Wrote UTXO snapshot to %s: %s", fs::PathToString(snapshot_path.make_preferred()), result.write()); // Read the written snapshot in and then activate it. // @@ -48,7 +61,38 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma malleation(auto_infile, metadata); - return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory=*/true); + if (reset_chainstate) { + { + // What follows is code to selectively reset chainstate data without + // disturbing the existing BlockManager instance, which is needed to + // recognize the headers chain previously generated by the chainstate we're + // removing. Without those headers, we can't activate the snapshot below. + // + // This is a stripped-down version of node::LoadChainstate which + // preserves the block index. + LOCK(::cs_main); + uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash(); + node.chainman->ResetChainstates(); + node.chainman->InitializeChainstate(node.mempool.get()); + Chainstate& chain = node.chainman->ActiveChainstate(); + Assert(chain.LoadGenesisBlock()); + // These cache values will be corrected shortly in `MaybeRebalanceCaches`. + chain.InitCoinsDB(1 << 20, true, false, ""); + chain.InitCoinsCache(1 << 20); + chain.CoinsTip().SetBestBlock(gen_hash); + chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash)); + chain.LoadChainTip(); + node.chainman->MaybeRebalanceCaches(); + } + BlockValidationState state; + if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) { + throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); + } + Assert( + 0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight())); + } + + return node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate); } diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp index 21273ac5c1..2e3e16e681 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -44,10 +44,7 @@ void ConnmanTestMsg::Handshake(CNode& node, (void)connman.ReceiveMsgFrom(node, msg_version); node.fPauseSend = false; connman.ProcessMessagesOnce(node); - { - LOCK(node.cs_sendProcessing); - peerman.SendMessages(&node); - } + peerman.SendMessages(&node); if (node.fDisconnect) return; assert(node.nVersion == version); assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION)); @@ -60,10 +57,7 @@ void ConnmanTestMsg::Handshake(CNode& node, (void)connman.ReceiveMsgFrom(node, msg_verack); node.fPauseSend = false; connman.ProcessMessagesOnce(node); - { - LOCK(node.cs_sendProcessing); - peerman.SendMessages(&node); - } + peerman.SendMessages(&node); assert(node.fSuccessfullyConnected == true); } } diff --git a/src/test/util/net.h b/src/test/util/net.h index b339bee32a..9ae7981980 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -44,9 +44,10 @@ struct ConnmanTestMsg : public CConnman { ServiceFlags remote_services, ServiceFlags local_services, int32_t version, - bool relay_txs); + bool relay_txs) + EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex); - void ProcessMessagesOnce(CNode& node) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); } + void ProcessMessagesOnce(CNode& node) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); } void NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const; @@ -165,6 +166,10 @@ public: return 0; } + bool SetNonBlocking() const override { return true; } + + bool IsSelectable() const override { return true; } + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index c3989a12fe..0d0db176f9 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -36,6 +36,7 @@ #include <shutdown.h> #include <streams.h> #include <test/util/net.h> +#include <test/util/txmempool.h> #include <timedata.h> #include <txdb.h> #include <txmempool.h> @@ -60,7 +61,6 @@ using node::ApplyArgsManOptions; using node::BlockAssembler; using node::CalculateCacheSizes; using node::LoadChainstate; -using node::NodeContext; using node::RegenerateCommitments; using node::VerifyLoadedChainstate; @@ -146,7 +146,6 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)); m_node.chain = interfaces::MakeChain(m_node); - fCheckBlockIndex = true; static bool noui_connected = false; if (!noui_connected) { noui_connect(); @@ -162,19 +161,6 @@ BasicTestingSetup::~BasicTestingSetup() gArgs.ClearArgs(); } -CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) -{ - CTxMemPool::Options mempool_opts{ - .estimator = node.fee_estimator.get(), - // Default to always checking mempool regardless of - // chainparams.DefaultConsistencyChecks for tests - .check_ratio = 1, - }; - const auto err{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)}; - Assert(!err); - return mempool_opts; -} - ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) : BasicTestingSetup(chainName, extra_args) { @@ -194,14 +180,13 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve const ChainstateManager::Options chainman_opts{ .chainparams = chainparams, .adjusted_time_callback = GetAdjustedTime, + .check_block_index = true, }; m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts); m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true); - // Start script-checking threads. Set g_parallel_script_checks to true so they are used. constexpr int script_check_threads = 2; StartScriptCheckWorkerThreads(script_check_threads); - g_parallel_script_checks = true; } ChainTestingSetup::~ChainTestingSetup() @@ -220,17 +205,12 @@ ChainTestingSetup::~ChainTestingSetup() m_node.chainman.reset(); } -TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) - : ChainTestingSetup(chainName, extra_args) +void TestingSetup::LoadVerifyActivateChainstate() { - // Ideally we'd move all the RPC tests to the functional testing framework - // instead of unit tests, but for now we need these here. - RegisterAllCoreRPCCommands(tableRPC); - node::ChainstateLoadOptions options; options.mempool = Assert(m_node.mempool.get()); - options.block_tree_db_in_memory = true; - options.coins_db_in_memory = true; + options.block_tree_db_in_memory = m_block_tree_db_in_memory; + options.coins_db_in_memory = m_coins_db_in_memory; options.reindex = node::fReindex; options.reindex_chainstate = m_args.GetBoolArg("-reindex-chainstate", false); options.prune = node::fPruneMode; @@ -246,6 +226,22 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } +} + +TestingSetup::TestingSetup( + const std::string& chainName, + const std::vector<const char*>& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : ChainTestingSetup(chainName, extra_args), + m_coins_db_in_memory(coins_db_in_memory), + m_block_tree_db_in_memory(block_tree_db_in_memory) +{ + // Ideally we'd move all the RPC tests to the functional testing framework + // instead of unit tests, but for now we need these here. + RegisterAllCoreRPCCommands(tableRPC); + + LoadVerifyActivateChainstate(); m_node.netgroupman = std::make_unique<NetGroupManager>(/*asmap=*/std::vector<bool>()); m_node.addrman = std::make_unique<AddrMan>(*m_node.netgroupman, @@ -263,8 +259,12 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const } } -TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args) - : TestingSetup{chain_name, extra_args} +TestChain100Setup::TestChain100Setup( + const std::string& chain_name, + const std::vector<const char*>& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : TestingSetup{CBaseChainParams::REGTEST, extra_args, coins_db_in_memory, block_tree_db_in_memory} { SetMockTime(1598887952); constexpr std::array<unsigned char, 32> vchKey = { @@ -296,7 +296,7 @@ void TestChain100Setup::mineBlocks(int num_blocks) CBlock TestChain100Setup::CreateBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState& chainstate) + Chainstate& chainstate) { CBlock block = BlockAssembler{chainstate, nullptr}.CreateNewBlock(scriptPubKey)->block; @@ -314,7 +314,7 @@ CBlock TestChain100Setup::CreateBlock( CBlock TestChain100Setup::CreateAndProcessBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState* chainstate) + Chainstate* chainstate) { if (!chainstate) { chainstate = &Assert(m_node.chainman)->ActiveChainstate(); @@ -423,17 +423,6 @@ std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContex return mempool_transactions; } -CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const -{ - return FromTx(MakeTransactionRef(tx)); -} - -CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const -{ - return CTxMemPoolEntry(tx, nFee, nTime, nHeight, - spendsCoinbase, sigOpCost, lp); -} - /** * @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) * with 9 txs. diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index ed2c5db7e6..dfa36039a2 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -8,21 +8,23 @@ #include <chainparamsbase.h> #include <fs.h> #include <key.h> -#include <util/system.h> #include <node/caches.h> #include <node/context.h> +#include <primitives/transaction.h> #include <pubkey.h> #include <random.h> #include <stdexcept> -#include <txmempool.h> #include <util/check.h> #include <util/string.h> +#include <util/system.h> #include <util/vector.h> #include <functional> #include <type_traits> #include <vector> +class Chainstate; + /** This is connected to the logger. Can be used to redirect logs to any other log */ extern const std::function<void(const std::string&)> G_TEST_LOG_FUN; @@ -90,9 +92,6 @@ struct BasicTestingSetup { ArgsManager m_args; }; - -CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); - /** Testing setup that performs all steps up until right before * ChainstateManager gets initialized. Meant for testing ChainstateManager * initialization behaviour. @@ -107,7 +106,16 @@ struct ChainTestingSetup : public BasicTestingSetup { /** Testing setup that configures a complete environment. */ struct TestingSetup : public ChainTestingSetup { - explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {}); + bool m_coins_db_in_memory{true}; + bool m_block_tree_db_in_memory{true}; + + void LoadVerifyActivateChainstate(); + + explicit TestingSetup( + const std::string& chainName = CBaseChainParams::MAIN, + const std::vector<const char*>& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); }; /** Identical to TestingSetup, but chain set to regtest */ @@ -124,8 +132,11 @@ class CScript; * Testing fixture that pre-creates a 100-block REGTEST-mode block chain */ struct TestChain100Setup : public TestingSetup { - TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST, - const std::vector<const char*>& extra_args = {}); + TestChain100Setup( + const std::string& chain_name = CBaseChainParams::REGTEST, + const std::vector<const char*>& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); /** * Create a new block with just given transactions, coinbase paying to @@ -134,7 +145,7 @@ struct TestChain100Setup : public TestingSetup { */ CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState* chainstate = nullptr); + Chainstate* chainstate = nullptr); /** * Create a new block with just given transactions, coinbase paying to @@ -143,7 +154,7 @@ struct TestChain100Setup : public TestingSetup { CBlock CreateBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState& chainstate); + Chainstate& chainstate); //! Mine a series of new blocks on the active chain. void mineBlocks(int num_blocks); @@ -201,33 +212,6 @@ std::unique_ptr<T> MakeNoLogFileContext(const std::string& chain_name = CBaseCha return std::make_unique<T>(chain_name, arguments); } -class CTxMemPoolEntry; - -struct TestMemPoolEntryHelper -{ - // Default values - CAmount nFee; - int64_t nTime; - unsigned int nHeight; - bool spendsCoinbase; - unsigned int sigOpCost; - LockPoints lp; - - TestMemPoolEntryHelper() : - nFee(0), nTime(0), nHeight(1), - spendsCoinbase(false), sigOpCost(4) { } - - CTxMemPoolEntry FromTx(const CMutableTransaction& tx) const; - CTxMemPoolEntry FromTx(const CTransactionRef& tx) const; - - // Change the default value - TestMemPoolEntryHelper &Fee(CAmount _fee) { nFee = _fee; return *this; } - TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; } - TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; } - TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } - TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; } -}; - CBlock getBlock13b8a(); // define an implicit conversion here so that uint256 may be used directly in BOOST_CHECK_* diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp new file mode 100644 index 0000000000..12cc1a4a3d --- /dev/null +++ b/src/test/util/txmempool.cpp @@ -0,0 +1,39 @@ +// 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 <test/util/txmempool.h> + +#include <chainparams.h> +#include <node/context.h> +#include <node/mempool_args.h> +#include <txmempool.h> +#include <util/check.h> +#include <util/time.h> +#include <util/translation.h> + +using node::NodeContext; + +CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) +{ + CTxMemPool::Options mempool_opts{ + .estimator = node.fee_estimator.get(), + // Default to always checking mempool regardless of + // chainparams.DefaultConsistencyChecks for tests + .check_ratio = 1, + }; + const auto err{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)}; + Assert(!err); + return mempool_opts; +} + +CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const +{ + return FromTx(MakeTransactionRef(tx)); +} + +CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const +{ + return CTxMemPoolEntry(tx, nFee, nTime, nHeight, + spendsCoinbase, sigOpCost, lp); +} diff --git a/src/test/util/txmempool.h b/src/test/util/txmempool.h new file mode 100644 index 0000000000..70b9ed88db --- /dev/null +++ b/src/test/util/txmempool.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef BITCOIN_TEST_UTIL_TXMEMPOOL_H +#define BITCOIN_TEST_UTIL_TXMEMPOOL_H + +#include <txmempool.h> + +namespace node { +struct NodeContext; +} + +CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); + +struct TestMemPoolEntryHelper +{ + // Default values + CAmount nFee{0}; + int64_t nTime{0}; + unsigned int nHeight{1}; + bool spendsCoinbase{false}; + unsigned int sigOpCost{4}; + LockPoints lp; + + CTxMemPoolEntry FromTx(const CMutableTransaction& tx) const; + CTxMemPoolEntry FromTx(const CTransactionRef& tx) const; + + // Change the default value + TestMemPoolEntryHelper &Fee(CAmount _fee) { nFee = _fee; return *this; } + TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; } + TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; } + TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } + TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; } +}; + +#endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H diff --git a/src/test/util/validation.h b/src/test/util/validation.h index b0bc717b6c..cbe7745b81 100644 --- a/src/test/util/validation.h +++ b/src/test/util/validation.h @@ -9,7 +9,7 @@ class CValidationInterface; -struct TestChainState : public CChainState { +struct TestChainState : public Chainstate { /** Reset the ibd cache to its initial state */ void ResetIbd(); /** Toggle IsInitialBlockDownload from true to false */ diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 0f9f332dc6..009c27927f 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -26,6 +26,7 @@ #include <util/bitdeque.h> #include <array> +#include <cmath> #include <fstream> #include <limits> #include <map> @@ -282,14 +283,10 @@ BOOST_AUTO_TEST_CASE(util_TrimString) 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) +BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); BOOST_CHECK_EQUAL(FormatISO8601DateTime(0), "1970-01-01T00:00:00Z"); - - BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); - BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); - BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 98294b9028..f868c0d4e6 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup) -//! Test resizing coins-related CChainState caches during runtime. +//! Test resizing coins-related Chainstate caches during runtime. //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) return outp; }; - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); @@ -89,7 +89,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) // After adding some blocks to the tip, best block should have changed. BOOST_CHECK(::g_best_block != curr_tip); - BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root)); + BOOST_REQUIRE(CreateAndActivateUTXOSnapshot( + this, NoMalleation, /*reset_chainstate=*/ true)); // Ensure our active chain is the snapshot chainstate. BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive())); @@ -106,8 +107,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2); - CChainState& background_cs{*[&] { - for (CChainState* cs : chainman.GetAll()) { + Chainstate& background_cs{*[&] { + for (Chainstate* cs : chainman.GetAll()) { if (cs != &chainman.ActiveChainstate()) { return cs; } diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 7b7bd05b5c..22b9af1201 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -10,6 +10,7 @@ #include <sync.h> #include <test/util/chainstate.h> #include <test/util/setup_common.h> +#include <timedata.h> #include <uint256.h> #include <validation.h> #include <validationinterface.h> @@ -32,13 +33,13 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) ChainstateManager& manager = *m_node.chainman; CTxMemPool& mempool = *m_node.mempool; - std::vector<CChainState*> chainstates; + std::vector<Chainstate*> chainstates; BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -63,7 +64,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Create a snapshot-based chainstate. // const uint256 snapshot_blockhash = GetRandHash(); - CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate( + Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot( &mempool, snapshot_blockhash)); chainstates.push_back(&c2); @@ -111,11 +112,11 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) manager.m_total_coinsdb_cache = max_cache; manager.m_total_coinstip_cache = max_cache; - std::vector<CChainState*> chainstates; + std::vector<Chainstate*> chainstates; // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -133,7 +134,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a snapshot-based chainstate. // - CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash())); + Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -154,167 +155,245 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1); } -//! Test basic snapshot activation. -BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) -{ - ChainstateManager& chainman = *Assert(m_node.chainman); - - size_t initial_size; - size_t initial_total_coins{100}; - - // Make some initial assertions about the contents of the chainstate. +struct SnapshotTestSetup : TestChain100Setup { + // Run with coinsdb on the filesystem to support, e.g., moving invalidated + // chainstate dirs to "*_invalid". + // + // Note that this means the tests run considerably slower than in-memory DB + // tests, but we can't otherwise test this functionality since it relies on + // destructive filesystem operations. + SnapshotTestSetup() : TestChain100Setup{ + {}, + {}, + /*coins_db_in_memory=*/false, + /*block_tree_db_in_memory=*/false, + } { - LOCK(::cs_main); - CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip(); - initial_size = ibd_coinscache.GetCacheSize(); - size_t total_coins{0}; - - for (CTransactionRef& txn : m_coinbase_txns) { - COutPoint op{txn->GetHash(), 0}; - BOOST_CHECK(ibd_coinscache.HaveCoin(op)); - total_coins++; - } - - BOOST_CHECK_EQUAL(total_coins, initial_total_coins); - BOOST_CHECK_EQUAL(initial_size, initial_total_coins); } - // Snapshot should refuse to load at this height. - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root)); - BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash); - BOOST_CHECK(!chainman.SnapshotBlockhash()); - - // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can - // be found. - constexpr int snapshot_height = 110; - mineBlocks(10); - initial_size += 10; - initial_total_coins += 10; - - // Should not load malleated snapshots - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { - // A UTXO is missing but count is correct - metadata.m_coins_count -= 1; - - COutPoint outpoint; - Coin coin; - - auto_infile >> outpoint; - auto_infile >> coin; - })); - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { - // Coins count is larger than coins in file - metadata.m_coins_count += 1; - })); - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { - // Coins count is smaller than coins in file - metadata.m_coins_count -= 1; - })); - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { - // Wrong hash - metadata.m_base_blockhash = uint256::ZERO; - })); - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { - // Wrong hash - metadata.m_base_blockhash = uint256::ONE; - })); - - BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root)); - - // Ensure our active chain is the snapshot chainstate. - BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull()); - BOOST_CHECK_EQUAL( - *chainman.ActiveChainstate().m_from_snapshot_blockhash, - *chainman.SnapshotBlockhash()); - - // Ensure that the genesis block was not marked assumed-valid. - BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid())); - - const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params()); - const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()); - - BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx); - - // To be checked against later when we try loading a subsequent snapshot. - uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()}; - - // Make some assertions about the both chainstates. These checks ensure the - // legacy chainstate hasn't changed and that the newly created chainstate - // reflects the expected content. + std::tuple<Chainstate*, Chainstate*> SetupSnapshot() { - LOCK(::cs_main); - int chains_tested{0}; + ChainstateManager& chainman = *Assert(m_node.chainman); - for (CChainState* chainstate : chainman.GetAll()) { - BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); - CCoinsViewCache& coinscache = chainstate->CoinsTip(); + BOOST_CHECK(!chainman.IsSnapshotActive()); - // Both caches will be empty initially. - BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize()); + { + LOCK(::cs_main); + BOOST_CHECK(!chainman.IsSnapshotValidated()); + BOOST_CHECK(!node::FindSnapshotChainstateDir()); + } + size_t initial_size; + size_t initial_total_coins{100}; + + // Make some initial assertions about the contents of the chainstate. + { + LOCK(::cs_main); + CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip(); + initial_size = ibd_coinscache.GetCacheSize(); size_t total_coins{0}; for (CTransactionRef& txn : m_coinbase_txns) { COutPoint op{txn->GetHash(), 0}; - BOOST_CHECK(coinscache.HaveCoin(op)); + BOOST_CHECK(ibd_coinscache.HaveCoin(op)); total_coins++; } - BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize()); BOOST_CHECK_EQUAL(total_coins, initial_total_coins); - chains_tested++; + BOOST_CHECK_EQUAL(initial_size, initial_total_coins); } - BOOST_CHECK_EQUAL(chains_tested, 2); - } + Chainstate& validation_chainstate = chainman.ActiveChainstate(); + + // Snapshot should refuse to load at this height. + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this)); + BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash); + BOOST_CHECK(!chainman.SnapshotBlockhash()); + + // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can + // be found. + constexpr int snapshot_height = 110; + mineBlocks(10); + initial_size += 10; + initial_total_coins += 10; + + // Should not load malleated snapshots + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( + this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { + // A UTXO is missing but count is correct + metadata.m_coins_count -= 1; + + COutPoint outpoint; + Coin coin; + + auto_infile >> outpoint; + auto_infile >> coin; + })); + + BOOST_CHECK(!node::FindSnapshotChainstateDir()); + + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( + this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { + // Coins count is larger than coins in file + metadata.m_coins_count += 1; + })); + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( + this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { + // Coins count is smaller than coins in file + metadata.m_coins_count -= 1; + })); + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( + this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { + // Wrong hash + metadata.m_base_blockhash = uint256::ZERO; + })); + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( + this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { + // Wrong hash + metadata.m_base_blockhash = uint256::ONE; + })); + + BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this)); + BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir())); + + // Ensure our active chain is the snapshot chainstate. + BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull()); + BOOST_CHECK_EQUAL( + *chainman.ActiveChainstate().m_from_snapshot_blockhash, + *chainman.SnapshotBlockhash()); + + Chainstate& snapshot_chainstate = chainman.ActiveChainstate(); + + { + LOCK(::cs_main); - // Mine some new blocks on top of the activated snapshot chainstate. - constexpr size_t new_coins{100}; - mineBlocks(new_coins); // Defined in TestChain100Setup. + fs::path found = *node::FindSnapshotChainstateDir(); - { - LOCK(::cs_main); - size_t coins_in_active{0}; - size_t coins_in_background{0}; - size_t coins_missing_from_background{0}; + // Note: WriteSnapshotBaseBlockhash() is implicitly tested above. + BOOST_CHECK_EQUAL( + *node::ReadSnapshotBaseBlockhash(found), + *chainman.SnapshotBlockhash()); - for (CChainState* chainstate : chainman.GetAll()) { - BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); - CCoinsViewCache& coinscache = chainstate->CoinsTip(); - bool is_background = chainstate != &chainman.ActiveChainstate(); + // Ensure that the genesis block was not marked assumed-valid. + BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid()); + } - for (CTransactionRef& txn : m_coinbase_txns) { - COutPoint op{txn->GetHash(), 0}; - if (coinscache.HaveCoin(op)) { - (is_background ? coins_in_background : coins_in_active)++; - } else if (is_background) { - coins_missing_from_background++; + const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params()); + const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()); + + BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx); + + // To be checked against later when we try loading a subsequent snapshot. + uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()}; + + // Make some assertions about the both chainstates. These checks ensure the + // legacy chainstate hasn't changed and that the newly created chainstate + // reflects the expected content. + { + LOCK(::cs_main); + int chains_tested{0}; + + for (Chainstate* chainstate : chainman.GetAll()) { + BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); + CCoinsViewCache& coinscache = chainstate->CoinsTip(); + + // Both caches will be empty initially. + BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize()); + + size_t total_coins{0}; + + for (CTransactionRef& txn : m_coinbase_txns) { + COutPoint op{txn->GetHash(), 0}; + BOOST_CHECK(coinscache.HaveCoin(op)); + total_coins++; } + + BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize()); + BOOST_CHECK_EQUAL(total_coins, initial_total_coins); + chains_tested++; } + + BOOST_CHECK_EQUAL(chains_tested, 2); } - BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins); - BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins); - BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins); + // Mine some new blocks on top of the activated snapshot chainstate. + constexpr size_t new_coins{100}; + mineBlocks(new_coins); // Defined in TestChain100Setup. + + { + LOCK(::cs_main); + size_t coins_in_active{0}; + size_t coins_in_background{0}; + size_t coins_missing_from_background{0}; + + for (Chainstate* chainstate : chainman.GetAll()) { + BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); + CCoinsViewCache& coinscache = chainstate->CoinsTip(); + bool is_background = chainstate != &chainman.ActiveChainstate(); + + for (CTransactionRef& txn : m_coinbase_txns) { + COutPoint op{txn->GetHash(), 0}; + if (coinscache.HaveCoin(op)) { + (is_background ? coins_in_background : coins_in_active)++; + } else if (is_background) { + coins_missing_from_background++; + } + } + } + + BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins); + BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins); + BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins); + } + + // Snapshot should refuse to load after one has already loaded. + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this)); + + // Snapshot blockhash should be unchanged. + BOOST_CHECK_EQUAL( + *chainman.ActiveChainstate().m_from_snapshot_blockhash, + loaded_snapshot_blockhash); + return std::make_tuple(&validation_chainstate, &snapshot_chainstate); } - // Snapshot should refuse to load after one has already loaded. - BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root)); + // Simulate a restart of the node by flushing all state to disk, clearing the + // existing ChainstateManager, and unloading the block index. + // + // @returns a reference to the "restarted" ChainstateManager + ChainstateManager& SimulateNodeRestart() + { + ChainstateManager& chainman = *Assert(m_node.chainman); + + BOOST_TEST_MESSAGE("Simulating node restart"); + { + LOCK(::cs_main); + for (Chainstate* cs : chainman.GetAll()) { + cs->ForceFlushStateToDisk(); + } + chainman.ResetChainstates(); + BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0); + const ChainstateManager::Options chainman_opts{ + .chainparams = ::Params(), + .adjusted_time_callback = GetAdjustedTime, + }; + // For robustness, ensure the old manager is destroyed before creating a + // new one. + m_node.chainman.reset(); + m_node.chainman.reset(new ChainstateManager(chainman_opts)); + } + return *Assert(m_node.chainman); + } +}; - // Snapshot blockhash should be unchanged. - BOOST_CHECK_EQUAL( - *chainman.ActiveChainstate().m_from_snapshot_blockhash, - loaded_snapshot_blockhash); +//! Test basic snapshot activation. +BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup) +{ + this->SetupSnapshot(); } //! Test LoadBlockIndex behavior when multiple chainstates are in use. //! -//! - First, verfiy that setBlockIndexCandidates is as expected when using a single, +//! - First, verify that setBlockIndexCandidates is as expected when using a single, //! fully-validating chainstate. //! //! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate @@ -326,7 +405,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) { ChainstateManager& chainman = *Assert(m_node.chainman); CTxMemPool& mempool = *m_node.mempool; - CChainState& cs1 = chainman.ActiveChainstate(); + Chainstate& cs1 = chainman.ActiveChainstate(); int num_indexes{0}; int num_assumed_valid{0}; @@ -338,7 +417,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())}; auto reload_all_block_indexes = [&]() { - for (CChainState* cs : chainman.GetAll()) { + for (Chainstate* cs : chainman.GetAll()) { LOCK(::cs_main); cs->UnloadBlockIndex(); BOOST_CHECK(cs->setBlockIndexCandidates.empty()); @@ -373,8 +452,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid); - CChainState& cs2 = WITH_LOCK(::cs_main, - return chainman.InitializeChainstate(&mempool, GetRandHash())); + Chainstate& cs2 = WITH_LOCK(::cs_main, + return chainman.ActivateExistingSnapshot(&mempool, GetRandHash())); reload_all_block_indexes(); @@ -390,4 +469,59 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes); } +//! Ensure that snapshot chainstates initialize properly when found on disk. +BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup) +{ + this->SetupSnapshot(); + + ChainstateManager& chainman = *Assert(m_node.chainman); + + fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(); + BOOST_CHECK(fs::exists(snapshot_chainstate_dir)); + BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot"); + + BOOST_CHECK(chainman.IsSnapshotActive()); + const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(), + return chainman.ActiveTip()->GetBlockHash()); + + auto all_chainstates = chainman.GetAll(); + BOOST_CHECK_EQUAL(all_chainstates.size(), 2); + + // Test that simulating a shutdown (resetting ChainstateManager) and then performing + // chainstate reinitializing successfully cleans up the background-validation + // chainstate data, and we end up with a single chainstate that is at tip. + ChainstateManager& chainman_restarted = this->SimulateNodeRestart(); + + BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate"); + + // This call reinitializes the chainstates. + this->LoadVerifyActivateChainstate(); + + { + LOCK(chainman_restarted.GetMutex()); + BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 2); + BOOST_CHECK(chainman_restarted.IsSnapshotActive()); + BOOST_CHECK(!chainman_restarted.IsSnapshotValidated()); + + BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash); + BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210); + } + + BOOST_TEST_MESSAGE( + "Ensure we can mine blocks on top of the initialized snapshot chainstate"); + mineBlocks(10); + { + LOCK(chainman_restarted.GetMutex()); + BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220); + + // Background chainstate should be unaware of new blocks on the snapshot + // chainstate. + for (Chainstate* cs : chainman_restarted.GetAll()) { + if (cs != &chainman_restarted.ActiveChainstate()) { + BOOST_CHECK_EQUAL(cs->m_chain.Height(), 110); + } + } + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index 74b2af6858..c06e6c8d3b 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -13,11 +13,11 @@ BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, TestingSetup) //! Test utilities for detecting when we need to flush the coins cache based //! on estimated memory usage. //! -//! @sa CChainState::GetCoinsCacheSizeState() +//! @sa Chainstate::GetCoinsCacheSizeState() //! BOOST_AUTO_TEST_CASE(getcoinscachesizestate) { - CChainState& chainstate{m_node.chainman->ActiveChainstate()}; + Chainstate& chainstate{m_node.chainman->ActiveChainstate()}; constexpr bool is_64_bit = sizeof(void*) == 8; diff --git a/src/txdb.h b/src/txdb.h index a04596f7bb..8c41e26f6a 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -9,6 +9,7 @@ #include <coins.h> #include <dbwrapper.h> #include <sync.h> +#include <fs.h> #include <memory> #include <optional> @@ -72,6 +73,9 @@ public: //! Dynamically alter the underlying leveldb cache size. void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + //! @returns filesystem path to on-disk storage or std::nullopt if in memory. + std::optional<fs::path> StoragePath() { return m_db->StoragePath(); } }; /** Access to the block database (blocks/index/) */ diff --git a/src/txmempool.cpp b/src/txmempool.cpp index b151953d0d..84ed2e9ef5 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -24,38 +24,6 @@ #include <cmath> #include <optional> -// Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. -struct update_descendant_state -{ - update_descendant_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount) : - modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount) - {} - - void operator() (CTxMemPoolEntry &e) - { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); } - - private: - int64_t modifySize; - CAmount modifyFee; - int64_t modifyCount; -}; - -struct update_ancestor_state -{ - update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost) : - modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost) - {} - - void operator() (CTxMemPoolEntry &e) - { e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOpsCost); } - - private: - int64_t modifySize; - CAmount modifyFee; - int64_t modifyCount; - int64_t modifySigOpsCost; -}; - bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) { AssertLockHeld(cs_main); @@ -146,7 +114,9 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan modifyCount++; cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant)); // Update ancestor state for each descendant - mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost())); + mapTx.modify(mapTx.iterator_to(descendant), [=](CTxMemPoolEntry& e) { + e.UpdateAncestorState(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost()); + }); // Don't directly remove the transaction here -- doing so would // invalidate iterators in cachedDescendants. Mark it for removal // by inserting into descendants_to_remove. @@ -155,7 +125,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan } } } - mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount)); + mapTx.modify(updateIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); }); } void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) @@ -175,7 +145,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes // Iterate in reverse, so that whenever we are looking at a transaction // we are sure that all in-mempool descendants have already been processed. // This maximizes the benefit of the descendant cache and guarantees that - // CTxMemPool::m_children will be updated, an assumption made in + // CTxMemPoolEntry::m_children will be updated, an assumption made in // UpdateForDescendants. for (const uint256 &hash : reverse_iterate(vHashesToUpdate)) { // calculate children from mapNextTx @@ -184,7 +154,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes continue; } auto iter = mapNextTx.lower_bound(COutPoint(hash, 0)); - // First calculate the children, and update CTxMemPool::m_children to + // First calculate the children, and update CTxMemPoolEntry::m_children to // include them, and update their CTxMemPoolEntry::m_parents to include this tx. // we cache the in-mempool children to avoid duplicate updates { @@ -217,10 +187,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, size_t entry_count, setEntries& setAncestors, CTxMemPoolEntry::Parents& staged_ancestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const { size_t totalSizeWithAncestors = entry_size; @@ -233,14 +200,14 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, staged_ancestors.erase(stage); totalSizeWithAncestors += stageit->GetTxSize(); - if (stageit->GetSizeWithDescendants() + entry_size > limitDescendantSize) { - errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantSize); + if (stageit->GetSizeWithDescendants() + entry_size > static_cast<uint64_t>(limits.descendant_size_vbytes)) { + errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes); return false; - } else if (stageit->GetCountWithDescendants() + entry_count > limitDescendantCount) { - errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantCount); + } else if (stageit->GetCountWithDescendants() + entry_count > static_cast<uint64_t>(limits.descendant_count)) { + errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count); return false; - } else if (totalSizeWithAncestors > limitAncestorSize) { - errString = strprintf("exceeds ancestor size limit [limit: %u]", limitAncestorSize); + } else if (totalSizeWithAncestors > static_cast<uint64_t>(limits.ancestor_size_vbytes)) { + errString = strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes); return false; } @@ -252,8 +219,8 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, if (setAncestors.count(parent_it) == 0) { staged_ancestors.insert(parent); } - if (staged_ancestors.size() + setAncestors.size() + entry_count > limitAncestorCount) { - errString = strprintf("too many unconfirmed ancestors [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + setAncestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count); return false; } } @@ -263,10 +230,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, } bool CTxMemPool::CheckPackageLimits(const Package& package, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const { CTxMemPoolEntry::Parents staged_ancestors; @@ -277,8 +241,8 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, std::optional<txiter> piter = GetIter(input.prevout.hash); if (piter) { staged_ancestors.insert(**piter); - if (staged_ancestors.size() + package.size() > limitAncestorCount) { - errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + package.size() > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count); return false; } } @@ -290,8 +254,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, setEntries setAncestors; const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(), setAncestors, staged_ancestors, - limitAncestorCount, limitAncestorSize, - limitDescendantCount, limitDescendantSize, errString); + limits, errString); // It's possible to overestimate the ancestor/descendant totals. if (!ret) errString.insert(0, "possibly "); return ret; @@ -299,10 +262,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString, bool fSearchForParents /* = true */) const { @@ -317,8 +277,8 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); if (piter) { staged_ancestors.insert(**piter); - if (staged_ancestors.size() + 1 > limitAncestorCount) { - errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + 1 > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count); return false; } } @@ -332,8 +292,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1, setAncestors, staged_ancestors, - limitAncestorCount, limitAncestorSize, - limitDescendantCount, limitDescendantSize, errString); + limits, errString); } void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors) @@ -347,7 +306,7 @@ void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors const int64_t updateSize = updateCount * it->GetTxSize(); const CAmount updateFee = updateCount * it->GetModifiedFee(); for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, update_descendant_state(updateSize, updateFee, updateCount)); + mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(updateSize, updateFee, updateCount); }); } } @@ -362,7 +321,7 @@ void CTxMemPool::UpdateEntryForAncestors(txiter it, const setEntries &setAncesto updateFee += ancestorIt->GetModifiedFee(); updateSigOpsCost += ancestorIt->GetSigOpCost(); } - mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOpsCost)); + mapTx.modify(it, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(updateSize, updateFee, updateCount, updateSigOpsCost); }); } void CTxMemPool::UpdateChildrenForRemoval(txiter it) @@ -377,7 +336,6 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b { // For each entry, walk back all ancestors and decrement size associated with this // transaction - const uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); if (updateDescendants) { // updateDescendants should be true whenever we're not recursively // removing a tx and all its descendants, eg when a transaction is @@ -393,7 +351,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b CAmount modifyFee = -removeIt->GetModifiedFee(); int modifySigOps = -removeIt->GetSigOpCost(); for (txiter dit : setDescendants) { - mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps)); + mapTx.modify(dit, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(modifySize, modifyFee, -1, modifySigOps); }); } } } @@ -420,7 +378,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b // mempool parents we'd calculate by searching, and it's important that // we use the cached notion of ancestor transactions as the set of // things to update for removal. - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy, false); // Note that UpdateAncestorsOf severs the child links that point to // removeIt in the entries for the parents of removeIt. UpdateAncestorsOf(false, removeIt, setAncestors); @@ -774,9 +732,8 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei assert(std::equal(setParentCheck.begin(), setParentCheck.end(), it->GetMemPoolParentsConst().begin(), comp)); // Verify ancestor state is correct. setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); + CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy); uint64_t nCountCheck = setAncestors.size() + 1; uint64_t nSizeCheck = it->GetTxSize(); CAmount nFeesCheck = it->GetModifiedFee(); @@ -938,18 +895,17 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); }); // Now update all ancestors' modified fees with descendants setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy, false); for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, update_descendant_state(0, nFeeDelta, 0)); + mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e){ e.UpdateDescendantState(0, nFeeDelta, 0);}); } // Now update all descendants' modified fees with ancestors setEntries setDescendants; CalculateDescendants(it, setDescendants); setDescendants.erase(it); for (txiter descendantIt : setDescendants) { - mapTx.modify(descendantIt, update_ancestor_state(0, nFeeDelta, 0, 0)); + mapTx.modify(descendantIt, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(0, nFeeDelta, 0, 0); }); } ++nTransactionsUpdated; } @@ -1079,9 +1035,8 @@ int CTxMemPool::Expire(std::chrono::seconds time) void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate) { setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); + CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy); return addUnchecked(entry, setAncestors, validFeeEstimate); } diff --git a/src/txmempool.h b/src/txmempool.h index d06816ba97..4afaac0506 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -35,7 +35,7 @@ class CBlockIndex; class CChain; -class CChainState; +class Chainstate; extern RecursiveMutex cs_main; /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */ @@ -526,6 +526,8 @@ public: typedef std::set<txiter, CompareIteratorByHash> setEntries; + using Limits = kernel::MemPoolLimits; + uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); private: typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap; @@ -545,19 +547,22 @@ private: /** * Helper function to calculate all in-mempool ancestors of staged_ancestors and apply ancestor * and descendant limits (including staged_ancestors thsemselves, entry_size and entry_count). - * param@[in] entry_size Virtual size to include in the limits. - * param@[in] entry_count How many entries to include in the limits. - * param@[in] staged_ancestors Should contain entries in the mempool. - * param@[out] setAncestors Will be populated with all mempool ancestors. + * + * @param[in] entry_size Virtual size to include in the limits. + * @param[in] entry_count How many entries to include in the limits. + * @param[out] setAncestors Will be populated with all mempool ancestors. + * @param[in] staged_ancestors Should contain entries in the mempool. + * @param[in] limits Maximum number and size of ancestors and descendants + * @param[out] errString Populated with error reason if any limits are hit + * + * @return true if no limits were hit and all in-mempool ancestors were calculated, false + * otherwise */ bool CalculateAncestorsAndCheckLimits(size_t entry_size, size_t entry_count, setEntries& setAncestors, CTxMemPoolEntry::Parents &staged_ancestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs); public: @@ -576,8 +581,6 @@ public: const bool m_require_standard; const bool m_full_rbf; - using Limits = kernel::MemPoolLimits; - const Limits m_limits; /** Create a new CTxMemPool. @@ -668,38 +671,41 @@ public: */ void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch); - /** Try to calculate all in-mempool ancestors of entry. - * (these are all calculated including the tx itself) - * limitAncestorCount = max number of ancestors - * limitAncestorSize = max size of ancestors - * limitDescendantCount = max number of descendants any ancestor can have - * limitDescendantSize = max size of descendants any ancestor can have - * errString = populated with error reason if any limits are hit - * fSearchForParents = whether to search a tx's vin for in-mempool parents, or - * look up parents from mapLinks. Must be true for entries not in the mempool + /** + * Try to calculate all in-mempool ancestors of entry. + * (these are all calculated including the tx itself) + * + * @param[in] entry CTxMemPoolEntry of which all in-mempool ancestors are calculated + * @param[out] setAncestors Will be populated with all mempool ancestors. + * @param[in] limits Maximum number and size of ancestors and descendants + * @param[out] errString Populated with error reason if any limits are hit + * @param[in] fSearchForParents Whether to search a tx's vin for in-mempool parents, or look + * up parents from mapLinks. Must be true for entries not in + * the mempool + * + * @return true if no limits were hit and all in-mempool ancestors were calculated, false + * otherwise */ - bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, setEntries& setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string& errString, bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs); + bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, + setEntries& setAncestors, + const Limits& limits, + std::string& errString, + bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Calculate all in-mempool ancestors of a set of transactions not already in the mempool and * check ancestor and descendant limits. Heuristics are used to estimate the ancestor and * descendant count of all entries if the package were to be added to the mempool. The limits * are applied to the union of all package transactions. For example, if the package has 3 - * transactions and limitAncestorCount = 25, the union of all 3 sets of ancestors (including the + * transactions and limits.ancestor_count = 25, the union of all 3 sets of ancestors (including the * transactions themselves) must be <= 22. * @param[in] package Transaction package being evaluated for acceptance * to mempool. The transactions need not be direct * ancestors/descendants of each other. - * @param[in] limitAncestorCount Max number of txns including ancestors. - * @param[in] limitAncestorSize Max virtual size including ancestors. - * @param[in] limitDescendantCount Max number of txns including descendants. - * @param[in] limitDescendantSize Max virtual size including descendants. + * @param[in] limits Maximum number and size of ancestors and descendants * @param[out] errString Populated with error reason if a limit is hit. */ bool CheckPackageLimits(const Package& package, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Populate setDescendants with all in-mempool descendants of hash. @@ -825,7 +831,7 @@ private: * mempool but may have child transactions in the mempool, eg during a * chain reorg. * - * @pre CTxMemPool::m_children is correct for the given tx and all + * @pre CTxMemPoolEntry::m_children is correct for the given tx and all * descendants. * @pre cachedDescendants is an accurate cache where each entry has all * descendants of the corresponding key, including those that should diff --git a/src/uint256.h b/src/uint256.h index 5c3a2f5409..e74b9ff7b1 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_UINT256_H #define BITCOIN_UINT256_H +#include <crypto/common.h> #include <span.h> #include <assert.h> @@ -84,15 +85,7 @@ public: uint64_t GetUint64(int pos) const { - const uint8_t* ptr = m_data + pos * 8; - return ((uint64_t)ptr[0]) | \ - ((uint64_t)ptr[1]) << 8 | \ - ((uint64_t)ptr[2]) << 16 | \ - ((uint64_t)ptr[3]) << 24 | \ - ((uint64_t)ptr[4]) << 32 | \ - ((uint64_t)ptr[5]) << 40 | \ - ((uint64_t)ptr[6]) << 48 | \ - ((uint64_t)ptr[7]) << 56; + return ReadLE64(m_data + pos * 8); } template<typename Stream> diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index ccaf56a271..850d0a1cbc 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -19,6 +19,11 @@ class UniValue { public: enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; + class type_error : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + UniValue() { typ = VNULL; } UniValue(UniValue::VType initialType, const std::string& initialStr = "") { typ = initialType; diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp index 12a434dd0e..4448981d3e 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -210,7 +210,7 @@ const UniValue& UniValue::operator[](size_t index) const void UniValue::checkType(const VType& expected) const { if (typ != expected) { - throw std::runtime_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " + + throw type_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " + std::string{uvTypeName(expected)}}; } } diff --git a/src/util/error.cpp b/src/util/error.cpp index 33a35a6d59..390cb6c11b 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -49,6 +49,11 @@ bilingual_str ResolveErrMsg(const std::string& optname, const std::string& strBi return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind); } +bilingual_str InvalidPortErrMsg(const std::string& optname, const std::string& invalid_value) +{ + return strprintf(_("Invalid port specified in %s: '%s'"), optname, invalid_value); +} + bilingual_str AmountHighWarn(const std::string& optname) { return strprintf(_("%s is set very high!"), optname); diff --git a/src/util/error.h b/src/util/error.h index 0429de651a..27916501f0 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -39,6 +39,8 @@ bilingual_str TransactionErrorString(const TransactionError error); bilingual_str ResolveErrMsg(const std::string& optname, const std::string& strBind); +bilingual_str InvalidPortErrMsg(const std::string& optname, const std::string& strPort); + bilingual_str AmountHighWarn(const std::string& optname); bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue); diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 125dbc7f18..84ac2759fa 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -117,6 +117,34 @@ int Sock::GetSockName(sockaddr* name, socklen_t* name_len) const return getsockname(m_socket, name, name_len); } +bool Sock::SetNonBlocking() const +{ +#ifdef WIN32 + u_long on{1}; + if (ioctlsocket(m_socket, FIONBIO, &on) == SOCKET_ERROR) { + return false; + } +#else + const int flags{fcntl(m_socket, F_GETFL, 0)}; + if (flags == SOCKET_ERROR) { + return false; + } + if (fcntl(m_socket, F_SETFL, flags | O_NONBLOCK) == SOCKET_ERROR) { + return false; + } +#endif + return true; +} + +bool Sock::IsSelectable() const +{ +#if defined(USE_POLL) || defined(WIN32) + return true; +#else + return m_socket < FD_SETSIZE; +#endif +} + bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { // We need a `shared_ptr` owning `this` for `WaitMany()`, but don't want @@ -185,10 +213,10 @@ bool Sock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per SOCKET socket_max{0}; for (const auto& [sock, events] : events_per_sock) { - const auto& s = sock->m_socket; - if (!IsSelectableSocket(s)) { + if (!sock->IsSelectable()) { return false; } + const auto& s = sock->m_socket; if (events.requested & RECV) { FD_SET(s, &recv); } diff --git a/src/util/sock.h b/src/util/sock.h index 38a7dc80d6..7912284904 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -133,6 +133,18 @@ public: */ [[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const; + /** + * Set the non-blocking option on the socket. + * @return true if set successfully + */ + [[nodiscard]] virtual bool SetNonBlocking() const; + + /** + * Check if the underlying socket can be used for `select(2)` (or the `Wait()` method). + * @return true if selectable + */ + [[nodiscard]] virtual bool IsSelectable() const; + using Event = uint8_t; /** diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index b5ac151374..e28ca8e73a 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -97,8 +97,9 @@ std::vector<Byte> ParseHex(std::string_view str) template std::vector<std::byte> ParseHex(std::string_view); template std::vector<uint8_t> ParseHex(std::string_view); -void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut) +bool SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut) { + bool valid = false; 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 bool fHaveColon = colon != in.npos; @@ -109,13 +110,18 @@ void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut) if (ParseUInt16(in.substr(colon + 1), &n)) { in = in.substr(0, colon); portOut = n; + valid = (portOut != 0); } + } else { + valid = true; } if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') { hostOut = in.substr(1, in.size() - 2); } else { hostOut = in; } + + return valid; } std::string EncodeBase64(Span<const unsigned char> input) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 14867b21b2..94bc6cc2f3 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -88,7 +88,16 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); */ std::string EncodeBase32(std::string_view str, bool pad = true); -void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut); +/** + * Splits socket address string into host string and port value. + * Validates port value. + * + * @param[in] in The socket address string to split. + * @param[out] portOut Port-portion of the input, if found and parsable. + * @param[out] hostOut Host-portion of the input, if found. + * @return true if port-portion is absent or within its allowed range, otherwise false + */ +bool SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut); // LocaleIndependentAtoi is provided for backwards compatibility reasons. // diff --git a/src/util/system.cpp b/src/util/system.cpp index 1953a9f836..d7a0793ea8 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -5,19 +5,6 @@ #include <util/system.h> -#ifdef ENABLE_EXTERNAL_SIGNER -#if defined(__GNUC__) -// Boost 1.78 requires the following workaround. -// See: https://github.com/boostorg/process/issues/235 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnarrowing" -#endif -#include <boost/process.hpp> -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif -#endif // ENABLE_EXTERNAL_SIGNER - #include <chainparamsbase.h> #include <fs.h> #include <sync.h> @@ -831,7 +818,7 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message std::string("\n\n"); } -static std::string FormatException(const std::exception* pex, const char* pszThread) +static std::string FormatException(const std::exception* pex, std::string_view thread_name) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; @@ -841,15 +828,15 @@ static std::string FormatException(const std::exception* pex, const char* pszThr #endif if (pex) return strprintf( - "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); + "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, thread_name); else return strprintf( - "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); + "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, thread_name); } -void PrintExceptionContinue(const std::exception* pex, const char* pszThread) +void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name) { - std::string message = FormatException(pex, pszThread); + std::string message = FormatException(pex, thread_name); LogPrintf("\n\n************************\n%s\n", message); tfm::format(std::cerr, "\n\n************************\n%s\n", message); } @@ -935,6 +922,20 @@ static bool GetConfigOptions(std::istream& stream, const std::string& filepath, return true; } +bool IsConfSupported(KeyInfo& key, std::string& error) { + if (key.name == "conf") { + error = "conf cannot be set in the configuration file; use includeconf= if you want to include additional config files"; + return false; + } + if (key.name == "reindex") { + // reindex can be set in a config file but it is strongly discouraged as this will cause the node to reindex on + // every restart. Allow the config but throw a warning + LogPrintf("Warning: reindex=1 is set in the configuration file, which will significantly slow down startup. Consider removing or commenting out this option for better performance, unless there is currently a condition which makes rebuilding the indexes necessary\n"); + return true; + } + return true; +} + bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys) { LOCK(cs_args); @@ -945,6 +946,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file for (const std::pair<std::string, std::string>& option : options) { KeyInfo key = InterpretKey(option.first); std::optional<unsigned int> flags = GetArgFlags('-' + key.name); + if (!IsConfSupported(key, error)) return false; if (flags) { std::optional<util::SettingsValue> value = InterpretValue(key, &option.second, *flags, error); if (!value) { @@ -1332,44 +1334,6 @@ void runCommand(const std::string& strCommand) } #endif -UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) -{ -#ifdef ENABLE_EXTERNAL_SIGNER - namespace bp = boost::process; - - UniValue result_json; - bp::opstream stdin_stream; - bp::ipstream stdout_stream; - bp::ipstream stderr_stream; - - if (str_command.empty()) return UniValue::VNULL; - - bp::child c( - str_command, - bp::std_out > stdout_stream, - bp::std_err > stderr_stream, - bp::std_in < stdin_stream - ); - if (!str_std_in.empty()) { - stdin_stream << str_std_in << std::endl; - } - stdin_stream.pipe().close(); - - std::string result; - std::string error; - std::getline(stdout_stream, result); - std::getline(stderr_stream, error); - - c.wait(); - const int n_error = c.exit_code(); - if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error)); - if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); - - return result_json; -#else - throw std::runtime_error("Compiled without external signing support (required for external signing)."); -#endif // ENABLE_EXTERNAL_SIGNER -} void SetupEnvironment() { diff --git a/src/util/system.h b/src/util/system.h index 756e6642f2..29629e547e 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -51,7 +51,7 @@ bool error(const char* fmt, const Args&... args) return false; } -void PrintExceptionContinue(const std::exception *pex, const char* pszThread); +void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name); /** * Ensure file contents are fully committed to disk, using a platform-specific @@ -107,14 +107,6 @@ std::string ShellEscape(const std::string& arg); #if HAVE_SYSTEM void runCommand(const std::string& strCommand); #endif -/** - * Execute a command which returns JSON, and parse the result. - * - * @param str_command The command to execute, including any arguments - * @param str_std_in string to pass to stdin - * @return parsed JSON - */ -UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); /** * Most paths passed as configuration arguments are treated as relative to diff --git a/src/util/thread.cpp b/src/util/thread.cpp index f9f427ba20..ae98abdb3d 100644 --- a/src/util/thread.cpp +++ b/src/util/thread.cpp @@ -10,10 +10,12 @@ #include <exception> #include <functional> +#include <string> +#include <utility> -void util::TraceThread(const char* thread_name, std::function<void()> thread_func) +void util::TraceThread(std::string_view thread_name, std::function<void()> thread_func) { - util::ThreadRename(thread_name); + util::ThreadRename(std::string{thread_name}); try { LogPrintf("%s thread start\n", thread_name); thread_func(); diff --git a/src/util/thread.h b/src/util/thread.h index ca2eccc0c3..b80bf046a0 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -6,12 +6,13 @@ #define BITCOIN_UTIL_THREAD_H #include <functional> +#include <string> namespace util { /** * A wrapper for do-something-once thread functions. */ -void TraceThread(const char* thread_name, std::function<void()> thread_func); +void TraceThread(std::string_view thread_name, std::function<void()> thread_func); } // namespace util diff --git a/src/util/time.cpp b/src/util/time.cpp index f6d37347f8..0b20849079 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -12,8 +12,6 @@ #include <util/time.h> #include <util/check.h> -#include <boost/date_time/posix_time/posix_time.hpp> - #include <atomic> #include <chrono> #include <ctime> @@ -142,20 +140,6 @@ std::string FormatISO8601Date(int64_t nTime) { return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } -int64_t ParseISO8601DateTime(const std::string& str) -{ - static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); - static const std::locale loc(std::locale::classic(), - new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); - std::istringstream iss(str); - iss.imbue(loc); - boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); - iss >> ptime; - if (ptime.is_not_a_date_time() || epoch > ptime) - return 0; - return (ptime - epoch).total_seconds(); -} - struct timeval MillisToTimeval(int64_t nTimeout) { struct timeval timeout; diff --git a/src/util/time.h b/src/util/time.h index 4f9bde5d56..10a581a44c 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -57,6 +57,7 @@ constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.cou using HoursDouble = std::chrono::duration<double, std::chrono::hours::period>; using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>; +using MillisecondsDouble = std::chrono::duration<double, std::chrono::milliseconds::period>; /** * DEPRECATED @@ -109,7 +110,6 @@ T GetTime() */ std::string FormatISO8601DateTime(int64_t nTime); std::string FormatISO8601Date(int64_t nTime); -int64_t ParseISO8601DateTime(const std::string& str); /** * Convert milliseconds to a struct timeval for e.g. select. diff --git a/src/validation.cpp b/src/validation.cpp index 3e17d4cd87..debdc2ae74 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -82,9 +82,6 @@ using node::SnapshotMetadata; using node::UndoReadFromDisk; using node::UnlinkPrunedFiles; -#define MICRO 0.000001 -#define MILLI 0.001 - /** Maximum kilobytes for transactions to store for processing during reorg */ static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000; /** Time to wait between writing blocks/block index to disk. */ @@ -123,15 +120,8 @@ RecursiveMutex cs_main; GlobalMutex g_best_block_mutex; std::condition_variable g_best_block_cv; uint256 g_best_block; -bool g_parallel_script_checks{false}; -bool fCheckBlockIndex = false; -bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; -int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; - -uint256 hashAssumeValid; -arith_uint256 nMinimumChainWork; -const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const +const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const { AssertLockHeld(cs_main); @@ -273,7 +263,7 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache) coins_cache.Uncache(removed); } -static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool IsCurrentForFeeEstimation(Chainstate& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (active_chainstate.IsInitialBlockDownload()) @@ -286,7 +276,7 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_ return true; } -void CChainState::MaybeUpdateMempoolForReorg( +void Chainstate::MaybeUpdateMempoolForReorg( DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) { @@ -424,11 +414,13 @@ namespace { class MemPoolAccept { public: - explicit MemPoolAccept(CTxMemPool& mempool, CChainState& active_chainstate) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&active_chainstate.CoinsTip(), m_pool), m_active_chainstate(active_chainstate), - m_limit_ancestors(m_pool.m_limits.ancestor_count), - m_limit_ancestor_size(m_pool.m_limits.ancestor_size_vbytes), - m_limit_descendants(m_pool.m_limits.descendant_count), - m_limit_descendant_size(m_pool.m_limits.descendant_size_vbytes) { + explicit MemPoolAccept(CTxMemPool& mempool, Chainstate& active_chainstate) : + m_pool(mempool), + m_view(&m_dummy), + m_viewmempool(&active_chainstate.CoinsTip(), m_pool), + m_active_chainstate(active_chainstate), + m_limits{m_pool.m_limits} + { } // We put the arguments we're handed into a struct, so we can pass them @@ -658,15 +650,9 @@ private: CCoinsViewMemPool m_viewmempool; CCoinsView m_dummy; - CChainState& m_active_chainstate; + Chainstate& m_active_chainstate; - // The package limits in effect at the time of invocation. - const size_t m_limit_ancestors; - const size_t m_limit_ancestor_size; - // These may be modified while evaluating a transaction (eg to account for - // in-mempool conflicts; see below). - size_t m_limit_descendants; - size_t m_limit_descendant_size; + CTxMemPool::Limits m_limits; /** Whether the transaction(s) would replace any mempool transactions. If so, RBF rules apply. */ bool m_rbf{false}; @@ -879,12 +865,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) assert(ws.m_iters_conflicting.size() == 1); CTxMemPool::txiter conflict = *ws.m_iters_conflicting.begin(); - m_limit_descendants += 1; - m_limit_descendant_size += conflict->GetSizeWithDescendants(); + m_limits.descendant_count += 1; + m_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants(); } std::string errString; - if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) { + if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limits, errString)) { ws.m_ancestors.clear(); // If CalculateMemPoolAncestors fails second time, we want the original error string. std::string dummy_err_string; @@ -899,8 +885,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // to be secure by simply only having two immediately-spendable // outputs - one for each counterparty. For more info on the uses for // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html + CTxMemPool::Limits cpfp_carve_out_limits{ + .ancestor_count = 2, + .ancestor_size_vbytes = m_limits.ancestor_size_vbytes, + .descendant_count = m_limits.descendant_count + 1, + .descendant_size_vbytes = m_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT, + }; if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || - !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) { + !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, + cpfp_carve_out_limits, + dummy_err_string)) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString); } } @@ -976,8 +970,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn { return !m_pool.exists(GenTxid::Txid(tx->GetHash()));})); std::string err_string; - if (!m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, - m_limit_descendant_size, err_string)) { + if (!m_pool.CheckPackageLimits(txns, m_limits, err_string)) { // This is a package-wide error, separate from an individual transaction error. return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string); } @@ -1121,9 +1114,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& // Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the // last calculation done in PreChecks, since package ancestors have already been submitted. std::string unused_err_string; - if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limit_ancestors, - m_limit_ancestor_size, m_limit_descendants, - m_limit_descendant_size, unused_err_string)) { + if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limits, 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); @@ -1411,12 +1402,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, } // anon namespace -MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx, +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, int64_t accept_time, bool bypass_limits, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); - const CChainParams& chainparams{active_chainstate.m_params}; + const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()}; assert(active_chainstate.GetMempool() != nullptr); CTxMemPool& pool{*active_chainstate.GetMempool()}; @@ -1438,7 +1429,7 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr return result; } -PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool, +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, const Package& package, bool test_accept) { AssertLockHeld(cs_main); @@ -1446,7 +1437,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); std::vector<COutPoint> coins_to_uncache; - const CChainParams& chainparams = active_chainstate.m_params; + const CChainParams& chainparams = active_chainstate.m_chainman.GetParams(); const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (test_accept) { @@ -1497,32 +1488,31 @@ void CoinsViews::InitCache() m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview); } -CChainState::CChainState( +Chainstate::Chainstate( CTxMemPool* mempool, BlockManager& blockman, ChainstateManager& chainman, std::optional<uint256> from_snapshot_blockhash) : m_mempool(mempool), m_blockman(blockman), - m_params(chainman.GetParams()), m_chainman(chainman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} -void CChainState::InitCoinsDB( +void Chainstate::InitCoinsDB( size_t cache_size_bytes, bool in_memory, bool should_wipe, fs::path leveldb_name) { if (m_from_snapshot_blockhash) { - leveldb_name += "_" + m_from_snapshot_blockhash->ToString(); + leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX; } m_coins_views = std::make_unique<CoinsViews>( leveldb_name, cache_size_bytes, in_memory, should_wipe); } -void CChainState::InitCoinsCache(size_t cache_size_bytes) +void Chainstate::InitCoinsCache(size_t cache_size_bytes) { AssertLockHeld(::cs_main); assert(m_coins_views != nullptr); @@ -1532,10 +1522,10 @@ void CChainState::InitCoinsCache(size_t cache_size_bytes) // Note that though this is marked const, we may end up modifying `m_cached_finished_ibd`, which // is a performance-related implementation detail. This function must be marked -// `const` so that `CValidationInterface` clients (which are given a `const CChainState*`) +// `const` so that `CValidationInterface` clients (which are given a `const Chainstate*`) // can call it. // -bool CChainState::IsInitialBlockDownload() const +bool Chainstate::IsInitialBlockDownload() const { // Optimization: pre-test latch before taking the lock. if (m_cached_finished_ibd.load(std::memory_order_relaxed)) @@ -1548,10 +1538,12 @@ bool CChainState::IsInitialBlockDownload() const return true; if (m_chain.Tip() == nullptr) return true; - if (m_chain.Tip()->nChainWork < nMinimumChainWork) + if (m_chain.Tip()->nChainWork < m_chainman.MinimumChainWork()) { return true; - if (m_chain.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge)) + } + if (m_chain.Tip()->Time() < NodeClock::now() - m_chainman.m_options.max_tip_age) { return true; + } LogPrintf("Leaving InitialBlockDownload (latching to false)\n"); m_cached_finished_ibd.store(true, std::memory_order_relaxed); return false; @@ -1577,7 +1569,7 @@ static void AlertNotify(const std::string& strMessage) #endif } -void CChainState::CheckForkWarningConditions() +void Chainstate::CheckForkWarningConditions() { AssertLockHeld(cs_main); @@ -1596,7 +1588,7 @@ void CChainState::CheckForkWarningConditions() } // Called both upon regular invalid block discovery *and* InvalidateBlock -void CChainState::InvalidChainFound(CBlockIndex* pindexNew) +void Chainstate::InvalidChainFound(CBlockIndex* pindexNew) { AssertLockHeld(cs_main); if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) { @@ -1619,7 +1611,7 @@ void CChainState::InvalidChainFound(CBlockIndex* pindexNew) // Same as InvalidChainFound, above, except not called directly from InvalidateBlock, // which does its own setBlockIndexCandidates management. -void CChainState::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) +void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) { AssertLockHeld(cs_main); if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { @@ -1824,7 +1816,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) /** Undo the effects of this block (with given index) on the UTXO set represented by coins. * When FAILED is returned, view is left in an indeterminate state. */ -DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) +DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) { AssertLockHeld(::cs_main); bool fClean = true; @@ -1840,11 +1832,21 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI return DISCONNECT_FAILED; } + // Ignore blocks that contain transactions which are 'overwritten' by later transactions, + // unless those are already completely spent. + // See https://github.com/bitcoin/bitcoin/issues/22596 for additional information. + // Note: the blocks specified here are different than the ones used in ConnectBlock because DisconnectBlock + // unwinds the blocks in reverse. As a result, the inconsistency is not discovered until the earlier + // blocks with the duplicate coinbase transactions are disconnected. + bool fEnforceBIP30 = !((pindex->nHeight==91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || + (pindex->nHeight==91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))); + // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = *(block.vtx[i]); uint256 hash = tx.GetHash(); bool is_coinbase = tx.IsCoinBase(); + bool is_bip30_exception = (is_coinbase && !fEnforceBIP30); // Check that all outputs are available and match the outputs in the block itself // exactly. @@ -1854,7 +1856,9 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI Coin coin; bool is_spent = view.SpendCoin(out, &coin); if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) { - fClean = false; // transaction output mismatch + if (!is_bip30_exception) { + fClean = false; // transaction output mismatch + } } } } @@ -1965,19 +1969,19 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch } -static int64_t nTimeCheck = 0; -static int64_t nTimeForks = 0; -static int64_t nTimeConnect = 0; -static int64_t nTimeVerify = 0; -static int64_t nTimeUndo = 0; -static int64_t nTimeIndex = 0; -static int64_t nTimeTotal = 0; -static int64_t nBlocksTotal = 0; +static SteadyClock::duration time_check{}; +static SteadyClock::duration time_forks{}; +static SteadyClock::duration time_connect{}; +static SteadyClock::duration time_verify{}; +static SteadyClock::duration time_undo{}; +static SteadyClock::duration time_index{}; +static SteadyClock::duration time_total{}; +static int64_t num_blocks_total = 0; /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ -bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, +bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck) { AssertLockHeld(cs_main); @@ -1985,8 +1989,10 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, uint256 block_hash{block.GetHash()}; assert(*pindex->phashBlock == block_hash); + const bool parallel_script_checks{scriptcheckqueue.HasThreads()}; - int64_t nTimeStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; + const CChainParams& params{m_chainman.GetParams()}; // Check it again in case a previous version let a bad block in // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or @@ -2001,7 +2007,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // is enforced in ContextualCheckBlockHeader(); we wouldn't want to // re-enforce that rule here (at least until we make it impossible for // m_adjusted_time_callback() to go backward). - if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) { + if (!CheckBlock(block, state, params.GetConsensus(), !fJustCheck, !fJustCheck)) { if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware @@ -2015,28 +2021,28 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash(); assert(hashPrevBlock == view.GetBestBlock()); - nBlocksTotal++; + num_blocks_total++; // Special case for the genesis block, skipping connection of its transactions // (its coinbase is unspendable) - if (block_hash == m_params.GetConsensus().hashGenesisBlock) { + if (block_hash == params.GetConsensus().hashGenesisBlock) { if (!fJustCheck) view.SetBestBlock(pindex->GetBlockHash()); return true; } bool fScriptChecks = true; - if (!hashAssumeValid.IsNull()) { + if (!m_chainman.AssumedValidBlock().IsNull()) { // We've been configured with the hash of a block which has been externally verified to have a valid history. // A suitable default value is included with the software and updated from time to time. Because validity // relative to a piece of software is an objective fact these defaults can be easily reviewed. // This setting doesn't force the selection of any particular chain but makes validating some faster by // effectively caching the result of part of the verification. - BlockMap::const_iterator it = m_blockman.m_block_index.find(hashAssumeValid); + BlockMap::const_iterator it{m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())}; if (it != m_blockman.m_block_index.end()) { if (it->second.GetAncestor(pindex->nHeight) == pindex && m_chainman.m_best_header->GetAncestor(pindex->nHeight) == pindex && - m_chainman.m_best_header->nChainWork >= nMinimumChainWork) { + m_chainman.m_best_header->nChainWork >= m_chainman.MinimumChainWork()) { // This block is a member of the assumed verified chain and an ancestor of the best header. // Script verification is skipped when connecting blocks under the // assumevalid block. Assuming the assumevalid block is valid this @@ -2049,15 +2055,19 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // it hard to hide the implication of the demand. This also avoids having release candidates // that are hardly doing any signature verification at all in testing without having to // artificially set the default assumed verified block further back. - // The test against nMinimumChainWork prevents the skipping when denied access to any chain at + // The test against the minimum chain work prevents the skipping when denied access to any chain at // least as good as the expected chain. - fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); + fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); } } } - int64_t nTime1 = GetTimeMicros(); nTimeCheck += nTime1 - nTimeStart; - LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO, nTimeCheck * MILLI / nBlocksTotal); + const auto time_1{SteadyClock::now()}; + time_check += time_1 - time_start; + LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_1 - time_start), + Ticks<SecondsDouble>(time_check), + Ticks<MillisecondsDouble>(time_check) / num_blocks_total); // Do not allow blocks that contain transactions which 'overwrite' older transactions, // unless those are already completely spent. @@ -2128,9 +2138,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // post BIP34 before approximately height 486,000,000. After block // 1,983,702 testnet3 starts doing unnecessary BIP30 checking again. assert(pindex->pprev); - CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(m_params.GetConsensus().BIP34Height); + CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(params.GetConsensus().BIP34Height); //Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond. - fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == m_params.GetConsensus().BIP34Hash)); + fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == params.GetConsensus().BIP34Hash)); // TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a // consensus change that ensures coinbases at those heights cannot @@ -2155,8 +2165,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Get the script flags for this block unsigned int flags{GetBlockScriptFlags(*pindex, m_chainman)}; - int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1; - LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal); + const auto time_2{SteadyClock::now()}; + time_forks += time_2 - time_1; + LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<SecondsDouble>(time_forks), + Ticks<MillisecondsDouble>(time_forks) / num_blocks_total); CBlockUndo blockundo; @@ -2165,7 +2179,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // in multiple threads). Preallocate the vector size so a new allocation // doesn't invalidate pointers into the vector, and keep txsdata in scope // for as long as `control`. - CCheckQueueControl<CScriptCheck> control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr); + CCheckQueueControl<CScriptCheck> control(fScriptChecks && parallel_script_checks ? &scriptcheckqueue : nullptr); std::vector<PrecomputedTransactionData> txsdata(block.vtx.size()); std::vector<int> prevheights; @@ -2224,7 +2238,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ TxValidationState tx_state; - if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], g_parallel_script_checks ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr)) { // Any transaction validation failure in ConnectBlock is a block consensus failure state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); @@ -2240,10 +2254,15 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, } UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); } - int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; - LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); + const auto time_3{SteadyClock::now()}; + time_connect += time_3 - time_2; + LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), + Ticks<MillisecondsDouble>(time_3 - time_2), Ticks<MillisecondsDouble>(time_3 - time_2) / block.vtx.size(), + nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_3 - time_2) / (nInputs - 1), + Ticks<SecondsDouble>(time_connect), + Ticks<MillisecondsDouble>(time_connect) / num_blocks_total); - CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, m_params.GetConsensus()); + CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, params.GetConsensus()); if (block.vtx[0]->GetValueOut() > blockReward) { LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount"); @@ -2253,18 +2272,27 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, LogPrintf("ERROR: %s: CheckQueue failed\n", __func__); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed"); } - int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; - LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); + const auto time_4{SteadyClock::now()}; + time_verify += time_4 - time_2; + LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, + Ticks<MillisecondsDouble>(time_4 - time_2), + nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_4 - time_2) / (nInputs - 1), + Ticks<SecondsDouble>(time_verify), + Ticks<MillisecondsDouble>(time_verify) / num_blocks_total); if (fJustCheck) return true; - if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, m_params)) { + if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, params)) { return false; } - int64_t nTime5 = GetTimeMicros(); nTimeUndo += nTime5 - nTime4; - LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeUndo * MICRO, nTimeUndo * MILLI / nBlocksTotal); + const auto time_5{SteadyClock::now()}; + time_undo += time_5 - time_4; + LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_5 - time_4), + Ticks<SecondsDouble>(time_undo), + Ticks<MillisecondsDouble>(time_undo) / num_blocks_total); if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) { pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); @@ -2274,8 +2302,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); - int64_t nTime6 = GetTimeMicros(); nTimeIndex += nTime6 - nTime5; - LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime6 - nTime5), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal); + const auto time_6{SteadyClock::now()}; + time_index += time_6 - time_5; + LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_5), + Ticks<SecondsDouble>(time_index), + Ticks<MillisecondsDouble>(time_index) / num_blocks_total); TRACE6(validation, block_connected, block_hash.data(), @@ -2283,13 +2315,13 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, block.vtx.size(), nInputs, nSigOpsCost, - nTime5 - nTimeStart // in microseconds (µs) + time_5 - time_start // in microseconds (µs) ); return true; } -CoinsCacheSizeState CChainState::GetCoinsCacheSizeState() +CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState() { AssertLockHeld(::cs_main); return this->GetCoinsCacheSizeState( @@ -2297,7 +2329,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState() m_mempool ? m_mempool->m_max_size_bytes : 0); } -CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( +CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState( size_t max_coins_cache_size_bytes, size_t max_mempool_size_bytes) { @@ -2321,7 +2353,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( return CoinsCacheSizeState::OK; } -bool CChainState::FlushStateToDisk( +bool Chainstate::FlushStateToDisk( BlockValidationState &state, FlushStateMode mode, int nManualPruneHeight) @@ -2370,7 +2402,7 @@ bool CChainState::FlushStateToDisk( } else { LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH); - m_blockman.FindFilesToPrune(setFilesToPrune, m_params.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload()); + m_blockman.FindFilesToPrune(setFilesToPrune, m_chainman.GetParams().PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload()); m_blockman.m_check_for_pruning = false; } if (!setFilesToPrune.empty()) { @@ -2464,7 +2496,7 @@ bool CChainState::FlushStateToDisk( return true; } -void CChainState::ForceFlushStateToDisk() +void Chainstate::ForceFlushStateToDisk() { BlockValidationState state; if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) { @@ -2472,7 +2504,7 @@ void CChainState::ForceFlushStateToDisk() } } -void CChainState::PruneAndFlush() +void Chainstate::PruneAndFlush() { BlockValidationState state; m_blockman.m_check_for_pruning = true; @@ -2519,18 +2551,20 @@ static void UpdateTipLog( !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : ""); } -void CChainState::UpdateTip(const CBlockIndex* pindexNew) +void Chainstate::UpdateTip(const CBlockIndex* pindexNew) { AssertLockHeld(::cs_main); const auto& coins_tip = this->CoinsTip(); + const CChainParams& params{m_chainman.GetParams()}; + // The remainder of the function isn't relevant if we are not acting on // the active chainstate, so return if need be. if (this != &m_chainman.ActiveChainstate()) { // Only log every so often so that we don't bury log messages at the tip. constexpr int BACKGROUND_LOG_INTERVAL = 2000; if (pindexNew->nHeight % BACKGROUND_LOG_INTERVAL == 0) { - UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "[background validation] ", ""); + UpdateTipLog(coins_tip, pindexNew, params, __func__, "[background validation] ", ""); } return; } @@ -2551,7 +2585,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(m_chainman, bit); - ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache.at(bit)); + ThresholdState state = checker.GetStateFor(pindex, 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) { @@ -2562,7 +2596,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) } } } - UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "", warning_messages.original); + UpdateTipLog(coins_tip, pindexNew, params, __func__, "", warning_messages.original); } /** Disconnect m_chain's tip. @@ -2575,7 +2609,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) * disconnectpool (note that the caller is responsible for mempool consistency * in any case). */ -bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) +bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); @@ -2586,11 +2620,11 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr // Read block from disk. std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); CBlock& block = *pblock; - if (!ReadBlockFromDisk(block, pindexDelete, m_params.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindexDelete, m_chainman.GetConsensus())) { return error("DisconnectTip(): Failed to read block"); } // Apply the block atomically to the chain state. - int64_t nStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; { CCoinsViewCache view(&CoinsTip()); assert(view.GetBestBlock() == pindexDelete->GetBlockHash()); @@ -2599,7 +2633,8 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr bool flushed = view.Flush(); assert(flushed); } - LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI); + LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", + Ticks<MillisecondsDouble>(SteadyClock::now() - time_start)); { // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg @@ -2639,11 +2674,11 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr return true; } -static int64_t nTimeReadFromDiskTotal = 0; -static int64_t nTimeConnectTotal = 0; -static int64_t nTimeFlush = 0; -static int64_t nTimeChainState = 0; -static int64_t nTimePostConnect = 0; +static SteadyClock::duration time_read_from_disk_total{}; +static SteadyClock::duration time_connect_total{}; +static SteadyClock::duration time_flush{}; +static SteadyClock::duration time_chainstate{}; +static SteadyClock::duration time_post_connect{}; struct PerBlockConnectTrace { CBlockIndex* pindex = nullptr; @@ -2691,18 +2726,18 @@ public: * * The block is added to connectTrace if connection succeeds. */ -bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) +bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); assert(pindexNew->pprev == m_chain.Tip()); // Read block from disk. - int64_t nTime1 = GetTimeMicros(); + const auto time_1{SteadyClock::now()}; std::shared_ptr<const CBlock> pthisBlock; if (!pblock) { std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>(); - if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_params.GetConsensus())) { + if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_chainman.GetConsensus())) { return AbortNode(state, "Failed to read block"); } pthisBlock = pblockNew; @@ -2712,9 +2747,13 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew } const CBlock& blockConnecting = *pthisBlock; // Apply the block atomically to the chain state. - int64_t nTime2 = GetTimeMicros(); nTimeReadFromDiskTotal += nTime2 - nTime1; - int64_t nTime3; - LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDiskTotal * MICRO, nTimeReadFromDiskTotal * MILLI / nBlocksTotal); + const auto time_2{SteadyClock::now()}; + time_read_from_disk_total += time_2 - time_1; + SteadyClock::time_point time_3; + LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<SecondsDouble>(time_read_from_disk_total), + Ticks<MillisecondsDouble>(time_read_from_disk_total) / num_blocks_total); { CCoinsViewCache view(&CoinsTip()); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view); @@ -2724,20 +2763,32 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew InvalidBlockFound(pindexNew, state); return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), state.ToString()); } - nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; - assert(nBlocksTotal > 0); - LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal); + time_3 = SteadyClock::now(); + time_connect_total += time_3 - time_2; + assert(num_blocks_total > 0); + LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_3 - time_2), + Ticks<SecondsDouble>(time_connect_total), + Ticks<MillisecondsDouble>(time_connect_total) / num_blocks_total); bool flushed = view.Flush(); assert(flushed); } - int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; - LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, nTimeFlush * MILLI / nBlocksTotal); + const auto time_4{SteadyClock::now()}; + time_flush += time_4 - time_3; + LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_4 - time_3), + Ticks<SecondsDouble>(time_flush), + Ticks<MillisecondsDouble>(time_flush) / num_blocks_total); // Write the chain state to disk, if necessary. if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; } - int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4; - LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal); + const auto time_5{SteadyClock::now()}; + time_chainstate += time_5 - time_4; + LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_5 - time_4), + Ticks<SecondsDouble>(time_chainstate), + Ticks<MillisecondsDouble>(time_chainstate) / num_blocks_total); // Remove conflicting transactions from the mempool.; if (m_mempool) { m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight); @@ -2747,9 +2798,17 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew m_chain.SetTip(*pindexNew); UpdateTip(pindexNew); - int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; - LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal); - LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime1) * MILLI, nTimeTotal * MICRO, nTimeTotal * MILLI / nBlocksTotal); + const auto time_6{SteadyClock::now()}; + time_post_connect += time_6 - time_5; + time_total += time_6 - time_1; + LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_5), + Ticks<SecondsDouble>(time_post_connect), + Ticks<MillisecondsDouble>(time_post_connect) / num_blocks_total); + LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_1), + Ticks<SecondsDouble>(time_total), + Ticks<MillisecondsDouble>(time_total) / num_blocks_total); connectTrace.BlockConnected(pindexNew, std::move(pthisBlock)); return true; @@ -2759,7 +2818,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew * Return the tip of the chain with the most work in it, that isn't * known to be invalid (it's however far from certain to be valid). */ -CBlockIndex* CChainState::FindMostWorkChain() +CBlockIndex* Chainstate::FindMostWorkChain() { AssertLockHeld(::cs_main); do { @@ -2818,7 +2877,7 @@ CBlockIndex* CChainState::FindMostWorkChain() } /** Delete all entries in setBlockIndexCandidates that are worse than the current tip. */ -void CChainState::PruneBlockIndexCandidates() { +void Chainstate::PruneBlockIndexCandidates() { // Note that we can't delete the current block itself, as we may need to return to it later in case a // reorganization to a better block fails. std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexCandidates.begin(); @@ -2835,7 +2894,7 @@ void CChainState::PruneBlockIndexCandidates() { * * @returns true unless a system error occurred */ -bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) +bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); @@ -2927,7 +2986,7 @@ static SynchronizationState GetSynchronizationState(bool init) return SynchronizationState::INIT_DOWNLOAD; } -static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { +static bool NotifyHeaderTip(Chainstate& chainstate) LOCKS_EXCLUDED(cs_main) { bool fNotify = false; bool fInitialBlockDownload = false; static CBlockIndex* pindexHeaderOld = nullptr; @@ -2957,7 +3016,7 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { } } -bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock) +bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock) { AssertLockNotHeld(m_chainstate_mutex); @@ -3059,7 +3118,7 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr return true; } -bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) +bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) { AssertLockNotHeld(m_chainstate_mutex); AssertLockNotHeld(::cs_main); @@ -3090,7 +3149,7 @@ bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex return ActivateBestChain(state, std::shared_ptr<const CBlock>()); } -bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) +bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) { AssertLockNotHeld(m_chainstate_mutex); AssertLockNotHeld(::cs_main); @@ -3233,7 +3292,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind return true; } -void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { +void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) { AssertLockHeld(cs_main); int nHeight = pindex->nHeight; @@ -3266,7 +3325,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { } /** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */ -void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) +void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) { AssertLockHeld(cs_main); pindexNew->nTx = block.vtx.size(); @@ -3469,7 +3528,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); // Check against checkpoints - if (fCheckpointsEnabled) { + if (chainman.m_options.checkpoints_enabled) { // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). @@ -3737,7 +3796,7 @@ void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) +bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) { const CBlock& block = *pblock; @@ -3783,10 +3842,12 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // If our tip is behind, a peer could try to send us // low-work blocks on a fake chain that we would never // request; don't process these. - if (pindex->nChainWork < nMinimumChainWork) return true; + if (pindex->nChainWork < m_chainman.MinimumChainWork()) return true; } - if (!CheckBlock(block, state, m_params.GetConsensus()) || + const CChainParams& params{m_chainman.GetParams()}; + + if (!CheckBlock(block, state, params.GetConsensus()) || !ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; @@ -3803,7 +3864,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // Write block to history file if (fNewBlock) *fNewBlock = true; try { - FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, pindex->nHeight, m_chain, m_params, dbp)}; + FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, pindex->nHeight, m_chain, params, dbp)}; if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; @@ -3862,7 +3923,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& tx, bool test_accept) { AssertLockHeld(cs_main); - CChainState& active_chainstate = ActiveChainstate(); + Chainstate& active_chainstate = ActiveChainstate(); if (!active_chainstate.GetMempool()) { TxValidationState state; state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool"); @@ -3875,7 +3936,7 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, - CChainState& chainstate, + Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, const std::function<NodeClock::time_point()>& adjusted_time_callback, @@ -3907,7 +3968,7 @@ bool TestBlockValidity(BlockValidationState& state, } /* This function is called from the RPC code for pruneblockchain */ -void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight) +void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight) { BlockValidationState state; if (!active_chainstate.FlushStateToDisk( @@ -3916,14 +3977,14 @@ void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeigh } } -void CChainState::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function) +void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function) { if (!m_mempool) return; ::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function); m_mempool->SetLoadTried(!ShutdownRequested()); } -bool CChainState::LoadChainTip() +bool Chainstate::LoadChainTip() { AssertLockHeld(cs_main); const CCoinsViewCache& coins_cache = CoinsTip(); @@ -3947,7 +4008,7 @@ bool CChainState::LoadChainTip() tip->GetBlockHash().ToString(), m_chain.Height(), FormatISO8601DateTime(tip->GetBlockTime()), - GuessVerificationProgress(m_params.TxData(), tip)); + GuessVerificationProgress(m_chainman.GetParams().TxData(), tip)); return true; } @@ -3962,7 +4023,7 @@ CVerifyDB::~CVerifyDB() } bool CVerifyDB::VerifyDB( - CChainState& chainstate, + Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, int nCheckDepth) @@ -4078,12 +4139,12 @@ bool CVerifyDB::VerifyDB( } /** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ -bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) +bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) { AssertLockHeld(cs_main); // TODO: merge with ConnectBlock CBlock block; - if (!ReadBlockFromDisk(block, pindex, m_params.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindex, m_chainman.GetConsensus())) { return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } @@ -4099,7 +4160,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i return true; } -bool CChainState::ReplayBlocks() +bool Chainstate::ReplayBlocks() { LOCK(cs_main); @@ -4135,7 +4196,7 @@ bool CChainState::ReplayBlocks() while (pindexOld != pindexFork) { if (pindexOld->nHeight > 0) { // Never disconnect the genesis block. CBlock block; - if (!ReadBlockFromDisk(block, pindexOld, m_params.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindexOld, m_chainman.GetConsensus())) { return error("RollbackBlock(): ReadBlockFromDisk() failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString()); } LogPrintf("Rolling back %s (%i)\n", pindexOld->GetBlockHash().ToString(), pindexOld->nHeight); @@ -4167,7 +4228,7 @@ bool CChainState::ReplayBlocks() return true; } -bool CChainState::NeedsRedownload() const +bool Chainstate::NeedsRedownload() const { AssertLockHeld(cs_main); @@ -4185,7 +4246,7 @@ bool CChainState::NeedsRedownload() const return false; } -void CChainState::UnloadBlockIndex() +void Chainstate::UnloadBlockIndex() { AssertLockHeld(::cs_main); nBlockSequenceId = 1; @@ -4254,7 +4315,7 @@ bool ChainstateManager::LoadBlockIndex() // detecting "holistically" whether the block index under consideration // relied on an assumed-valid ancestor, but this proved to be too slow to // be practical. - for (CChainState* chainstate : GetAll()) { + for (Chainstate* chainstate : GetAll()) { if (chainstate->reliesOnAssumedValid() || pindex->nHeight < first_assumed_valid_height) { chainstate->setBlockIndexCandidates.insert(pindex); @@ -4283,20 +4344,22 @@ bool ChainstateManager::LoadBlockIndex() return true; } -bool CChainState::LoadGenesisBlock() +bool Chainstate::LoadGenesisBlock() { LOCK(cs_main); + const CChainParams& params{m_chainman.GetParams()}; + // Check whether we're already initialized by checking for genesis in // m_blockman.m_block_index. Note that we can't use m_chain here, since it is // set based on the coins db, not the block index db, which is the only // thing loaded at this point. - if (m_blockman.m_block_index.count(m_params.GenesisBlock().GetHash())) + if (m_blockman.m_block_index.count(params.GenesisBlock().GetHash())) return true; try { - const CBlock& block = m_params.GenesisBlock(); - FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, 0, m_chain, m_params, nullptr)}; + const CBlock& block = params.GenesisBlock(); + FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, 0, m_chain, params, nullptr)}; if (blockPos.IsNull()) { return error("%s: writing genesis block to disk failed", __func__); } @@ -4309,7 +4372,7 @@ bool CChainState::LoadGenesisBlock() return true; } -void CChainState::LoadExternalBlockFile( +void Chainstate::LoadExternalBlockFile( FILE* fileIn, FlatFilePos* dbp, std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent) @@ -4319,7 +4382,8 @@ void CChainState::LoadExternalBlockFile( // Either both should be specified (-reindex), or neither (-loadblock). assert(!dbp == !blocks_with_unknown_parent); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; + const CChainParams& params{m_chainman.GetParams()}; int nLoaded = 0; try { @@ -4336,10 +4400,10 @@ void CChainState::LoadExternalBlockFile( try { // locate a header unsigned char buf[CMessageHeader::MESSAGE_START_SIZE]; - blkdat.FindByte(m_params.MessageStart()[0]); + blkdat.FindByte(params.MessageStart()[0]); nRewind = blkdat.GetPos() + 1; blkdat >> buf; - if (memcmp(buf, m_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) { + if (memcmp(buf, params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) { continue; } // read size @@ -4365,7 +4429,7 @@ void CChainState::LoadExternalBlockFile( { LOCK(cs_main); // detect out of order blocks, and store them for later - if (hash != m_params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { + if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), block.hashPrevBlock.ToString()); if (dbp && blocks_with_unknown_parent) { @@ -4384,13 +4448,13 @@ void CChainState::LoadExternalBlockFile( if (state.IsError()) { break; } - } else if (hash != m_params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { + } else if (hash != params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight); } } // Activate the genesis block so normal node progress can continue - if (hash == m_params.GetConsensus().hashGenesisBlock) { + if (hash == params.GetConsensus().hashGenesisBlock) { BlockValidationState state; if (!ActivateBestChain(state, nullptr)) { break; @@ -4411,7 +4475,7 @@ void CChainState::LoadExternalBlockFile( while (range.first != range.second) { std::multimap<uint256, FlatFilePos>::iterator it = range.first; std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>(); - if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) { + if (ReadBlockFromDisk(*pblockrecursive, it->second, params.GetConsensus())) { LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(), head.ToString()); LOCK(cs_main); @@ -4427,18 +4491,29 @@ void CChainState::LoadExternalBlockFile( } } } catch (const std::exception& e) { - LogPrintf("%s: Deserialize or I/O error - %s\n", __func__, e.what()); + // historical bugs added extra data to the block files that does not deserialize cleanly. + // commonly this data is between readable blocks, but it does not really matter. such data is not fatal to the import process. + // the code that reads the block files deals with invalid data by simply ignoring it. + // it continues to search for the next {4 byte magic message start bytes + 4 byte length + block} that does deserialize cleanly + // and passes all of the other block validation checks dealing with POW and the merkle root, etc... + // we merely note with this informational log message when unexpected data is encountered. + // we could also be experiencing a storage system read error, or a read of a previous bad write. these are possible, but + // less likely scenarios. we don't have enough information to tell a difference here. + // the reindex process is not the place to attempt to clean and/or compact the block files. if so desired, a studious node operator + // may use knowledge of the fact that the block files are not entirely pristine in order to prepare a set of pristine, and + // perhaps ordered, block files for later reindexing. + LogPrint(BCLog::REINDEX, "%s: unexpected data at file offset 0x%x - %s. continuing\n", __func__, (nRewind - 1), e.what()); } } } catch (const std::runtime_error& e) { AbortNode(std::string("System error: ") + e.what()); } - LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart); + LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } -void CChainState::CheckBlockIndex() +void Chainstate::CheckBlockIndex() { - if (!fCheckBlockIndex) { + if (!m_chainman.ShouldCheckBlockIndex()) { return; } @@ -4511,7 +4586,7 @@ void CChainState::CheckBlockIndex() // Begin: actual consistency checks. if (pindex->pprev == nullptr) { // Genesis block checks. - assert(pindex->GetBlockHash() == m_params.GetConsensus().hashGenesisBlock); // Genesis block's hash must match. + assert(pindex->GetBlockHash() == m_chainman.GetConsensus().hashGenesisBlock); // Genesis block's hash must match. assert(pindex == m_chain.Genesis()); // The current active chain's genesis block must be this block. } if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) @@ -4658,7 +4733,7 @@ void CChainState::CheckBlockIndex() assert(nNodes == forward.size()); } -std::string CChainState::ToString() +std::string Chainstate::ToString() { AssertLockHeld(::cs_main); CBlockIndex* tip = m_chain.Tip(); @@ -4667,7 +4742,7 @@ std::string CChainState::ToString() tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null"); } -bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) +bool Chainstate::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) { AssertLockHeld(::cs_main); if (coinstip_size == m_coinstip_cache_size_bytes && @@ -4728,10 +4803,10 @@ std::optional<uint256> ChainstateManager::SnapshotBlockhash() const return std::nullopt; } -std::vector<CChainState*> ChainstateManager::GetAll() +std::vector<Chainstate*> ChainstateManager::GetAll() { LOCK(::cs_main); - std::vector<CChainState*> out; + std::vector<Chainstate*> out; if (!IsSnapshotValidated() && m_ibd_chainstate) { out.push_back(m_ibd_chainstate.get()); @@ -4744,28 +4819,15 @@ std::vector<CChainState*> ChainstateManager::GetAll() return out; } -CChainState& ChainstateManager::InitializeChainstate( - CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash) +Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool) { AssertLockHeld(::cs_main); - bool is_snapshot = snapshot_blockhash.has_value(); - std::unique_ptr<CChainState>& to_modify = - is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate; - - if (to_modify) { - throw std::logic_error("should not be overwriting a chainstate"); - } - to_modify.reset(new CChainState(mempool, m_blockman, *this, snapshot_blockhash)); - - // Snapshot chainstates and initial IBD chaintates always become active. - if (is_snapshot || (!is_snapshot && !m_active_chainstate)) { - LogPrintf("Switching active chainstate to %s\n", to_modify->ToString()); - m_active_chainstate = to_modify.get(); - } else { - throw std::logic_error("unexpected chainstate activation"); - } + assert(!m_ibd_chainstate); + assert(!m_active_chainstate); - return *to_modify; + m_ibd_chainstate = std::make_unique<Chainstate>(mempool, m_blockman, *this); + m_active_chainstate = m_ibd_chainstate.get(); + return *m_active_chainstate; } const AssumeutxoData* ExpectedAssumeutxo( @@ -4780,6 +4842,46 @@ const AssumeutxoData* ExpectedAssumeutxo( return nullptr; } +static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +{ + AssertLockHeld(::cs_main); + + if (is_snapshot) { + fs::path base_blockhash_path = db_path / node::SNAPSHOT_BLOCKHASH_FILENAME; + + if (fs::exists(base_blockhash_path)) { + bool removed = fs::remove(base_blockhash_path); + if (!removed) { + LogPrintf("[snapshot] failed to remove file %s\n", + fs::PathToString(base_blockhash_path)); + } + } else { + LogPrintf("[snapshot] snapshot chainstate dir being removed lacks %s file\n", + fs::PathToString(node::SNAPSHOT_BLOCKHASH_FILENAME)); + } + } + + std::string path_str = fs::PathToString(db_path); + LogPrintf("Removing leveldb dir at %s\n", path_str); + + // We have to destruct before this call leveldb::DB in order to release the db + // lock, otherwise `DestroyDB` will fail. See `leveldb::~DBImpl()`. + const bool destroyed = dbwrapper::DestroyDB(path_str, {}).ok(); + + if (!destroyed) { + LogPrintf("error: leveldb DestroyDB call failed on %s\n", path_str); + } + + // Datadir should be removed from filesystem; otherwise initialization may detect + // it on subsequent statups and get confused. + // + // If the base_blockhash_path removal above fails in the case of snapshot + // chainstates, this will return false since leveldb won't remove a non-empty + // directory. + return destroyed && !fs::exists(db_path); +} + bool ChainstateManager::ActivateSnapshot( AutoFile& coins_file, const SnapshotMetadata& metadata, @@ -4825,7 +4927,7 @@ bool ChainstateManager::ActivateSnapshot( } auto snapshot_chainstate = WITH_LOCK(::cs_main, - return std::make_unique<CChainState>( + return std::make_unique<Chainstate>( /*mempool=*/nullptr, m_blockman, *this, base_blockhash)); { @@ -4837,11 +4939,34 @@ bool ChainstateManager::ActivateSnapshot( static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC)); } - const bool snapshot_ok = this->PopulateAndValidateSnapshot( + bool snapshot_ok = this->PopulateAndValidateSnapshot( *snapshot_chainstate, coins_file, metadata); + // If not in-memory, persist the base blockhash for use during subsequent + // initialization. + if (!in_memory) { + LOCK(::cs_main); + if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) { + snapshot_ok = false; + } + } if (!snapshot_ok) { - WITH_LOCK(::cs_main, this->MaybeRebalanceCaches()); + LOCK(::cs_main); + this->MaybeRebalanceCaches(); + + // PopulateAndValidateSnapshot can return (in error) before the leveldb datadir + // has been created, so only attempt removal if we got that far. + if (auto snapshot_datadir = node::FindSnapshotChainstateDir()) { + // We have to destruct leveldb::DB in order to release the db lock, otherwise + // DestroyDB() (in DeleteCoinsDBFromDisk()) will fail. See `leveldb::~DBImpl()`. + // Destructing the chainstate (and so resetting the coinsviews object) does this. + snapshot_chainstate.reset(); + bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir, /*is_snapshot=*/true); + if (!removed) { + AbortNode(strprintf("Failed to remove snapshot chainstate dir (%s). " + "Manually remove it before restarting.\n", fs::PathToString(*snapshot_datadir))); + } + } return false; } @@ -4875,7 +5000,7 @@ static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_load } bool ChainstateManager::PopulateAndValidateSnapshot( - CChainState& snapshot_chainstate, + Chainstate& snapshot_chainstate, AutoFile& coins_file, const SnapshotMetadata& metadata) { @@ -4969,7 +5094,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // Important that we set this. This and the coins_cache accesses above are // sort of a layer violation, but either we reach into the innards of - // CCoinsViewCache here or we have to invert some of the CChainState to + // CCoinsViewCache here or we have to invert some of the Chainstate to // embed them in a snapshot-activation-specific CCoinsViewCache bulk load // method. coins_cache.SetBestBlock(base_blockhash); @@ -5048,7 +5173,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( index->nStatus |= BLOCK_ASSUMED_VALID; } - // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() + // Fake BLOCK_OPT_WITNESS so that Chainstate::NeedsRedownload() // won't ask to rewind the entire assumed-valid chain on startup. if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) { index->nStatus |= BLOCK_OPT_WITNESS; @@ -5071,7 +5196,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( return true; } -CChainState& ChainstateManager::ActiveChainstate() const +Chainstate& ChainstateManager::ActiveChainstate() const { LOCK(::cs_main); assert(m_active_chainstate); @@ -5115,6 +5240,29 @@ void ChainstateManager::MaybeRebalanceCaches() } } +void ChainstateManager::ResetChainstates() +{ + m_ibd_chainstate.reset(); + m_snapshot_chainstate.reset(); + m_active_chainstate = nullptr; +} + +/** + * Apply default chain params to nullopt members. + * This helps to avoid coding errors around the accidental use of the compare + * operators that accept nullopt, thus ignoring the intended default value. + */ +static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts) +{ + if (!opts.check_block_index.has_value()) opts.check_block_index = opts.chainparams.DefaultConsistencyChecks(); + if (!opts.minimum_chain_work.has_value()) opts.minimum_chain_work = UintToArith256(opts.chainparams.GetConsensus().nMinimumChainWork); + if (!opts.assumed_valid_block.has_value()) opts.assumed_valid_block = opts.chainparams.GetConsensus().defaultAssumeValid; + Assert(opts.adjusted_time_callback); + return std::move(opts); +} + +ChainstateManager::ChainstateManager(Options options) : m_options{Flatten(std::move(options))} {} + ChainstateManager::~ChainstateManager() { LOCK(::cs_main); @@ -5126,3 +5274,31 @@ ChainstateManager::~ChainstateManager() i.clear(); } } + +bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool) +{ + assert(!m_snapshot_chainstate); + std::optional<fs::path> path = node::FindSnapshotChainstateDir(); + if (!path) { + return false; + } + std::optional<uint256> base_blockhash = node::ReadSnapshotBaseBlockhash(*path); + if (!base_blockhash) { + return false; + } + LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n", + fs::PathToString(*path)); + + this->ActivateExistingSnapshot(mempool, *base_blockhash); + return true; +} + +Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash) +{ + assert(!m_snapshot_chainstate); + m_snapshot_chainstate = + std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash); + LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString()); + m_active_chainstate = m_snapshot_chainstate.get(); + return *m_snapshot_chainstate; +} diff --git a/src/validation.h b/src/validation.h index 7f5039aaea..b8151dc1fc 100644 --- a/src/validation.h +++ b/src/validation.h @@ -43,7 +43,7 @@ #include <utility> #include <vector> -class CChainState; +class Chainstate; class CBlockTreeDB; class CTxMemPool; class ChainstateManager; @@ -63,11 +63,6 @@ struct Params; static const int MAX_SCRIPTCHECK_THREADS = 15; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; -static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60; -static const bool DEFAULT_CHECKPOINTS_ENABLED = true; -static const bool DEFAULT_TXINDEX = false; -static constexpr bool DEFAULT_COINSTATSINDEX{false}; -static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; /** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ActiveChain().Tip() will not be pruned. */ @@ -96,20 +91,6 @@ extern GlobalMutex g_best_block_mutex; extern std::condition_variable g_best_block_cv; /** Used to notify getblocktemplate RPC of new tips. */ extern uint256 g_best_block; -/** Whether there are dedicated script-checking threads running. - * False indicates all script checking is done on the main threadMessageHandler thread. - */ -extern bool g_parallel_script_checks; -extern bool fCheckBlockIndex; -extern bool fCheckpointsEnabled; -/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ -extern int64_t nMaxTipAge; - -/** Block hash whose ancestors we will assume to have valid scripts without checking them. */ -extern uint256 hashAssumeValid; - -/** Minimum work we will assume exists on some valid chain. */ -extern arith_uint256 nMinimumChainWork; /** Documentation for argument 'checklevel'. */ extern const std::vector<std::string> CHECKLEVEL_DOC; @@ -127,7 +108,7 @@ bool AbortNode(BlockValidationState& state, const std::string& strMessage, const double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pindex); /** Prune block files up to a given height */ -void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight); +void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight); /** * Validation result for a single transaction mempool acceptance. @@ -240,7 +221,7 @@ struct PackageMempoolAcceptResult * * @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason. */ -MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx, +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, int64_t accept_time, bool bypass_limits, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -252,7 +233,7 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr * If a transaction fails, validation will exit early and some results may be missing. It is also * possible for the package to be partially submitted. */ -PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool, +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, const Package& txns, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -333,7 +314,7 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu /** Check a block is completely valid from start to finish (only works on top of our current best block) */ bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, - CChainState& chainstate, + Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, const std::function<NodeClock::time_point()>& adjusted_time_callback, @@ -352,7 +333,7 @@ public: CVerifyDB(); ~CVerifyDB(); bool VerifyDB( - CChainState& chainstate, + Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, @@ -368,7 +349,7 @@ enum DisconnectResult class ConnectTrace; -/** @see CChainState::FlushStateToDisk */ +/** @see Chainstate::FlushStateToDisk */ enum class FlushStateMode { NONE, IF_NEEDED, @@ -421,7 +402,7 @@ enum class CoinsCacheSizeState }; /** - * CChainState stores and provides an API to update our local knowledge of the + * Chainstate stores and provides an API to update our local knowledge of the * current best chain. * * Eventually, the API here is targeted at being exposed externally as a @@ -434,7 +415,7 @@ enum class CoinsCacheSizeState * whereas block information and metadata independent of the current tip is * kept in `BlockManager`. */ -class CChainState +class Chainstate { protected: /** @@ -472,19 +453,15 @@ protected: public: //! Reference to a BlockManager instance which itself is shared across all - //! CChainState instances. + //! Chainstate instances. node::BlockManager& m_blockman; - /** Chain parameters for this chainstate */ - /* TODO: replace with m_chainman.GetParams() */ - const CChainParams& m_params; - //! The chainstate manager that owns this chainstate. The reference is //! necessary so that this instance can check whether it is the active //! chainstate within deeply nested method calls. ChainstateManager& m_chainman; - explicit CChainState( + explicit Chainstate( CTxMemPool* mempool, node::BlockManager& blockman, ChainstateManager& chainman, @@ -705,7 +682,7 @@ public: /** * Make various assertions about the state of the block index. * - * By default this only executes fully when using the Regtest chain; see: fCheckBlockIndex. + * By default this only executes fully when using the Regtest chain; see: m_options.check_block_index. */ void CheckBlockIndex(); @@ -814,7 +791,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - std::unique_ptr<CChainState> m_ibd_chainstate GUARDED_BY(::cs_main); + std::unique_ptr<Chainstate> m_ibd_chainstate GUARDED_BY(::cs_main); //! A chainstate initialized on the basis of a UTXO snapshot. If this is //! non-null, it is always our active chainstate. @@ -825,7 +802,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - std::unique_ptr<CChainState> m_snapshot_chainstate GUARDED_BY(::cs_main); + std::unique_ptr<Chainstate> m_snapshot_chainstate GUARDED_BY(::cs_main); //! Points to either the ibd or snapshot chainstate; indicates our //! most-work chain. @@ -836,7 +813,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - CChainState* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; + Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; //! If true, the assumed-valid chainstate has been fully validated //! by the background validation chainstate. @@ -846,7 +823,7 @@ private: //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( - CChainState& snapshot_chainstate, + Chainstate& snapshot_chainstate, AutoFile& coins_file, const node::SnapshotMetadata& metadata); @@ -862,7 +839,7 @@ private: BlockValidationState& state, CBlockIndex** ppindex, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - friend CChainState; + friend Chainstate; /** Most recent headers presync progress update, for rate-limiting. */ std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {}; @@ -870,13 +847,13 @@ private: public: using Options = kernel::ChainstateManagerOpts; - explicit ChainstateManager(Options options) : m_options{std::move(options)} - { - Assert(m_options.adjusted_time_callback); - } + explicit ChainstateManager(Options options); const CChainParams& GetParams() const { return m_options.chainparams; } const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); } + bool ShouldCheckBlockIndex() const { return *Assert(m_options.check_block_index); } + const arith_uint256& MinimumChainWork() const { return *Assert(m_options.minimum_chain_work); } + const uint256& AssumedValidBlock() const { return *Assert(m_options.assumed_valid_block); } /** * Alias for ::cs_main. @@ -929,26 +906,20 @@ public: //! coins databases. This will be split somehow across chainstates. int64_t m_total_coinsdb_cache{0}; - //! Instantiate a new chainstate and assign it based upon whether it is - //! from a snapshot. + //! Instantiate a new chainstate. //! //! @param[in] mempool The mempool to pass to the chainstate // constructor - //! @param[in] snapshot_blockhash If given, signify that this chainstate - //! is based on a snapshot. - CChainState& InitializeChainstate( - CTxMemPool* mempool, - const std::optional<uint256>& snapshot_blockhash = std::nullopt) - LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + Chainstate& InitializeChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! Get all chainstates currently being used. - std::vector<CChainState*> GetAll(); + std::vector<Chainstate*> GetAll(); //! Construct and activate a Chainstate on the basis of UTXO snapshot data. //! //! Steps: //! - //! - Initialize an unused CChainState. + //! - Initialize an unused Chainstate. //! - Load its `CoinsViews` contents from `coins_file`. //! - Verify that the hash of the resulting coinsdb matches the expected hash //! per assumeutxo chain parameters. @@ -961,7 +932,7 @@ public: AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory); //! The most-work chain. - CChainState& ActiveChainstate() const; + Chainstate& ActiveChainstate() const; CChain& ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChainstate().m_chain; } int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Height(); } CBlockIndex* ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Tip(); } @@ -1053,6 +1024,17 @@ public: * information. */ void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp); + //! When starting up, search the datadir for a chainstate based on a UTXO + //! snapshot that is in the process of being validated. + bool DetectSnapshotChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + //! Switch the active chainstate to one based on a UTXO snapshot that was loaded + //! previously. + Chainstate& ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + ~ChainstateManager(); }; diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index deb1293cd4..3a9d277f65 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -533,7 +533,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) void BerkeleyEnvironment::Flush(bool fShutdown) { - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // Flush log data to the actual data file on all files that are not in use LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); if (!fDbEnvInit) @@ -561,7 +561,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown) no_dbs_accessed = false; } } - LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); if (fShutdown) { char** listp; if (no_dbs_accessed) { @@ -591,14 +591,14 @@ bool BerkeleyDatabase::PeriodicFlush() const std::string strFile = fs::PathToString(m_filename); LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // Flush wallet file so it's self contained env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; - LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); return true; } diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 98925e6b10..9cf2b677e6 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -560,8 +560,12 @@ public: options.create_flags = wallet_creation_flags; options.create_passphrase = passphrase; bilingual_str error; - util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; - return wallet ? std::move(wallet) : util::Error{error}; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override { @@ -570,15 +574,23 @@ public: ReadDatabaseArgs(*m_context.args, options); options.require_existing = true; bilingual_str error; - util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; - return wallet ? std::move(wallet) : util::Error{error}; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override { DatabaseStatus status; bilingual_str error; - util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; - return wallet ? std::move(wallet) : util::Error{error}; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } std::string getWalletDir() override { diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 35df151e84..1d2d7d2a10 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -293,10 +293,7 @@ RPCHelpMan importaddress() if (fRescan) { RescanWallet(*pwallet, reserver); - { - LOCK(pwallet->cs_wallet); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } return UniValue::VNULL; @@ -474,10 +471,7 @@ RPCHelpMan importpubkey() if (fRescan) { RescanWallet(*pwallet, reserver); - { - LOCK(pwallet->cs_wallet); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } return UniValue::VNULL; @@ -1266,15 +1260,15 @@ RPCHelpMan importmulti() { {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"}, {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor", - /*oneline_description=*/"", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"} + RPCArgOptions{.type_str={"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}} }, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n" - " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" - " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" - " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" - " creation time of all keys being imported by the importmulti call will be scanned.", - /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"} + "or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" + "key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" + "\"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" + "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" + "creation time of all keys being imported by the importmulti call will be scanned.", + RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} }, {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, {"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, @@ -1296,12 +1290,12 @@ RPCHelpMan importmulti() }, }, }, - "\"requests\""}, + RPCArgOptions{.oneline_description="\"requests\""}}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."}, }, - "\"options\""}, + RPCArgOptions{.oneline_description="\"options\""}}, }, RPCResult{ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", @@ -1363,7 +1357,18 @@ RPCHelpMan importmulti() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(*pwallet); + + // Check all requests are watchonly + bool is_watchonly{true}; + for (size_t i = 0; i < requests.size(); ++i) { + const UniValue& request = requests[i]; + if (!request.exists("watchonly") || !request["watchonly"].get_bool()) { + is_watchonly = false; + break; + } + } + // Wallet does not need to be unlocked if all requests are watchonly + if (!is_watchonly) EnsureWalletIsUnlocked(wallet); // Verify all timestamps are present before importing any keys. CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); @@ -1395,10 +1400,7 @@ RPCHelpMan importmulti() } if (fRescan && fRunScan && requests.size()) { int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); - { - LOCK(pwallet->cs_wallet); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); @@ -1587,7 +1589,8 @@ RPCHelpMan importdescriptors() return RPCHelpMan{"importdescriptors", "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" - "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n", + "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" + "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", { @@ -1598,18 +1601,18 @@ RPCHelpMan importdescriptors() {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"}, {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"}, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n" - " Use the string \"now\" to substitute the current synced blockchain time.\n" - " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" + "Use the string \"now\" to substitute the current synced blockchain time.\n" + "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" + "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" "of all descriptors being imported will be scanned as well as the mempool.", - /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"} + RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} }, {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, }, }, }, - "\"requests\""}, + RPCArgOptions{.oneline_description="\"requests\""}}, }, RPCResult{ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", @@ -1689,10 +1692,7 @@ RPCHelpMan importdescriptors() // Rescan the blockchain using the lowest timestamp if (rescan) { int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */); - { - LOCK(pwallet->cs_wallet); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); @@ -1749,7 +1749,7 @@ RPCHelpMan listdescriptors() }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, - {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects", + {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, @@ -1888,7 +1888,9 @@ RPCHelpMan restorewallet() { return RPCHelpMan{ "restorewallet", - "\nRestore and loads a wallet from backup.\n", + "\nRestore and loads a wallet from backup.\n" + "\nThe rescan is significantly faster if a descriptor wallet is restored" + "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"}, {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."}, diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 50766f20eb..9c0c953a7a 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -290,8 +290,6 @@ RPCHelpMan lockunspent() LOCK(pwallet->cs_wallet); - RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); - bool fUnlock = request.params[0].get_bool(); const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()}; @@ -304,9 +302,7 @@ RPCHelpMan lockunspent() return true; } - RPCTypeCheckArgument(request.params[1], UniValue::VARR); - - const UniValue& output_params = request.params[1]; + const UniValue& output_params = request.params[1].get_array(); // Create and validate the COutPoints first. @@ -520,7 +516,7 @@ RPCHelpMan listunspent() {"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"}, {"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, }, - "query_options"}, + RPCArgOptions{.oneline_description="query_options"}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -566,19 +562,16 @@ RPCHelpMan listunspent() int nMinDepth = 1; if (!request.params[0].isNull()) { - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); nMinDepth = request.params[0].getInt<int>(); } int nMaxDepth = 9999999; if (!request.params[1].isNull()) { - RPCTypeCheckArgument(request.params[1], UniValue::VNUM); nMaxDepth = request.params[1].getInt<int>(); } std::set<CTxDestination> destinations; if (!request.params[2].isNull()) { - RPCTypeCheckArgument(request.params[2], UniValue::VARR); UniValue inputs = request.params[2].get_array(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; @@ -594,7 +587,6 @@ RPCHelpMan listunspent() bool include_unsafe = true; if (!request.params[3].isNull()) { - RPCTypeCheckArgument(request.params[3], UniValue::VBOOL); include_unsafe = request.params[3].get_bool(); } diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 7eefa76a3e..6cb33fc11e 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -317,7 +317,7 @@ RPCHelpMan sendmany() "\nSend multiple times. Amounts are double-precision floating point numbers." + HELP_REQUIRING_PASSPHRASE, { - {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", "\"\""}, + {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", RPCArgOptions{.oneline_description="\"\""}}, {"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts", { {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, @@ -338,7 +338,7 @@ RPCHelpMan sendmany() {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" "\"" + FeeModes("\"\n\"") + "\""}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra infomration about the transaction."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, }, { RPCResult{"if verbose is not set or set to false", @@ -503,7 +503,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, coinControl.fAllowWatchOnly = options.get_bool(); } else { - RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj(options, { {"add_inputs", UniValueType(UniValue::VBOOL)}, @@ -775,7 +774,7 @@ RPCHelpMan fundrawtransaction() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" "If iswitness is not present, heuristic tests will be used in decoding.\n" "If true, only witness deserialization will be tried.\n" @@ -969,7 +968,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" "\"" + FeeModes("\"\n\"") + "\""}, }, - "options"}, + RPCArgOptions{.oneline_description="options"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", Cat( @@ -1137,7 +1136,7 @@ RPCHelpMan send() {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat<std::vector<RPCArg>>( { - {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."}, + {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"},"Automatically include coins from the wallet to cover the target amount.\n"}, {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n" "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n" "If that happens, you will need to fund the transaction with different inputs and republish it."}, @@ -1174,7 +1173,7 @@ RPCHelpMan send() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1282,7 +1281,7 @@ RPCHelpMan sendall() }, FundTxDoc() ), - "options" + RPCArgOptions{.oneline_description="options"} }, }, RPCResult{ @@ -1380,7 +1379,7 @@ RPCHelpMan sendall() throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n)); } const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)}; - if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) { + if (!tx || input.prevout.n >= tx->tx->vout.size() || !(pwallet->IsMine(tx->tx->vout[input.prevout.n]) & (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE))) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n)); } total_input_value += tx->tx->vout[input.prevout.n].nValue; @@ -1402,6 +1401,10 @@ RPCHelpMan sendall() const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)}; const CAmount effective_value{total_input_value - fee_from_size}; + if (fee_from_size > pwallet->m_default_max_tx_fee) { + throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original); + } + if (effective_value <= 0) { if (send_max) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction, try using lower feerate."); @@ -1410,6 +1413,11 @@ RPCHelpMan sendall() } } + // If this transaction is too large, e.g. because the wallet has many UTXOs, it will be rejected by the node's mempool. + if (tx_size.weight > MAX_STANDARD_TX_WEIGHT) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction too large."); + } + CAmount output_amounts_claimed{0}; for (const CTxOut& out : rawTx.vout) { output_amounts_claimed += out.nValue; @@ -1582,7 +1590,7 @@ RPCHelpMan walletcreatefundedpsbt() {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat<std::vector<RPCArg>>( { - {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."}, + {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"}, "Automatically include coins from the wallet to cover the target amount.\n"}, {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n" "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n" "If that happens, you will need to fund the transaction with different inputs and republish it."}, @@ -1603,7 +1611,7 @@ RPCHelpMan walletcreatefundedpsbt() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, }, RPCResult{ @@ -1644,7 +1652,6 @@ RPCHelpMan walletcreatefundedpsbt() bool rbf{wallet.m_signal_rbf}; const UniValue &replaceable_arg = options["replaceable"]; if (!replaceable_arg.isNull()) { - RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL); rbf = replaceable_arg.isTrue(); } CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index 0e13e4756b..0fe4ed703d 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -839,7 +839,9 @@ RPCHelpMan rescanblockchain() { return RPCHelpMan{"rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n", + "Note: Use \"getwalletinfo\" to query the scanning progress.\n" + "The rescan is significantly faster when used on a descriptor wallet\n" + "and block filters are available (using startup option \"-blockfilterindex=1\").\n", { {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"}, {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."}, diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 3c1634324e..1aa2a87e99 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -12,10 +12,26 @@ #include <univalue.h> +#include <boost/date_time/posix_time/posix_time.hpp> + namespace wallet { static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"}; +int64_t ParseISO8601DateTime(const std::string& str) +{ + static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); + static const std::locale loc(std::locale::classic(), + new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); + std::istringstream iss(str); + iss.imbue(loc); + boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); + iss >> ptime; + if (ptime.is_not_a_date_time() || epoch > ptime) + return 0; + return (ptime - epoch).total_seconds(); +} + bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) { bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool(); diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h index 2ca28914e7..87d34f7c11 100644 --- a/src/wallet/rpc/util.h +++ b/src/wallet/rpc/util.h @@ -45,6 +45,8 @@ std::string LabelFromValue(const UniValue& value); void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry); void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error); + +int64_t ParseISO8601DateTime(const std::string& str); } // namespace wallet #endif // BITCOIN_WALLET_RPC_UTIL_H diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 41654579c6..4c534d64ec 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -28,6 +28,9 @@ util::Result<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const Outp } assert(type != OutputType::BECH32M); + // Fill-up keypool if needed + TopUp(); + LOCK(cs_KeyStore); // Generate a new key that is added to wallet @@ -304,6 +307,9 @@ util::Result<CTxDestination> LegacyScriptPubKeyMan::GetReservedDestination(const return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } + // Fill-up keypool if needed + TopUp(); + if (!ReserveKeyFromKeyPool(index, keypool, internal)) { return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } @@ -586,7 +592,7 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig // or solving information, even if not able to sign fully. return true; } else { - // If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script + // If, given the stuff in sigdata, we could make a valid signature, then we can provide for this script ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata); if (!sigdata.signatures.empty()) { // If we could make signatures, make sure we have a private key to actually make a signature @@ -1983,7 +1989,7 @@ util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const std::optional<OutputType> desc_addr_type = m_wallet_descriptor.descriptor->GetOutputType(); assert(desc_addr_type); if (type != *desc_addr_type) { - throw std::runtime_error(std::string(__func__) + ": Types are inconsistent"); + throw std::runtime_error(std::string(__func__) + ": Types are inconsistent. Stored type does not match type of newly generated address"); } TopUp(); @@ -2001,11 +2007,8 @@ util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const } CTxDestination dest; - std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType(); - if (out_script_type && out_script_type == type) { - ExtractDestination(scripts_temp[0], dest); - } else { - throw std::runtime_error(std::string(__func__) + ": Types are inconsistent. Stored type does not match type of newly generated address"); + if (!ExtractDestination(scripts_temp[0], dest)) { + return util::Error{_("Error: Cannot extract destination from the generated scriptpubkey")}; // shouldn't happen } m_wallet_descriptor.next_index++; WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor); @@ -2642,16 +2645,26 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const { + return GetScriptPubKeys(0); +} + +const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys(int32_t minimum_index) const +{ LOCK(cs_desc_man); std::unordered_set<CScript, SaltedSipHasher> script_pub_keys; script_pub_keys.reserve(m_map_script_pub_keys.size()); - for (auto const& script_pub_key: m_map_script_pub_keys) { - script_pub_keys.insert(script_pub_key.first); + for (auto const& [script_pub_key, index] : m_map_script_pub_keys) { + if (index >= minimum_index) script_pub_keys.insert(script_pub_key); } return script_pub_keys; } +int32_t DescriptorScriptPubKeyMan::GetEndRange() const +{ + return m_max_cached_index + 1; +} + bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool priv) const { LOCK(cs_desc_man); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 3ab489c374..eb77015956 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -643,6 +643,8 @@ public: const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override; + const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const; + int32_t GetEndRange() const; bool GetDescriptorString(std::string& out, const bool priv) const; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index d84310c323..6833f9a095 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -260,32 +260,24 @@ CoinsResult AvailableCoins(const CWallet& wallet, // Filter by spendable outputs only if (!spendable && only_spendable) continue; - // If the Output is P2SH and spendable, we want to know if it is + // Obtain script type + std::vector<std::vector<uint8_t>> script_solutions; + TxoutType type = Solver(output.scriptPubKey, script_solutions); + + // If the output is P2SH and solvable, we want to know if it is // a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine - // this from the redeemScript. If the Output is not spendable, it will be classified + // this from the redeemScript. If the output is not solvable, it will be classified // as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript - CScript script; bool is_from_p2sh{false}; - if (output.scriptPubKey.IsPayToScriptHash() && solvable) { - CTxDestination destination; - if (!ExtractDestination(output.scriptPubKey, destination)) - continue; - const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination)); - if (!provider->GetCScript(hash, script)) - continue; + if (type == TxoutType::SCRIPTHASH && solvable) { + CScript script; + if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue; + type = Solver(script, script_solutions); is_from_p2sh = true; - } else { - script = output.scriptPubKey; } - COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); - - // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script) - // We don't need those here, so we are leaving them in return_values_unused - std::vector<std::vector<uint8_t>> return_values_unused; - TxoutType type; - type = Solver(script, return_values_unused); - result.Add(GetOutputType(type, is_from_p2sh), coin); + result.Add(GetOutputType(type, is_from_p2sh), + COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate)); // Cache total amount as we go result.total_amount += output.nValue; @@ -590,7 +582,13 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) { SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); result.AddInput(preset_inputs); - if (result.GetSelectedValue() < nTargetValue) return std::nullopt; + + if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) { + return std::nullopt; + } else if (result.GetSelectedValue() < nTargetValue) { + return std::nullopt; + } + result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); return result; } diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 30a7fb9eb6..23f024247d 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -922,5 +922,52 @@ BOOST_AUTO_TEST_CASE(effective_value_test) BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 } +BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) +{ + // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used. + // This test creates a coin whose value is higher than the target but whose effective value is lower than the target. + // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds. + + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + wallet->LoadWallet(); + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet->SetupDescriptorScriptPubKeyMans(); + + CoinsResult available_coins; + { + std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase()); + dummyWallet->LoadWallet(); + LOCK(dummyWallet->cs_wallet); + dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + dummyWallet->SetupDescriptorScriptPubKeyMans(); + + add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC + } + + CAmount target{99900}; // 0.000999 BTC + + FastRandomContext rand; + CoinSelectionParams cs_params{ + rand, + /*change_output_size=*/34, + /*change_spend_size=*/148, + /*min_change_target=*/1000, + /*effective_feerate=*/CFeeRate(3000), + /*long_term_feerate=*/CFeeRate(1000), + /*discard_feerate=*/CFeeRate(1000), + /*tx_noinputs_size=*/0, + /*avoid_partial=*/false, + }; + CCoinControl cc; + cc.m_allow_other_inputs = false; + COutput output = available_coins.All().at(0); + cc.SetInputWeight(output.outpoint, 148); + cc.SelectExternal(output.outpoint, output.txout); + + const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params); + BOOST_CHECK(!result); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet diff --git a/src/wallet/test/feebumper_tests.cpp b/src/wallet/test/feebumper_tests.cpp index 6add86dc3d..2480a9b4e1 100644 --- a/src/wallet/test/feebumper_tests.cpp +++ b/src/wallet/test/feebumper_tests.cpp @@ -2,6 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. +#include <consensus/validation.h> +#include <policy/policy.h> #include <primitives/transaction.h> #include <script/script.h> #include <util/strencodings.h> diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp index 90a328179e..1a0708c43a 100644 --- a/src/wallet/test/fuzz/coinselection.cpp +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -30,7 +30,7 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect bool valid_outputgroup{false}; for (auto& coin : coins) { output_group.Insert(coin, /*ancestors=*/0, /*descendants=*/0, positive_only); - // If positive_only was specified, nothing may have been inserted, leading to an empty outpout group + // If positive_only was specified, nothing may have been inserted, leading to an empty output group // that would be invalid for the BnB algorithm valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0; if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) { diff --git a/src/test/fuzz/parse_iso8601.cpp b/src/wallet/test/fuzz/parse_iso8601.cpp index 0fef9a9a1d..5be248c2fb 100644 --- a/src/test/fuzz/parse_iso8601.cpp +++ b/src/wallet/test/fuzz/parse_iso8601.cpp @@ -5,6 +5,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <util/time.h> +#include <wallet/rpc/util.h> #include <cassert> #include <cstdint> @@ -20,7 +21,7 @@ FUZZ_TARGET(parse_iso8601) const std::string iso8601_datetime = FormatISO8601DateTime(random_time); (void)FormatISO8601Date(random_time); - const int64_t parsed_time_1 = ParseISO8601DateTime(iso8601_datetime); + const int64_t parsed_time_1 = wallet::ParseISO8601DateTime(iso8601_datetime); if (random_time >= 0) { assert(parsed_time_1 >= 0); if (iso8601_datetime.length() == 20) { @@ -28,6 +29,6 @@ FUZZ_TARGET(parse_iso8601) } } - const int64_t parsed_time_2 = ParseISO8601DateTime(random_string); + const int64_t parsed_time_2 = wallet::ParseISO8601DateTime(random_string); assert(parsed_time_2 >= 0); } diff --git a/src/wallet/test/rpc_util_tests.cpp b/src/wallet/test/rpc_util_tests.cpp new file mode 100644 index 0000000000..32f6f5ab46 --- /dev/null +++ b/src/wallet/test/rpc_util_tests.cpp @@ -0,0 +1,24 @@ +// 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 <wallet/rpc/util.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { + +BOOST_AUTO_TEST_SUITE(wallet_util_tests) + +BOOST_AUTO_TEST_CASE(util_ParseISO8601DateTime) +{ + BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2000-01-01T00:00:01Z"), 946684801); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2100-12-31T23:59:59Z"), 4133980799); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace wallet diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp new file mode 100644 index 0000000000..f45b69a418 --- /dev/null +++ b/src/wallet/test/walletload_tests.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <wallet/wallet.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { + +BOOST_AUTO_TEST_SUITE(walletload_tests) + +class DummyDescriptor final : public Descriptor { +private: + std::string desc; +public: + explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {}; + ~DummyDescriptor() = default; + + std::string ToString() const override { return desc; } + std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; } + + bool IsRange() const override { return false; } + bool IsSolvable() const override { return false; } + bool IsSingleType() const override { return true; } + bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; } + bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; } + bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; }; + bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; } + void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {} +}; + +BOOST_FIXTURE_TEST_CASE(wallet_load_unknown_descriptor, TestingSetup) +{ + std::unique_ptr<WalletDatabase> database = CreateMockWalletDatabase(); + { + // Write unknown active descriptor + WalletBatch batch(*database, false); + std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt"; + WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0); + BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor)); + BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false)); + } + + { + // Now try to load the wallet and verify the error. + const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(database))); + BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR); + } +} + +BOOST_AUTO_TEST_SUITE_END() +} // namespace wallet diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0949045ff0..431e970edc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,6 +5,7 @@ #include <wallet/wallet.h> +#include <blockfilter.h> #include <chain.h> #include <consensus/amount.h> #include <consensus/consensus.h> @@ -21,6 +22,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <psbt.h> +#include <random.h> #include <script/descriptor.h> #include <script/script.h> #include <script/signingprovider.h> @@ -124,7 +126,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet interfaces::Chain& chain = wallet->chain(); std::string name = wallet->GetName(); - // Unregister with the validation interface which also drops shared ponters. + // Unregister with the validation interface which also drops shared pointers. wallet->m_chain_notifications_handler.reset(); LOCK(context.wallets_mutex); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet); @@ -260,6 +262,64 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s return nullptr; } } + +class FastWalletRescanFilter +{ +public: + FastWalletRescanFilter(const CWallet& wallet) : m_wallet(wallet) + { + // fast rescanning via block filters is only supported by descriptor wallets right now + assert(!m_wallet.IsLegacy()); + + // create initial filter with scripts from all ScriptPubKeyMans + for (auto spkm : m_wallet.GetAllScriptPubKeyMans()) { + auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)}; + assert(desc_spkm != nullptr); + AddScriptPubKeys(desc_spkm); + // save each range descriptor's end for possible future filter updates + if (desc_spkm->IsHDEnabled()) { + m_last_range_ends.emplace(desc_spkm->GetID(), desc_spkm->GetEndRange()); + } + } + } + + void UpdateIfNeeded() + { + // repopulate filter with new scripts if top-up has happened since last iteration + for (const auto& [desc_spkm_id, last_range_end] : m_last_range_ends) { + auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(m_wallet.GetScriptPubKeyMan(desc_spkm_id))}; + assert(desc_spkm != nullptr); + int32_t current_range_end{desc_spkm->GetEndRange()}; + if (current_range_end > last_range_end) { + AddScriptPubKeys(desc_spkm, last_range_end); + m_last_range_ends.at(desc_spkm->GetID()) = current_range_end; + } + } + } + + std::optional<bool> MatchesBlock(const uint256& block_hash) const + { + return m_wallet.chain().blockFilterMatchesAny(BlockFilterType::BASIC, block_hash, m_filter_set); + } + +private: + const CWallet& m_wallet; + /** Map for keeping track of each range descriptor's last seen end range. + * This information is used to detect whether new addresses were derived + * (that is, if the current end range is larger than the saved end range) + * after processing a block and hence a filter set update is needed to + * take possible keypool top-ups into account. + */ + std::map<uint256, int32_t> m_last_range_ends; + GCSFilter::ElementSet m_filter_set; + + void AddScriptPubKeys(const DescriptorScriptPubKeyMan* desc_spkm, int32_t last_range_end = 0) + { + for (const auto& script_pub_key : desc_spkm->GetScriptPubKeys(last_range_end)) { + m_filter_set.emplace(script_pub_key.begin(), script_pub_key.end()); + } + } +}; } // namespace std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) @@ -386,25 +446,31 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b ReadDatabaseArgs(*context.args, options); options.require_existing = true; - if (!fs::exists(backup_file)) { - error = Untranslated("Backup file does not exist"); - status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE; - return nullptr; - } - const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name)); + auto wallet_file = wallet_path / "wallet.dat"; + std::shared_ptr<CWallet> wallet; - if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) { - error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path))); - status = DatabaseStatus::FAILED_ALREADY_EXISTS; - return nullptr; - } + try { + if (!fs::exists(backup_file)) { + error = Untranslated("Backup file does not exist"); + status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE; + return nullptr; + } - auto wallet_file = wallet_path / "wallet.dat"; - fs::copy_file(backup_file, wallet_file, fs::copy_options::none); + if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) { + error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path))); + status = DatabaseStatus::FAILED_ALREADY_EXISTS; + return nullptr; + } - auto wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings); + fs::copy_file(backup_file, wallet_file, fs::copy_options::none); + wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings); + } catch (const std::exception& e) { + assert(!wallet); + if (!error.empty()) error += Untranslated("\n"); + error += strprintf(Untranslated("Unexpected exception: %s"), e.what()); + } if (!wallet) { fs::remove(wallet_file); fs::remove(wallet_path); @@ -882,7 +948,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) wtx.mapValue["replaced_by_txid"] = newHash.ToString(); - // Refresh mempool status without waiting for transactionRemovedFromMempool + // Refresh mempool status without waiting for transactionRemovedFromMempool or transactionAddedToMempool RefreshMempoolStatus(wtx, chain()); WalletBatch batch(GetDatabase()); @@ -1748,7 +1814,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc uint256 block_hash = start_block; ScanResult result; - WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); + std::unique_ptr<FastWalletRescanFilter> fast_rescan_filter; + if (!IsLegacy() && chain().hasBlockFilterIndex(BlockFilterType::BASIC)) fast_rescan_filter = std::make_unique<FastWalletRescanFilter>(*this); + + WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(), + fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks"); fAbortRescan = false; ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption) @@ -1775,9 +1845,22 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } - // Read block data - CBlock block; - chain().findBlock(block_hash, FoundBlock().data(block)); + bool fetch_block{true}; + if (fast_rescan_filter) { + fast_rescan_filter->UpdateIfNeeded(); + auto matches_block{fast_rescan_filter->MatchesBlock(block_hash)}; + if (matches_block.has_value()) { + if (*matches_block) { + LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (filter matched)\n", block_height, block_hash.ToString()); + } else { + result.last_scanned_block = block_hash; + result.last_scanned_height = block_height; + fetch_block = false; + } + } else { + LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (WARNING: block filter not found!)\n", block_height, block_hash.ToString()); + } + } // Find next block separately from reading data above, because reading // is slow and there might be a reorg while it is read. @@ -1786,35 +1869,41 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc uint256 next_block_hash; chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash))); - if (!block.IsNull()) { - LOCK(cs_wallet); - if (!block_still_active) { - // Abort scan if current block is no longer active, to prevent - // marking transactions as coming from the wrong block. - result.last_failed_block = block_hash; - result.status = ScanResult::FAILURE; - break; - } - for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true); - } - // scan succeeded, record block as most recent successfully scanned - result.last_scanned_block = block_hash; - result.last_scanned_height = block_height; + if (fetch_block) { + // Read block data + CBlock block; + chain().findBlock(block_hash, FoundBlock().data(block)); + + if (!block.IsNull()) { + LOCK(cs_wallet); + if (!block_still_active) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; + break; + } + for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { + SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true); + } + // scan succeeded, record block as most recent successfully scanned + result.last_scanned_block = block_hash; + result.last_scanned_height = block_height; - if (save_progress && next_interval) { - CBlockLocator loc = m_chain->getActiveChainLocator(block_hash); + if (save_progress && next_interval) { + CBlockLocator loc = m_chain->getActiveChainLocator(block_hash); - if (!loc.IsNull()) { - WalletLogPrintf("Saving scan progress %d.\n", block_height); - WalletBatch batch(GetDatabase()); - batch.WriteBestBlock(loc); + if (!loc.IsNull()) { + WalletLogPrintf("Saving scan progress %d.\n", block_height); + WalletBatch batch(GetDatabase()); + batch.WriteBestBlock(loc); + } } + } else { + // could not scan block, keep scanning but record this block as the most recent failure + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; } - } else { - // could not scan block, keep scanning but record this block as the most recent failure - result.last_failed_block = block_hash; - result.status = ScanResult::FAILURE; } if (max_height && block_height >= *max_height) { break; @@ -1897,11 +1986,29 @@ std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const return result; } +bool CWallet::ShouldResend() const +{ + // Don't attempt to resubmit if the wallet is configured to not broadcast + if (!fBroadcastTransactions) return false; + + // During reindex, importing and IBD, old wallet transactions become + // unconfirmed. Don't resend them as that would spam other nodes. + // We only allow forcing mempool submission when not relaying to avoid this spam. + if (!chain().isReadyToBroadcast()) return false; + + // Do this infrequently and randomly to avoid giving away + // that these are our transactions. + if (NodeClock::now() < m_next_resend) return false; + + return true; +} + +NodeClock::time_point CWallet::GetDefaultNextResend() { return FastRandomContext{}.rand_uniform_delay(NodeClock::now() + 12h, 24h); } + // Resubmit transactions from the wallet to the mempool, optionally asking the // mempool to relay them. On startup, we will do this for all unconfirmed // transactions but will not ask the mempool to relay them. We do this on startup -// to ensure that our own mempool is aware of our transactions, and to also -// initialize nNextResend so that the actual rebroadcast is scheduled. There +// to ensure that our own mempool is aware of our transactions. There // is a privacy side effect here as not broadcasting on startup also means that we won't // inform the world of our wallet's state, particularly if the wallet (or node) is not // yet synced. @@ -1920,7 +2027,7 @@ std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const // The `force` option results in all unconfirmed transactions being submitted to // the mempool. This does not necessarily result in those transactions being relayed, // that depends on the `relay` option. Periodic rebroadcast uses the pattern -// relay=true force=false (also the default values), while loading into the mempool +// relay=true force=false, while loading into the mempool // (on start, or after import) uses relay=false force=true. void CWallet::ResubmitWalletTransactions(bool relay, bool force) { @@ -1928,17 +2035,6 @@ void CWallet::ResubmitWalletTransactions(bool relay, bool force) // even if forcing. if (!fBroadcastTransactions) return; - // During reindex, importing and IBD, old wallet transactions become - // unconfirmed. Don't resend them as that would spam other nodes. - // We only allow forcing mempool submission when not relaying to avoid this spam. - if (!force && relay && !chain().isReadyToBroadcast()) return; - - // Do this infrequently and randomly to avoid giving away - // that these are our transactions. - if (!force && GetTime() < nNextResend) return; - // resend 12-36 hours from now, ~1 day on average. - nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60); - int submitted_tx_count = 0; { // cs_wallet scope @@ -1951,7 +2047,7 @@ void CWallet::ResubmitWalletTransactions(bool relay, bool force) // Only rebroadcast unconfirmed txs if (!wtx.isUnconfirmed()) continue; - // attempt to rebroadcast all txes more than 5 minutes older than + // Attempt to rebroadcast all txes more than 5 minutes older than // the last block, or all txs if forcing. if (!force && wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; to_submit.insert(&wtx); @@ -1973,7 +2069,9 @@ void CWallet::ResubmitWalletTransactions(bool relay, bool force) void MaybeResendWalletTxs(WalletContext& context) { for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) { + if (!pwallet->ShouldResend()) continue; pwallet->ResubmitWalletTransactions(/*relay=*/true, /*force=*/false); + pwallet->SetNextResend(); } } @@ -2104,6 +2202,7 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh CScript script_pub_key = GetScriptForDestination(pkhash); for (const auto& spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) { + LOCK(cs_wallet); // DescriptorScriptPubKeyMan calls IsLocked which can lock cs_wallet in a deadlocking order return spk_man_pair.second->SignMessage(message, pkhash, str_sig); } } @@ -2369,7 +2468,6 @@ util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, c return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))}; } - spk_man->TopUp(); auto op_dest = spk_man->GetNewDestination(type); if (op_dest) { SetAddressBook(*op_dest, label, "receive"); @@ -2463,10 +2561,7 @@ util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool int return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))}; } - if (nIndex == -1) - { - m_spk_man->TopUp(); - + if (nIndex == -1) { CKeyPool keypool; auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool); if (!op_address) return op_address; @@ -2786,7 +2881,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri ArgsManager& args = *Assert(context.args); const std::string& walletFile = database->Filename(); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. const std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, args, std::move(database)), ReleaseWallet); @@ -2819,8 +2914,12 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri warnings.push_back(strprintf(_("Error reading %s! Transaction data may be missing or incorrect." " Rescanning wallet."), walletFile)); rescan_required = true; - } - else { + } else if (nLoadWalletRet == DBErrors::UNKNOWN_DESCRIPTOR) { + error = strprintf(_("Unrecognized descriptor found. Loading wallet %s\n\n" + "The wallet might had been created on a newer version.\n" + "Please try running the latest software version.\n"), walletFile); + return nullptr; + } else { error = strprintf(_("Error loading %s"), walletFile); return nullptr; } @@ -3003,7 +3102,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri walletInstance->m_spend_zero_conf_change = args.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = args.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); - walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); @@ -3100,12 +3199,14 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf // If a block is pruned after this check, we will load the wallet, // but fail the rescan with a generic error. - error = chain.hasAssumedValidChain() ? - _( - "Assumed-valid: last wallet synchronisation goes beyond " - "available block data. You need to wait for the background " - "validation chain to download more blocks.") : - _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"); + error = chain.havePruned() ? + _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)") : + strprintf(_( + "Error loading wallet. Wallet requires blocks to be downloaded, " + "and software does not currently support loading wallets while " + "blocks are being downloaded out of order when using assumeutxo " + "snapshots. Wallet should be able to load successfully after " + "node sync reaches height %s"), block_height); return false; } } @@ -3185,14 +3286,12 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error) void CWallet::postInitProcess() { - LOCK(cs_wallet); - // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); // Update wallet transactions with current mempool transactions. - chain().requestMempoolTransactions(*this); + WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this)); } bool CWallet::BackupWallet(const std::string& strDest) const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f4ea5f1920..137320acda 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -20,6 +20,7 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> +#include <util/time.h> #include <util/ui_change_type.h> #include <validationinterface.h> #include <wallet/crypter.h> @@ -195,7 +196,7 @@ public: util::Result<CTxDestination> GetReservedDestination(bool internal); //! Return reserved address void ReturnDestination(); - //! Keep the address. Do not return it's key to the keypool when this object goes out of scope + //! Keep the address. Do not return its key to the keypool when this object goes out of scope void KeepDestination(); }; @@ -250,7 +251,7 @@ private: int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE}; /** The next scheduled rebroadcast of wallet transactions. */ - int64_t nNextResend = 0; + NodeClock::time_point m_next_resend{GetDefaultNextResend()}; /** Whether this wallet will submit newly created transactions to the node's mempool and * prompt rebroadcasts (see ResendWalletTransactions()). */ bool fBroadcastTransactions = false; @@ -348,6 +349,8 @@ private: */ static bool AttachChain(const std::shared_ptr<CWallet>& wallet, interfaces::Chain& chain, const bool rescan_required, bilingual_str& error, std::vector<bilingual_str>& warnings); + static NodeClock::time_point GetDefaultNextResend(); + public: /** * Main wallet lock. @@ -399,7 +402,6 @@ public: TxItems wtxOrdered; int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; - uint64_t nAccountingEntryNumber = 0; std::map<CTxDestination, CAddressBookData> m_address_book GUARDED_BY(cs_wallet); const CAddressBookData* FindAddressBookEntry(const CTxDestination&, bool allow_change = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -537,6 +539,10 @@ public: }; ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress); void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; + /** Set the next time this wallet should resend transactions to 12-36 hours from now, ~1 day on average. */ + void SetNextResend() { m_next_resend = GetDefaultNextResend(); } + /** Return true if all conditions for periodically resending transactions are met. */ + bool ShouldResend() const; void ResubmitWalletTransactions(bool relay, bool force); OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 30406a22f9..6a8f0d2481 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -314,6 +314,7 @@ public: std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys; std::map<uint160, CHDChain> m_hd_chains; bool tx_corrupt{false}; + bool descriptor_unknown{false}; CWalletScanState() = default; }; @@ -627,7 +628,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, uint256 id; ssKey >> id; WalletDescriptor desc; - ssValue >> desc; + try { + ssValue >> desc; + } catch (const std::ios_base::failure& e) { + strErr = e.what(); + wss.descriptor_unknown = true; + return false; + } if (wss.m_descriptor_caches.count(id) == 0) { wss.m_descriptor_caches[id] = DescriptorCache(); } @@ -767,6 +774,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) DBErrors result = DBErrors::LOAD_OK; LOCK(pwallet->cs_wallet); + + // Last client version to open this wallet + int last_client = CLIENT_VERSION; + bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client); + pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), last_client); + try { int nMinVersion = 0; if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) { @@ -832,6 +845,13 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) // Set tx_corrupt back to false so that the error is only printed once (per corrupt tx) wss.tx_corrupt = false; result = DBErrors::CORRUPT; + } else if (wss.descriptor_unknown) { + strErr = strprintf("Error: Unrecognized descriptor found in wallet %s. ", pwallet->GetName()); + strErr += (last_client > CLIENT_VERSION) ? "The wallet might had been created on a newer version. " : + "The database might be corrupted or the software version is not compatible with one of your wallet descriptors. "; + strErr += "Please try running the latest software version"; + pwallet->WalletLogPrintf("%s\n", strErr); + return DBErrors::UNKNOWN_DESCRIPTOR; } else { // Leave other errors alone, if we try to fix them we might make things worse. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. @@ -884,11 +904,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (result != DBErrors::LOAD_OK) return result; - // Last client version to open this wallet - int last_client = CLIENT_VERSION; - bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client); - pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), last_client); - pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 6aa25fae03..da6efe534b 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -51,7 +51,8 @@ enum class DBErrors EXTERNAL_SIGNER_SUPPORT_REQUIRED, LOAD_FAIL, NEED_REWRITE, - NEED_RESCAN + NEED_RESCAN, + UNKNOWN_DESCRIPTOR }; namespace DBKeys { diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h index fa3944e32b..97c2599366 100644 --- a/src/zmq/zmqabstractnotifier.h +++ b/src/zmq/zmqabstractnotifier.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H #define BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H - +#include <cstdint> #include <memory> #include <string> diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index 26618735f6..6ee134f392 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -3,13 +3,23 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <zmq/zmqnotificationinterface.h> + +#include <logging.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <util/system.h> +#include <validationinterface.h> +#include <zmq/zmqabstractnotifier.h> #include <zmq/zmqpublishnotifier.h> #include <zmq/zmqutil.h> #include <zmq.h> -#include <validation.h> -#include <util/system.h> +#include <cassert> +#include <map> +#include <string> +#include <utility> +#include <vector> CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(nullptr) { diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 8f81bfd63f..585e900ca6 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -5,10 +5,14 @@ #ifndef BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H #define BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H +#include <primitives/transaction.h> #include <validationinterface.h> + +#include <cstdint> #include <list> #include <memory> +class CBlock; class CBlockIndex; class CZMQAbstractNotifier; diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 011336bf72..eaf3455296 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -6,22 +6,37 @@ #include <chain.h> #include <chainparams.h> +#include <crypto/common.h> +#include <logging.h> +#include <netaddress.h> #include <netbase.h> #include <node/blockstorage.h> +#include <primitives/block.h> +#include <primitives/transaction.h> #include <rpc/server.h> +#include <serialize.h> #include <streams.h> -#include <util/system.h> -#include <validation.h> // For cs_main +#include <sync.h> +#include <uint256.h> +#include <version.h> #include <zmq/zmqutil.h> #include <zmq.h> +#include <cassert> #include <cstdarg> #include <cstddef> +#include <cstdint> +#include <cstring> #include <map> #include <optional> #include <string> #include <utility> +#include <vector> + +namespace Consensus { +struct Params; +} using node::ReadBlockFromDisk; diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h index c1d66bddb1..fcedd1aabe 100644 --- a/src/zmq/zmqpublishnotifier.h +++ b/src/zmq/zmqpublishnotifier.h @@ -7,7 +7,11 @@ #include <zmq/zmqabstractnotifier.h> +#include <cstddef> +#include <cstdint> + class CBlockIndex; +class CTransaction; class CZMQAbstractPublishNotifier : public CZMQAbstractNotifier { diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index ec6d1cbba3..047e6bf9b7 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -11,6 +11,11 @@ #include <univalue.h> +#include <list> +#include <string> + +class JSONRPCRequest; + namespace { static RPCHelpMan getzmqnotifications() |