aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am49
-rw-r--r--src/Makefile.bench.include2
-rw-r--r--src/Makefile.test.include7
-rw-r--r--src/Makefile.test_util.include26
-rw-r--r--src/bench/addrman.cpp8
-rw-r--r--src/bench/base58.cpp6
-rw-r--r--src/bench/bech32.cpp4
-rw-r--r--src/bench/bench.cpp55
-rw-r--r--src/bench/bench.h22
-rw-r--r--src/bench/bench_bitcoin.cpp38
-rw-r--r--src/bench/block_assemble.cpp2
-rw-r--r--src/bench/ccoins_caching.cpp2
-rw-r--r--src/bench/chacha20.cpp6
-rw-r--r--src/bench/chacha_poly_aead.cpp18
-rw-r--r--src/bench/checkblock.cpp4
-rw-r--r--src/bench/checkqueue.cpp2
-rw-r--r--src/bench/coin_selection.cpp4
-rw-r--r--src/bench/crypto_hash.cpp32
-rw-r--r--src/bench/descriptors.cpp8
-rw-r--r--src/bench/duplicate_inputs.cpp2
-rw-r--r--src/bench/examples.cpp2
-rw-r--r--src/bench/gcs_filter.cpp10
-rw-r--r--src/bench/hashpadding.cpp4
-rw-r--r--src/bench/load_external.cpp63
-rw-r--r--src/bench/lockedpool.cpp2
-rw-r--r--src/bench/logging.cpp10
-rw-r--r--src/bench/mempool_eviction.cpp2
-rw-r--r--src/bench/mempool_stress.cpp4
-rw-r--r--src/bench/merkle_root.cpp2
-rw-r--r--src/bench/peer_eviction.cpp12
-rw-r--r--src/bench/poly1305.cpp6
-rw-r--r--src/bench/prevector.cpp4
-rw-r--r--src/bench/rollingbloom.cpp4
-rw-r--r--src/bench/rpc_blockchain.cpp4
-rw-r--r--src/bench/rpc_mempool.cpp2
-rw-r--r--src/bench/strencodings.cpp2
-rw-r--r--src/bench/util_time.cpp8
-rw-r--r--src/bench/verify_script.cpp4
-rw-r--r--src/bench/wallet_balance.cpp8
-rw-r--r--src/bench/wallet_create_tx.cpp143
-rw-r--r--src/bench/wallet_loading.cpp4
-rw-r--r--src/bitcoin-cli.cpp4
-rw-r--r--src/bitcoin-wallet.cpp2
-rw-r--r--src/bitcoind.cpp2
-rw-r--r--src/checkqueue.h3
-rw-r--r--src/common/url.cpp (renamed from src/util/url.cpp)2
-rw-r--r--src/common/url.h (renamed from src/util/url.h)6
-rw-r--r--src/compat/compat.h8
-rw-r--r--src/crypto/sha512.cpp2
-rw-r--r--src/dbwrapper.cpp2
-rw-r--r--src/dbwrapper.h18
-rw-r--r--src/external_signer.cpp3
-rw-r--r--src/init.cpp92
-rw-r--r--src/interfaces/chain.h8
-rw-r--r--src/kernel/chainstatemanager_opts.h14
-rw-r--r--src/logging.cpp6
-rw-r--r--src/logging.h2
-rw-r--r--src/minisketch/configure.ac3
-rw-r--r--src/minisketch/src/bench.cpp4
-rw-r--r--src/minisketch/src/int_utils.h24
-rw-r--r--src/minisketch/src/test.cpp1
-rw-r--r--src/net.cpp3
-rw-r--r--src/net.h6
-rw-r--r--src/net_processing.cpp155
-rw-r--r--src/netbase.cpp22
-rw-r--r--src/netbase.h2
-rw-r--r--src/node/blockstorage.cpp23
-rw-r--r--src/node/blockstorage.h4
-rw-r--r--src/node/chainstate.cpp35
-rw-r--r--src/node/chainstatemanager_args.cpp39
-rw-r--r--src/node/chainstatemanager_args.h19
-rw-r--r--src/node/interfaces.cpp16
-rw-r--r--src/node/psbt.cpp2
-rw-r--r--src/node/txreconciliation.cpp184
-rw-r--r--src/node/txreconciliation.h90
-rw-r--r--src/node/utxo_snapshot.cpp91
-rw-r--r--src/node/utxo_snapshot.h33
-rw-r--r--src/policy/fees.cpp33
-rw-r--r--src/policy/fees.h11
-rw-r--r--src/protocol.cpp2
-rw-r--r--src/protocol.h8
-rw-r--r--src/psbt.cpp49
-rw-r--r--src/psbt.h30
-rw-r--r--src/qt/bitcoingui.cpp6
-rw-r--r--src/qt/forms/debugwindow.ui4
-rw-r--r--src/qt/forms/optionsdialog.ui2
-rw-r--r--src/qt/guiutil.cpp5
-rw-r--r--src/qt/main.cpp2
-rw-r--r--src/qt/optionsdialog.cpp4
-rw-r--r--src/qt/rpcconsole.cpp8
-rw-r--r--src/qt/sendcoinsdialog.cpp4
-rw-r--r--src/rest.cpp44
-rw-r--r--src/rpc/blockchain.cpp268
-rw-r--r--src/rpc/client.cpp3
-rw-r--r--src/rpc/net.cpp16
-rw-r--r--src/rpc/output_script.cpp2
-rw-r--r--src/rpc/rawtransaction.cpp8
-rw-r--r--src/rpc/util.cpp7
-rw-r--r--src/script/sign.cpp21
-rw-r--r--src/script/standard.h2
-rw-r--r--src/streams.h54
-rw-r--r--src/sync.h41
-rw-r--r--src/test/argsman_tests.cpp1043
-rw-r--r--src/test/blockencodings_tests.cpp1
-rw-r--r--src/test/blockmanager_tests.cpp42
-rw-r--r--src/test/coinstatsindex_tests.cpp10
-rw-r--r--src/test/fuzz/process_message.cpp1
-rw-r--r--src/test/fuzz/rbf.cpp1
-rw-r--r--src/test/fuzz/rpc.cpp1
-rw-r--r--src/test/fuzz/string.cpp2
-rw-r--r--src/test/fuzz/tx_pool.cpp1
-rw-r--r--src/test/fuzz/util.cpp20
-rw-r--r--src/test/fuzz/util.h11
-rw-r--r--src/test/fuzz/validation_load_mempool.cpp1
-rw-r--r--src/test/getarg_tests.cpp4
-rw-r--r--src/test/mempool_tests.cpp1
-rw-r--r--src/test/miner_tests.cpp1
-rw-r--r--src/test/netbase_tests.cpp23
-rw-r--r--src/test/policyestimator_tests.cpp1
-rw-r--r--src/test/rbf_tests.cpp1
-rw-r--r--src/test/sigopcount_tests.cpp1
-rw-r--r--src/test/streams_tests.cpp67
-rw-r--r--src/test/system_tests.cpp10
-rw-r--r--src/test/txindex_tests.cpp11
-rw-r--r--src/test/txreconciliation_tests.cpp86
-rw-r--r--src/test/util/chainstate.h49
-rw-r--r--src/test/util/net.h4
-rw-r--r--src/test/util/setup_common.cpp67
-rw-r--r--src/test/util/setup_common.h54
-rw-r--r--src/test/util/txmempool.cpp39
-rw-r--r--src/test/util/txmempool.h37
-rw-r--r--src/test/util/wallet.cpp7
-rw-r--r--src/test/util/wallet.h5
-rw-r--r--src/test/util_tests.cpp1027
-rw-r--r--src/test/validation_chainstate_tests.cpp3
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp396
-rw-r--r--src/txdb.h4
-rw-r--r--src/txmempool.cpp14
-rw-r--r--src/txmempool.h2
-rw-r--r--src/univalue/include/univalue.h9
-rw-r--r--src/univalue/lib/univalue.cpp12
-rw-r--r--src/univalue/test/object.cpp9
-rw-r--r--src/util/check.cpp15
-rw-r--r--src/util/check.h27
-rw-r--r--src/util/error.cpp5
-rw-r--r--src/util/error.h2
-rw-r--r--src/util/sock.cpp32
-rw-r--r--src/util/sock.h12
-rw-r--r--src/util/strencodings.cpp8
-rw-r--r--src/util/strencodings.h11
-rw-r--r--src/util/system.cpp15
-rw-r--r--src/validation.cpp323
-rw-r--r--src/validation.h51
-rw-r--r--src/validationinterface.cpp7
-rw-r--r--src/wallet/coincontrol.h2
-rw-r--r--src/wallet/coinselection.cpp6
-rw-r--r--src/wallet/coinselection.h1
-rw-r--r--src/wallet/rpc/backup.cpp27
-rw-r--r--src/wallet/rpc/coins.cpp22
-rw-r--r--src/wallet/rpc/spend.cpp12
-rw-r--r--src/wallet/rpc/transactions.cpp8
-rw-r--r--src/wallet/rpc/util.cpp2
-rw-r--r--src/wallet/scriptpubkeyman.cpp64
-rw-r--r--src/wallet/scriptpubkeyman.h2
-rw-r--r--src/wallet/spend.cpp209
-rw-r--r--src/wallet/spend.h65
-rw-r--r--src/wallet/test/coinselector_tests.cpp27
-rw-r--r--src/wallet/test/feebumper_tests.cpp2
-rw-r--r--src/wallet/wallet.cpp188
-rw-r--r--src/wallet/wallet.h10
-rw-r--r--src/zmq/zmqabstractnotifier.h2
-rw-r--r--src/zmq/zmqnotificationinterface.cpp14
-rw-r--r--src/zmq/zmqnotificationinterface.h4
-rw-r--r--src/zmq/zmqpublishnotifier.cpp18
-rw-r--r--src/zmq/zmqpublishnotifier.h4
-rw-r--r--src/zmq/zmqrpc.cpp5
176 files changed, 4357 insertions, 2084 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index caeaee511f..8f22b85a80 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
coins.h \
common/bloom.h \
common/run_command.h \
+ common/url.h \
compat/assumptions.h \
compat/byteswap.h \
compat/compat.h \
@@ -198,6 +199,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 \
@@ -209,6 +211,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 \
@@ -301,7 +304,6 @@ BITCOIN_CORE_H = \
util/translation.h \
util/types.h \
util/ui_change_type.h \
- util/url.h \
util/vector.h \
validation.h \
validationinterface.h \
@@ -345,11 +347,7 @@ 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.
+# 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 = \
@@ -384,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 \
@@ -396,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 \
@@ -438,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)
@@ -449,10 +452,9 @@ libbitcoin_zmq_a_SOURCES = \
zmq/zmqrpc.cpp \
zmq/zmqutil.cpp
endif
+#
-
-# wallet: shared between bitcoind and bitcoin-qt, but only linked
-# when wallet enabled
+# 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 = \
@@ -491,14 +493,17 @@ endif
if USE_BDB
libbitcoin_wallet_a_SOURCES += wallet/bdb.cpp wallet/salvage.cpp
endif
+#
+# 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
@@ -578,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 = \
@@ -615,8 +621,9 @@ libbitcoin_consensus_a_SOURCES = \
util/strencodings.cpp \
util/strencodings.h \
version.h
+#
-# common: shared between bitcoind, and bitcoin-qt and non-server tools
+# common #
libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_common_a_SOURCES = \
@@ -656,7 +663,13 @@ libbitcoin_common_a_SOURCES = \
warnings.cpp \
$(BITCOIN_CORE_H)
-# util: shared between all executables.
+if USE_LIBEVENT
+libbitcoin_common_a_CPPFLAGS += $(EVENT_CFLAGS)
+libbitcoin_common_a_SOURCES += common/url.cpp
+endif
+#
+
+# util #
libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_util_a_SOURCES = \
@@ -700,12 +713,9 @@ libbitcoin_util_a_SOURCES = \
util/time.cpp \
util/tokenpipe.cpp \
$(BITCOIN_CORE_H)
+#
-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 = \
@@ -766,6 +776,7 @@ endif
bitcoin_cli_LDADD = \
$(LIBBITCOIN_CLI) \
$(LIBUNIVALUE) \
+ $(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_UTIL) \
$(LIBBITCOIN_CRYPTO)
@@ -902,6 +913,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 \
@@ -925,7 +937,6 @@ libbitcoinkernel_la_SOURCES = \
support/cleanse.cpp \
support/lockedpool.cpp \
sync.cpp \
- threadinterrupt.cpp \
txdb.cpp \
txmempool.cpp \
uint256.cpp \
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index e1e2066877..f1e4e706a1 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -32,6 +32,7 @@ bench_bench_bitcoin_SOURCES = \
bench/examples.cpp \
bench/gcs_filter.cpp \
bench/hashpadding.cpp \
+ bench/load_external.cpp \
bench/lockedpool.cpp \
bench/logging.cpp \
bench/mempool_eviction.cpp \
@@ -79,6 +80,7 @@ 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_SOURCES += bench/wallet_create_tx.cpp
bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS)
endif
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 253f64d2c3..9a9424e84c 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -66,6 +66,7 @@ BITCOIN_TESTS =\
test/addrman_tests.cpp \
test/allocator_tests.cpp \
test/amount_tests.cpp \
+ test/argsman_tests.cpp \
test/arith_uint256_tests.cpp \
test/banman_tests.cpp \
test/base32_tests.cpp \
@@ -77,6 +78,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 +149,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 \
@@ -374,8 +377,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_util.include b/src/Makefile.test_util.include
index ada789f1b0..d142572b27 100644
--- a/src/Makefile.test_util.include
+++ b/src/Makefile.test_util.include
@@ -5,20 +5,21 @@
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) $(BOOST_CPPFLAGS)
libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
@@ -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/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/load_external.cpp b/src/bench/load_external.cpp
new file mode 100644
index 0000000000..be01b2a483
--- /dev/null
+++ b/src/bench/load_external.cpp
@@ -0,0 +1,63 @@
+// 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 <bench/bench.h>
+#include <bench/data.h>
+#include <chainparams.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+
+/**
+ * The LoadExternalBlockFile() function is used during -reindex and -loadblock.
+ *
+ * Create a test file that's similar to a datadir/blocks/blk?????.dat file,
+ * It contains around 134 copies of the same block (typical size of real block files).
+ * For each block in the file, LoadExternalBlockFile() won't find its parent,
+ * and so will skip the block. (In the real system, it will re-read the block
+ * from disk later when it encounters its parent.)
+ *
+ * This benchmark measures the performance of deserializing the block (or just
+ * its header, beginning with PR 16981).
+ */
+static void LoadExternalBlockFile(benchmark::Bench& bench)
+{
+ const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN)};
+
+ // Create a single block as in the blocks files (magic bytes, block size,
+ // block data) as a stream object.
+ const fs::path blkfile{testing_setup.get()->m_path_root / "blk.dat"};
+ CDataStream ss(SER_DISK, 0);
+ auto params{testing_setup->m_node.chainman->GetParams()};
+ ss << params.MessageStart();
+ ss << static_cast<uint32_t>(benchmark::data::block413567.size());
+ // We can't use the streaming serialization (ss << benchmark::data::block413567)
+ // because that first writes a compact size.
+ ss.write(MakeByteSpan(benchmark::data::block413567));
+
+ // Create the test file.
+ {
+ // "wb+" is "binary, O_RDWR | O_CREAT | O_TRUNC".
+ FILE* file{fsbridge::fopen(blkfile, "wb+")};
+ // Make the test block file about 128 MB in length.
+ for (size_t i = 0; i < node::MAX_BLOCKFILE_SIZE / ss.size(); ++i) {
+ if (fwrite(ss.data(), 1, ss.size(), file) != ss.size()) {
+ throw std::runtime_error("write to test file failed\n");
+ }
+ }
+ fclose(file);
+ }
+
+ Chainstate& chainstate{testing_setup->m_node.chainman->ActiveChainstate()};
+ std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
+ FlatFilePos pos;
+ bench.run([&] {
+ // "rb" is "binary, O_RDONLY", positioned to the start of the file.
+ // The file will be closed by LoadExternalBlockFile().
+ FILE* file{fsbridge::fopen(blkfile, "rb")};
+ chainstate.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
+ });
+ fs::remove(blkfile);
+}
+
+BENCHMARK(LoadExternalBlockFile, 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/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_create_tx.cpp b/src/bench/wallet_create_tx.cpp
new file mode 100644
index 0000000000..8f5c50872b
--- /dev/null
+++ b/src/bench/wallet_create_tx.cpp
@@ -0,0 +1,143 @@
+// 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 <bench/bench.h>
+#include <chainparams.h>
+#include <wallet/coincontrol.h>
+#include <consensus/merkle.h>
+#include <kernel/chain.h>
+#include <node/context.h>
+#include <test/util/setup_common.h>
+#include <test/util/wallet.h>
+#include <validation.h>
+#include <wallet/spend.h>
+#include <wallet/wallet.h>
+
+using wallet::CWallet;
+using wallet::CreateMockWalletDatabase;
+using wallet::DBErrors;
+using wallet::WALLET_FLAG_DESCRIPTORS;
+
+struct TipBlock
+{
+ uint256 prev_block_hash;
+ int64_t prev_block_time;
+ int tip_height;
+};
+
+TipBlock getTip(const CChainParams& params, const node::NodeContext& context)
+{
+ auto tip = WITH_LOCK(::cs_main, return context.chainman->ActiveTip());
+ return (tip) ? TipBlock{tip->GetBlockHash(), tip->GetBlockTime(), tip->nHeight} :
+ TipBlock{params.GenesisBlock().GetHash(), params.GenesisBlock().GetBlockTime(), 0};
+}
+
+void generateFakeBlock(const CChainParams& params,
+ const node::NodeContext& context,
+ CWallet& wallet,
+ const CScript& coinbase_out_script)
+{
+ TipBlock tip{getTip(params, context)};
+
+ // Create block
+ CBlock block;
+ CMutableTransaction coinbase_tx;
+ coinbase_tx.vin.resize(1);
+ coinbase_tx.vin[0].prevout.SetNull();
+ coinbase_tx.vout.resize(2);
+ coinbase_tx.vout[0].scriptPubKey = coinbase_out_script;
+ coinbase_tx.vout[0].nValue = 49 * COIN;
+ coinbase_tx.vin[0].scriptSig = CScript() << ++tip.tip_height << OP_0;
+ coinbase_tx.vout[1].scriptPubKey = coinbase_out_script; // extra output
+ coinbase_tx.vout[1].nValue = 1 * COIN;
+ block.vtx = {MakeTransactionRef(std::move(coinbase_tx))};
+
+ block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION;
+ block.hashPrevBlock = tip.prev_block_hash;
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ block.nTime = ++tip.prev_block_time;
+ block.nBits = params.GenesisBlock().nBits;
+ block.nNonce = 0;
+
+ {
+ LOCK(::cs_main);
+ // Add it to the index
+ CBlockIndex* pindex{context.chainman->m_blockman.AddToBlockIndex(block, context.chainman->m_best_header)};
+ // add it to the chain
+ context.chainman->ActiveChain().SetTip(*pindex);
+ }
+
+ // notify wallet
+ const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip());
+ wallet.blockConnected(kernel::MakeBlockInfo(pindex, &block));
+}
+
+struct PreSelectInputs {
+ // How many coins from the wallet the process should select
+ int num_of_internal_inputs;
+ // future: this could have external inputs as well.
+};
+
+static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type, bool allow_other_inputs, std::optional<PreSelectInputs> preset_inputs)
+{
+ const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
+
+ CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()};
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ wallet.SetupDescriptorScriptPubKeyMans();
+ if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false);
+ }
+
+ // Generate destinations
+ CScript dest = GetScriptForDestination(getNewDestination(wallet, output_type));
+
+ // Generate chain; each coinbase will have two outputs to fill-up the wallet
+ const auto& params = Params();
+ unsigned int chain_size = 5000; // 5k blocks means 10k UTXO for the wallet (minus 200 due COINBASE_MATURITY)
+ for (unsigned int i = 0; i < chain_size; ++i) {
+ generateFakeBlock(params, test_setup->m_node, wallet, dest);
+ }
+
+ // Check available balance
+ auto bal = wallet::GetAvailableBalance(wallet); // Cache
+ assert(bal == 50 * COIN * (chain_size - COINBASE_MATURITY));
+
+ wallet::CCoinControl coin_control;
+ coin_control.m_allow_other_inputs = allow_other_inputs;
+
+ CAmount target = 0;
+ if (preset_inputs) {
+ // Select inputs, each has 49 BTC
+ wallet::CoinFilterParams filter_coins;
+ filter_coins.max_count = preset_inputs->num_of_internal_inputs;
+ const auto& res = WITH_LOCK(wallet.cs_wallet,
+ return wallet::AvailableCoins(wallet, /*coinControl=*/nullptr, /*feerate=*/std::nullopt, filter_coins));
+ for (int i=0; i < preset_inputs->num_of_internal_inputs; i++) {
+ const auto& coin{res.coins.at(output_type)[i]};
+ target += coin.txout.nValue;
+ coin_control.Select(coin.outpoint);
+ }
+ }
+
+ // If automatic coin selection is enabled, add the value of another UTXO to the target
+ if (coin_control.m_allow_other_inputs) target += 50 * COIN;
+ std::vector<wallet::CRecipient> recipients = {{dest, target, true}};
+
+ bench.epochIterations(5).run([&] {
+ LOCK(wallet.cs_wallet);
+ const auto& tx_res = CreateTransaction(wallet, recipients, -1, coin_control);
+ assert(tx_res);
+ });
+}
+
+static void WalletCreateTxUseOnlyPresetInputs(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/false,
+ {{/*num_of_internal_inputs=*/4}}); }
+
+static void WalletCreateTxUsePresetInputsAndCoinSelection(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/true,
+ {{/*num_of_internal_inputs=*/4}}); }
+
+BENCHMARK(WalletCreateTxUseOnlyPresetInputs, benchmark::PriorityLevel::LOW)
+BENCHMARK(WalletCreateTxUsePresetInputsAndCoinSelection, benchmark::PriorityLevel::LOW)
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-cli.cpp b/src/bitcoin-cli.cpp
index e64e2202ba..c06488dbe9 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -9,6 +9,7 @@
#include <chainparamsbase.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <compat/stdin.h>
#include <policy/feerate.h>
@@ -21,7 +22,6 @@
#include <util/strencodings.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <algorithm>
#include <chrono>
@@ -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-wallet.cpp b/src/bitcoin-wallet.cpp
index d556300ee2..78c1a2060c 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -9,6 +9,7 @@
#include <chainparams.h>
#include <chainparamsbase.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <interfaces/init.h>
#include <key.h>
@@ -17,7 +18,6 @@
#include <tinyformat.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <wallet/wallettool.h>
#include <exception>
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 9f81640ddb..d8d4e34e47 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -9,6 +9,7 @@
#include <chainparams.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <init.h>
#include <interfaces/chain.h>
@@ -25,7 +26,6 @@
#include <util/threadnames.h>
#include <util/tokenpipe.h>
#include <util/translation.h>
-#include <util/url.h>
#include <any>
#include <functional>
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/util/url.cpp b/src/common/url.cpp
index ea9323e666..5200d55096 100644
--- a/src/util/url.cpp
+++ b/src/common/url.cpp
@@ -2,7 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <util/url.h>
+#include <common/url.h>
#include <event2/http.h>
diff --git a/src/util/url.h b/src/common/url.h
index 5a7b11fa04..7bbd8b60de 100644
--- a/src/util/url.h
+++ b/src/common/url.h
@@ -2,8 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_UTIL_URL_H
-#define BITCOIN_UTIL_URL_H
+#ifndef BITCOIN_COMMON_URL_H
+#define BITCOIN_COMMON_URL_H
#include <string>
@@ -11,4 +11,4 @@ using UrlDecodeFn = std::string(const std::string& url_encoded);
UrlDecodeFn urlDecode;
extern UrlDecodeFn* const URL_DECODE;
-#endif // BITCOIN_UTIL_URL_H
+#endif // BITCOIN_COMMON_URL_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/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/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 0e582629f7..f255834830 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -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/init.cpp b/src/init.cpp
index 2dc7ff4254..6f44ed6c3d 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>
@@ -484,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);
@@ -552,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);
@@ -928,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) {
@@ -993,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)) {
@@ -1042,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;
}
@@ -1144,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);
}
@@ -1255,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")) {
@@ -1388,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());
@@ -1424,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;
@@ -1679,7 +1723,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);
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/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h
index 520d0e8e75..226bb6031e 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/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/minisketch/configure.ac b/src/minisketch/configure.ac
index 9dc66e7fd2..83910448a2 100644
--- a/src/minisketch/configure.ac
+++ b/src/minisketch/configure.ac
@@ -124,9 +124,6 @@ if test "x$use_ccache" != "xno"; then
fi
AC_MSG_RESULT($use_ccache)
fi
-if test "x$use_ccache" = "xyes"; then
- AX_CHECK_COMPILE_FLAG([-Qunused-arguments],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Qunused-arguments"],,[[$CXXFLAG_WERROR]])
-fi
VERIFY_DEFINES=-DMINISKETCH_VERIFY
RELEASE_DEFINES=
diff --git a/src/minisketch/src/bench.cpp b/src/minisketch/src/bench.cpp
index f55944a448..dc44379fdb 100644
--- a/src/minisketch/src/bench.cpp
+++ b/src/minisketch/src/bench.cpp
@@ -62,13 +62,11 @@ int main(int argc, char** argv) {
if (!states[0]) {
printf(" -\t");
} else {
- double total = 0.0;
for (auto& state : states) {
auto start = std::chrono::steady_clock::now();
minisketch_decode(state, 2 * syndromes, roots.data());
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> dur(stop - start);
- total += dur.count();
benches.push_back(dur.count());
}
std::sort(benches.begin(), benches.end());
@@ -98,7 +96,6 @@ int main(int argc, char** argv) {
if (!states[0]) {
printf(" -\t");
} else {
- double total = 0.0;
for (auto& state : states) {
auto start = std::chrono::steady_clock::now();
for (auto val : data) {
@@ -106,7 +103,6 @@ int main(int argc, char** argv) {
}
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> dur(stop - start);
- total += dur.count();
benches.push_back(dur.count());
}
std::sort(benches.begin(), benches.end());
diff --git a/src/minisketch/src/int_utils.h b/src/minisketch/src/int_utils.h
index 62b2c38a29..d21ba56f33 100644
--- a/src/minisketch/src/int_utils.h
+++ b/src/minisketch/src/int_utils.h
@@ -129,17 +129,7 @@ constexpr inline I Mask() { return ((I((I(-1)) << (std::numeric_limits<I>::digit
/** Compute the smallest power of two that is larger than val. */
template<typename I>
static inline int CountBits(I val, int max) {
-#ifdef HAVE_CLZ
- (void)max;
- if (val == 0) return 0;
- if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) {
- return std::numeric_limits<unsigned>::digits - __builtin_clz(val);
- } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) {
- return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val);
- } else {
- return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val);
- }
-#elif _MSC_VER
+#ifdef _MSC_VER
(void)max;
unsigned long index;
unsigned char ret;
@@ -149,7 +139,17 @@ static inline int CountBits(I val, int max) {
ret = _BitScanReverse64(&index, val);
}
if (!ret) return 0;
- return index;
+ return index + 1;
+#elif HAVE_CLZ
+ (void)max;
+ if (val == 0) return 0;
+ if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) {
+ return std::numeric_limits<unsigned>::digits - __builtin_clz(val);
+ } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) {
+ return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val);
+ } else {
+ return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val);
+ }
#else
while (max && (val >> (max - 1) == 0)) --max;
return max;
diff --git a/src/minisketch/src/test.cpp b/src/minisketch/src/test.cpp
index 417937ea5f..85b9e9e396 100644
--- a/src/minisketch/src/test.cpp
+++ b/src/minisketch/src/test.cpp
@@ -9,6 +9,7 @@
#include <limits>
#include <random>
#include <stdexcept>
+#include <string>
#include <vector>
#include "../include/minisketch.h"
diff --git a/src/net.cpp b/src/net.cpp
index 0736f3ef1b..3c28b9eddf 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -970,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;
}
diff --git a/src/net.h b/src/net.h
index 11bfc4c9fb..245f14731b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -489,10 +489,8 @@ public:
/** Whether this peer provides all services that we want. Used for eviction decisions */
std::atomic_bool m_has_all_wanted_services{false};
- /** Whether we should relay transactions to this peer (their version
- * message did not include fRelay=false and this is not a block-relay-only
- * connection). This only changes from false to true. It will never change
- * back to false. Used only in inbound eviction logic. */
+ /** Whether we should relay transactions to this peer. This only changes
+ * from false to true. It will never change back to false. */
std::atomic_bool m_relays_txs{false};
/** Whether this peer has loaded a bloom filter. Used only in inbound
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index eca6263392..6aaacd5068 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>
@@ -271,12 +272,7 @@ struct Peer {
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};
@@ -399,9 +395,7 @@ struct Peer {
private:
Mutex m_tx_relay_mutex;
- /** Transaction relay data. Will be a nullptr if we're not relaying
- * transactions with this peer (e.g. if it's a block-relay-only peer or
- * the peer has sent us fRelay=false with bloom filters disabled). */
+ /** Transaction relay data. May be a nullptr. */
std::unique_ptr<TxRelay> m_tx_relay GUARDED_BY(m_tx_relay_mutex);
};
@@ -642,9 +636,8 @@ private:
* @param[in] chain_start_header Where these headers connect in our index.
* @param[in,out] headers The headers to be processed.
*
- * @return True if chain was low work and a headers sync was
- * initiated (and headers will be empty after calling); false
- * otherwise.
+ * @return True if chain was low work (headers will be empty after
+ * calling); false otherwise.
*/
bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom,
const CBlockIndex* chain_start_header,
@@ -708,6 +701,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};
@@ -1294,7 +1288,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;
}
@@ -1383,7 +1377,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)
@@ -1497,6 +1491,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);
@@ -1781,6 +1776,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)
@@ -2389,7 +2389,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());
}
/**
@@ -2560,16 +2560,20 @@ bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlo
peer.m_headers_sync.reset(new HeadersSyncState(peer.m_id, m_chainparams.GetConsensus(),
chain_start_header, minimum_chain_work));
- // Now a HeadersSyncState object for tracking this synchronization is created,
- // process the headers using it as normal.
- return IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers);
+ // Now a HeadersSyncState object for tracking this synchronization
+ // is created, process the headers using it as normal. Failures are
+ // handled inside of IsContinuationOfLowWorkHeadersSync.
+ (void)IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers);
} 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;
}
@@ -2699,14 +2703,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 minimum 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());
@@ -3241,8 +3245,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);
@@ -3252,11 +3254,14 @@ 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)
+ // Only initialize the Peer::TxRelay m_relay_txs data structure if:
+ // - this isn't an outbound block-relay-only connection, and
+ // - this isn't an outbound feeler 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() &&
+ !pfrom.IsFeelerConn() &&
(fRelay || (peer->m_our_services & NODE_BLOOM))) {
auto* const tx_relay = peer->SetTxRelay();
{
@@ -3266,6 +3271,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);
@@ -3393,6 +3417,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;
}
@@ -3456,6 +3490,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;
@@ -3810,12 +3896,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 minimum 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.
@@ -4279,7 +4365,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);
@@ -5145,7 +5231,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
@@ -5224,6 +5310,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;
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 57f81e6bb6..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();
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 37d74ed102..29501c1959 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -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 */
@@ -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/chainstate.cpp b/src/node/chainstate.cpp
index 3f1d6dd743..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>
@@ -32,13 +35,13 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
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 (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)) {
@@ -146,6 +162,11 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
};
}
+ // 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, {}};
}
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 8a0011a629..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);
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 9dd6f06997..c94521792f 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -6,8 +6,14 @@
#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
@@ -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 22defb91a9..ab5599a1b4 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -1011,20 +1011,37 @@ void CBlockPolicyEstimator::FlushUnconfirmed()
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/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 894a401e56..2bd38103ed 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -410,7 +410,7 @@ void BitcoinGUI::createActions()
connect(action, &QAction::triggered, [this, path] {
auto activity = new OpenWalletActivity(m_wallet_controller, this);
- connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet);
+ connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
activity->open(path);
});
}
@@ -512,7 +512,7 @@ void BitcoinGUI::createMenuBar()
connect(minimize_action, &QAction::triggered, [] {
QApplication::activeWindow()->showMinimized();
});
- connect(qApp, &QApplication::focusWindowChanged, [minimize_action] (QWindow* window) {
+ connect(qApp, &QApplication::focusWindowChanged, this, [minimize_action] (QWindow* window) {
minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized);
});
@@ -527,7 +527,7 @@ void BitcoinGUI::createMenuBar()
}
});
- connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) {
+ connect(qApp, &QApplication::focusWindowChanged, this, [zoom_action] (QWindow* window) {
zoom_action->setEnabled(window != nullptr);
});
#endif
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index ead977296a..f1b66341d1 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.</string>
</property>
<property name="text">
- <string>Wants Tx Relay</string>
+ <string>Transaction Relay</string>
</property>
</widget>
</item>
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 b9f0be41e3..6e88b57e08 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -615,9 +615,10 @@ bool SetStartOnSystemStartup(bool fAutoStart)
else
{
char pszExePath[MAX_PATH+1];
- ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1);
- if (r == -1)
+ ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath));
+ if (r == -1 || r > MAX_PATH) {
return false;
+ }
pszExePath[r] = '\0';
fs::create_directories(GetAutostartDir());
diff --git a/src/qt/main.cpp b/src/qt/main.cpp
index e8f39584ad..45131a1cf5 100644
--- a/src/qt/main.cpp
+++ b/src/qt/main.cpp
@@ -4,9 +4,9 @@
#include <qt/bitcoin.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <util/translation.h>
-#include <util/url.h>
#include <QCoreApplication>
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/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 53c352b393..57094fc857 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -289,7 +289,9 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
updateCoinControlState();
- prepareStatus = model->prepareTransaction(*m_current_transaction, *m_coin_control);
+ CCoinControl coin_control = *m_coin_control;
+ coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
+ prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
// process prepareStatus and on error generate message shown to user
processSendCoinsReturn(prepareStatus,
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 93d11c8276..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");
@@ -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.",
@@ -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])"},
- }},
- },
- RPCArgOptions{.oneline_description="[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 + "\"]\'") +
@@ -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",
@@ -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/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/net.cpp b/src/rpc/net.cpp
index d701a180ab..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) {
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 f365de7d0c..d654de1862 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -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/util.cpp b/src/rpc/util.cpp
index 3e98e89791..dd5739faf7 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -65,11 +65,8 @@ void RPCTypeCheckObj(const UniValue& o,
if (!fAllowNull && v.isNull())
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first));
- if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) {
- std::string err = strprintf("Expected type %s for %s, got %s",
- uvTypeName(t.second.type), t.first, uvTypeName(v.type()));
- throw JSONRPCError(RPC_TYPE_ERROR, err);
- }
+ if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull())))
+ throw JSONRPCError(RPC_TYPE_ERROR, strprintf("JSON value of type %s for field %s is not of expected type %s", uvTypeName(v.type()), t.first, uvTypeName(t.second.type)));
}
if (fStrict)
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 5da0d076d8..0d74a661a5 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -146,6 +146,16 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat
static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion)
{
+ KeyOriginInfo info;
+ if (provider.GetKeyOriginByXOnly(pubkey, info)) {
+ auto it = sigdata.taproot_misc_pubkeys.find(pubkey);
+ if (it == sigdata.taproot_misc_pubkeys.end()) {
+ sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info));
+ } else {
+ it->second.first.insert(leaf_hash);
+ }
+ }
+
auto lookup_key = std::make_pair(pubkey, leaf_hash);
auto it = sigdata.taproot_script_sigs.find(lookup_key);
if (it != sigdata.taproot_script_sigs.end()) {
@@ -170,17 +180,6 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu
// <xonly pubkey> OP_CHECKSIG
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
XOnlyPubKey pubkey{Span{script}.subspan(1, 32)};
-
- KeyOriginInfo info;
- if (provider.GetKeyOriginByXOnly(pubkey, info)) {
- auto it = sigdata.taproot_misc_pubkeys.find(pubkey);
- if (it == sigdata.taproot_misc_pubkeys.end()) {
- sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info));
- } else {
- it->second.first.insert(leaf_hash);
- }
- }
-
std::vector<unsigned char> sig;
if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) {
result = Vector(std::move(sig));
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 ce1ba26d45..84b12f65aa 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -487,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.
@@ -610,7 +612,6 @@ private:
uint64_t nRewind; //!< how many bytes we guarantee to rewind
std::vector<std::byte> vchBuf; //!< the buffer
-protected:
//! read data from the source to fill the buffer
bool Fill() {
unsigned int pos = nSrcPos % vchBuf.size();
@@ -628,6 +629,28 @@ protected:
return true;
}
+ //! Advance the stream's read pointer (m_read_pos) by up to 'length' bytes,
+ //! filling the buffer from the file so that at least one byte is available.
+ //! Return a pointer to the available buffer data and the number of bytes
+ //! (which may be less than the requested length) that may be accessed
+ //! beginning at that pointer.
+ std::pair<std::byte*, size_t> AdvanceStream(size_t length)
+ {
+ assert(m_read_pos <= nSrcPos);
+ if (m_read_pos + length > nReadLimit) {
+ throw std::ios_base::failure("Attempt to position past buffer limit");
+ }
+ // If there are no bytes available, read from the file.
+ if (m_read_pos == nSrcPos && length > 0) Fill();
+
+ size_t buffer_offset{static_cast<size_t>(m_read_pos % vchBuf.size())};
+ size_t buffer_available{static_cast<size_t>(vchBuf.size() - buffer_offset)};
+ size_t bytes_until_source_pos{static_cast<size_t>(nSrcPos - m_read_pos)};
+ size_t advance{std::min({length, buffer_available, bytes_until_source_pos})};
+ m_read_pos += advance;
+ return std::make_pair(&vchBuf[buffer_offset], advance);
+ }
+
public:
CBufferedFile(FILE* fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn)
: nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), m_read_pos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, std::byte{0})
@@ -665,24 +688,21 @@ public:
//! read a number of bytes
void read(Span<std::byte> dst)
{
- if (dst.size() + m_read_pos > nReadLimit) {
- throw std::ios_base::failure("Read attempted past buffer limit");
- }
while (dst.size() > 0) {
- if (m_read_pos == nSrcPos)
- Fill();
- unsigned int pos = m_read_pos % vchBuf.size();
- size_t nNow = dst.size();
- if (nNow + pos > vchBuf.size())
- nNow = vchBuf.size() - pos;
- if (nNow + m_read_pos > nSrcPos)
- nNow = nSrcPos - m_read_pos;
- memcpy(dst.data(), &vchBuf[pos], nNow);
- m_read_pos += nNow;
- dst = dst.subspan(nNow);
+ auto [buffer_pointer, length]{AdvanceStream(dst.size())};
+ memcpy(dst.data(), buffer_pointer, length);
+ dst = dst.subspan(length);
}
}
+ //! Move the read position ahead in the stream to the given position.
+ //! Use SetPos() to back up in the stream, not SkipTo().
+ void SkipTo(const uint64_t file_pos)
+ {
+ assert(file_pos >= m_read_pos);
+ while (m_read_pos < file_pos) AdvanceStream(file_pos - m_read_pos);
+ }
+
//! return the current reading position
uint64_t GetPos() const {
return m_read_pos;
diff --git a/src/sync.h b/src/sync.h
index a9d0091bd2..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());
@@ -173,7 +175,7 @@ private:
}
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/argsman_tests.cpp b/src/test/argsman_tests.cpp
new file mode 100644
index 0000000000..d00876bc70
--- /dev/null
+++ b/src/test/argsman_tests.cpp
@@ -0,0 +1,1043 @@
+// Copyright (c) 2011-2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <util/system.h>
+#include <fs.h>
+#include <sync.h>
+#include <test/util/logging.h>
+#include <test/util/setup_common.h>
+#include <test/util/str.h>
+#include <util/strencodings.h>
+#include <univalue.h>
+
+#include <array>
+#include <optional>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(argsman_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(util_datadir)
+{
+ // Use local args variable instead of m_args to avoid making assumptions about test setup
+ ArgsManager args;
+ args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
+
+ const fs::path dd_norm = args.GetDataDirBase();
+
+ args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/");
+ args.ClearPathCache();
+ BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
+
+ args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.");
+ args.ClearPathCache();
+ BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
+
+ args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/./");
+ args.ClearPathCache();
+ BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
+
+ args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.//");
+ args.ClearPathCache();
+ BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
+}
+
+struct TestArgsManager : public ArgsManager
+{
+ TestArgsManager() { m_network_only_args.clear(); }
+ void ReadConfigString(const std::string str_config)
+ {
+ std::istringstream streamConfig(str_config);
+ {
+ LOCK(cs_args);
+ m_settings.ro_config.clear();
+ m_config_sections.clear();
+ }
+ std::string error;
+ BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error));
+ }
+ void SetNetworkOnlyArg(const std::string arg)
+ {
+ LOCK(cs_args);
+ m_network_only_args.insert(arg);
+ }
+ void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args)
+ {
+ for (const auto& arg : args) {
+ AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS);
+ }
+ }
+ using ArgsManager::GetSetting;
+ using ArgsManager::GetSettingsList;
+ using ArgsManager::ReadConfigStream;
+ using ArgsManager::cs_args;
+ using ArgsManager::m_network;
+ using ArgsManager::m_settings;
+};
+
+//! Test GetSetting and GetArg type coercion, negation, and default value handling.
+class CheckValueTest : public TestChain100Setup
+{
+public:
+ struct Expect {
+ util::SettingsValue setting;
+ bool default_string = false;
+ bool default_int = false;
+ bool default_bool = false;
+ const char* string_value = nullptr;
+ std::optional<int64_t> int_value;
+ std::optional<bool> bool_value;
+ std::optional<std::vector<std::string>> list_value;
+ const char* error = nullptr;
+
+ explicit Expect(util::SettingsValue s) : setting(std::move(s)) {}
+ Expect& DefaultString() { default_string = true; return *this; }
+ Expect& DefaultInt() { default_int = true; return *this; }
+ Expect& DefaultBool() { default_bool = true; return *this; }
+ Expect& String(const char* s) { string_value = s; return *this; }
+ Expect& Int(int64_t i) { int_value = i; return *this; }
+ Expect& Bool(bool b) { bool_value = b; return *this; }
+ Expect& List(std::vector<std::string> m) { list_value = std::move(m); return *this; }
+ Expect& Error(const char* e) { error = e; return *this; }
+ };
+
+ void CheckValue(unsigned int flags, const char* arg, const Expect& expect)
+ {
+ TestArgsManager test;
+ test.SetupArgs({{"-value", flags}});
+ const char* argv[] = {"ignored", arg};
+ std::string error;
+ bool success = test.ParseParameters(arg ? 2 : 1, (char**)argv, error);
+
+ BOOST_CHECK_EQUAL(test.GetSetting("-value").write(), expect.setting.write());
+ auto settings_list = test.GetSettingsList("-value");
+ if (expect.setting.isNull() || expect.setting.isFalse()) {
+ BOOST_CHECK_EQUAL(settings_list.size(), 0U);
+ } else {
+ BOOST_CHECK_EQUAL(settings_list.size(), 1U);
+ BOOST_CHECK_EQUAL(settings_list[0].write(), expect.setting.write());
+ }
+
+ if (expect.error) {
+ BOOST_CHECK(!success);
+ BOOST_CHECK_NE(error.find(expect.error), std::string::npos);
+ } else {
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(error, "");
+ }
+
+ if (expect.default_string) {
+ BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), "zzzzz");
+ } else if (expect.string_value) {
+ BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), expect.string_value);
+ } else {
+ BOOST_CHECK(!success);
+ }
+
+ if (expect.default_int) {
+ BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), 99999);
+ } else if (expect.int_value) {
+ BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), *expect.int_value);
+ } else {
+ BOOST_CHECK(!success);
+ }
+
+ if (expect.default_bool) {
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), false);
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), true);
+ } else if (expect.bool_value) {
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), *expect.bool_value);
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), *expect.bool_value);
+ } else {
+ BOOST_CHECK(!success);
+ }
+
+ if (expect.list_value) {
+ auto l = test.GetArgs("-value");
+ BOOST_CHECK_EQUAL_COLLECTIONS(l.begin(), l.end(), expect.list_value->begin(), expect.list_value->end());
+ } else {
+ BOOST_CHECK(!success);
+ }
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest)
+{
+ using M = ArgsManager;
+
+ CheckValue(M::ALLOW_ANY, nullptr, Expect{{}}.DefaultString().DefaultInt().DefaultBool().List({}));
+ CheckValue(M::ALLOW_ANY, "-novalue", Expect{false}.String("0").Int(0).Bool(false).List({}));
+ CheckValue(M::ALLOW_ANY, "-novalue=", Expect{false}.String("0").Int(0).Bool(false).List({}));
+ CheckValue(M::ALLOW_ANY, "-novalue=0", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
+ CheckValue(M::ALLOW_ANY, "-novalue=1", Expect{false}.String("0").Int(0).Bool(false).List({}));
+ CheckValue(M::ALLOW_ANY, "-novalue=2", Expect{false}.String("0").Int(0).Bool(false).List({}));
+ CheckValue(M::ALLOW_ANY, "-novalue=abc", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
+ CheckValue(M::ALLOW_ANY, "-value", Expect{""}.String("").Int(0).Bool(true).List({""}));
+ CheckValue(M::ALLOW_ANY, "-value=", Expect{""}.String("").Int(0).Bool(true).List({""}));
+ CheckValue(M::ALLOW_ANY, "-value=0", Expect{"0"}.String("0").Int(0).Bool(false).List({"0"}));
+ CheckValue(M::ALLOW_ANY, "-value=1", Expect{"1"}.String("1").Int(1).Bool(true).List({"1"}));
+ CheckValue(M::ALLOW_ANY, "-value=2", Expect{"2"}.String("2").Int(2).Bool(true).List({"2"}));
+ CheckValue(M::ALLOW_ANY, "-value=abc", Expect{"abc"}.String("abc").Int(0).Bool(false).List({"abc"}));
+}
+
+struct NoIncludeConfTest {
+ std::string Parse(const char* arg)
+ {
+ TestArgsManager test;
+ test.SetupArgs({{"-includeconf", ArgsManager::ALLOW_ANY}});
+ std::array argv{"ignored", arg};
+ std::string error;
+ (void)test.ParseParameters(argv.size(), argv.data(), error);
+ return error;
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(util_NoIncludeConf, NoIncludeConfTest)
+{
+ BOOST_CHECK_EQUAL(Parse("-noincludeconf"), "");
+ BOOST_CHECK_EQUAL(Parse("-includeconf"), "-includeconf cannot be used from commandline; -includeconf=\"\"");
+ BOOST_CHECK_EQUAL(Parse("-includeconf=file"), "-includeconf cannot be used from commandline; -includeconf=\"file\"");
+}
+
+BOOST_AUTO_TEST_CASE(util_ParseParameters)
+{
+ TestArgsManager testArgs;
+ const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
+ const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
+ const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
+ const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
+
+ const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"};
+
+ std::string error;
+ LOCK(testArgs.cs_args);
+ testArgs.SetupArgs({a, b, ccc, d});
+ BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error));
+ BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
+
+ BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error));
+ BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
+
+ BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error));
+ // expectation: -ignored is ignored (program name argument),
+ // -a, -b and -ccc end up in map, -d ignored because it is after
+ // a non-option argument (non-GNU option parsing)
+ BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty());
+ BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc")
+ && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d"));
+ BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc")
+ && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d"));
+
+ BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1);
+ BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == "");
+ BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2);
+ BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument");
+ BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple");
+ BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2);
+}
+
+BOOST_AUTO_TEST_CASE(util_ParseInvalidParameters)
+{
+ TestArgsManager test;
+ test.SetupArgs({{"-registered", ArgsManager::ALLOW_ANY}});
+
+ const char* argv[] = {"ignored", "-registered"};
+ std::string error;
+ BOOST_CHECK(test.ParseParameters(2, (char**)argv, error));
+ BOOST_CHECK_EQUAL(error, "");
+
+ argv[1] = "-unregistered";
+ BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error));
+ BOOST_CHECK_EQUAL(error, "Invalid parameter -unregistered");
+
+ // Make sure registered parameters prefixed with a chain name trigger errors.
+ // (Previously, they were accepted and ignored.)
+ argv[1] = "-test.registered";
+ BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error));
+ BOOST_CHECK_EQUAL(error, "Invalid parameter -test.registered");
+}
+
+static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int)
+{
+ TestArgsManager test;
+ test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}});
+ std::string arg = "-value=" + str;
+ const char* argv[] = {"ignored", arg.c_str()};
+ std::string error;
+ BOOST_CHECK(test.ParseParameters(2, (char**)argv, error));
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool);
+ BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool);
+ BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99998), expected_int);
+ BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), expected_int);
+}
+
+// Test bool and int parsing.
+BOOST_AUTO_TEST_CASE(util_ArgParsing)
+{
+ // Some of these cases could be ambiguous or surprising to users, and might
+ // be worth triggering errors or warnings in the future. But for now basic
+ // test coverage is useful to avoid breaking backwards compatibility
+ // unintentionally.
+ TestParse("", true, 0);
+ TestParse(" ", false, 0);
+ TestParse("0", false, 0);
+ TestParse("0 ", false, 0);
+ TestParse(" 0", false, 0);
+ TestParse("+0", false, 0);
+ TestParse("-0", false, 0);
+ TestParse("5", true, 5);
+ TestParse("5 ", true, 5);
+ TestParse(" 5", true, 5);
+ TestParse("+5", true, 5);
+ TestParse("-5", true, -5);
+ TestParse("0 5", false, 0);
+ TestParse("5 0", true, 5);
+ TestParse("050", true, 50);
+ TestParse("0.", false, 0);
+ TestParse("5.", true, 5);
+ TestParse("0.0", false, 0);
+ TestParse("0.5", false, 0);
+ TestParse("5.0", true, 5);
+ TestParse("5.5", true, 5);
+ TestParse("x", false, 0);
+ TestParse("x0", false, 0);
+ TestParse("x5", false, 0);
+ TestParse("0x", false, 0);
+ TestParse("5x", true, 5);
+ TestParse("0x5", false, 0);
+ TestParse("false", false, 0);
+ TestParse("true", false, 0);
+ TestParse("yes", false, 0);
+ TestParse("no", false, 0);
+}
+
+BOOST_AUTO_TEST_CASE(util_GetBoolArg)
+{
+ TestArgsManager testArgs;
+ const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
+ const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
+ const auto c = std::make_pair("-c", ArgsManager::ALLOW_ANY);
+ const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
+ const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
+ const auto f = std::make_pair("-f", ArgsManager::ALLOW_ANY);
+
+ const char *argv_test[] = {
+ "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"};
+ std::string error;
+ LOCK(testArgs.cs_args);
+ testArgs.SetupArgs({a, b, c, d, e, f});
+ BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error));
+
+ // Each letter should be set.
+ for (const char opt : "abcdef")
+ BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt);
+
+ // Nothing else should be in the map
+ BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 &&
+ testArgs.m_settings.ro_config.empty());
+
+ // The -no prefix should get stripped on the way in.
+ BOOST_CHECK(!testArgs.IsArgSet("-nob"));
+
+ // The -b option is flagged as negated, and nothing else is
+ BOOST_CHECK(testArgs.IsArgNegated("-b"));
+ BOOST_CHECK(!testArgs.IsArgNegated("-a"));
+
+ // Check expected values.
+ BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true);
+ BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false);
+ BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false);
+ BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true);
+ BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false);
+ BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false);
+}
+
+BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
+{
+ // Test some awful edge cases that hopefully no user will ever exercise.
+ TestArgsManager testArgs;
+
+ // Params test
+ const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY);
+ const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY);
+ const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"};
+ testArgs.SetupArgs({foo, bar});
+ std::string error;
+ BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error));
+
+ // This was passed twice, second one overrides the negative setting.
+ BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
+ BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "");
+
+ // A double negative is a positive, and not marked as negated.
+ BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
+ BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
+
+ // Config test
+ const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n";
+ BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error));
+ testArgs.ReadConfigString(conf_test);
+
+ // This was passed twice, second one overrides the negative setting,
+ // and the value.
+ BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
+ BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1");
+
+ // A double negative is a positive, and does not count as negated.
+ BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
+ BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
+
+ // Combined test
+ const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"};
+ const char *combo_test_conf = "foo=1\nnobar=1\n";
+ BOOST_CHECK(testArgs.ParseParameters(3, (char**)combo_test_args, error));
+ testArgs.ReadConfigString(combo_test_conf);
+
+ // Command line overrides, but doesn't erase old setting
+ BOOST_CHECK(testArgs.IsArgNegated("-foo"));
+ BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0");
+ BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0);
+
+ // Command line overrides, but doesn't erase old setting
+ BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
+ BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "");
+ BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1
+ && testArgs.GetArgs("-bar").front() == "");
+}
+
+BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
+{
+ const char *str_config =
+ "a=\n"
+ "b=1\n"
+ "ccc=argument\n"
+ "ccc=multiple\n"
+ "d=e\n"
+ "nofff=1\n"
+ "noggg=0\n"
+ "h=1\n"
+ "noh=1\n"
+ "noi=1\n"
+ "i=1\n"
+ "sec1.ccc=extend1\n"
+ "\n"
+ "[sec1]\n"
+ "ccc=extend2\n"
+ "d=eee\n"
+ "h=1\n"
+ "[sec2]\n"
+ "ccc=extend3\n"
+ "iii=2\n";
+
+ TestArgsManager test_args;
+ LOCK(test_args.cs_args);
+ const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
+ const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
+ const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
+ const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
+ const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
+ const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_ANY);
+ const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_ANY);
+ const auto h = std::make_pair("-h", ArgsManager::ALLOW_ANY);
+ const auto i = std::make_pair("-i", ArgsManager::ALLOW_ANY);
+ const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_ANY);
+ test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii});
+
+ test_args.ReadConfigString(str_config);
+ // expectation: a, b, ccc, d, fff, ggg, h, i end up in map
+ // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii
+
+ BOOST_CHECK(test_args.m_settings.command_line_options.empty());
+ BOOST_CHECK(test_args.m_settings.ro_config.size() == 3);
+ BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8);
+ BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3);
+ BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2);
+
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("a"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("b"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("d"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("h"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("i"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii"));
+
+ BOOST_CHECK(test_args.IsArgSet("-a"));
+ BOOST_CHECK(test_args.IsArgSet("-b"));
+ BOOST_CHECK(test_args.IsArgSet("-ccc"));
+ BOOST_CHECK(test_args.IsArgSet("-d"));
+ BOOST_CHECK(test_args.IsArgSet("-fff"));
+ BOOST_CHECK(test_args.IsArgSet("-ggg"));
+ BOOST_CHECK(test_args.IsArgSet("-h"));
+ BOOST_CHECK(test_args.IsArgSet("-i"));
+ BOOST_CHECK(!test_args.IsArgSet("-zzz"));
+ BOOST_CHECK(!test_args.IsArgSet("-iii"));
+
+ BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
+
+ for (const bool def : {false, true}) {
+ BOOST_CHECK(test_args.GetBoolArg("-a", def));
+ BOOST_CHECK(test_args.GetBoolArg("-b", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-ccc", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-d", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-fff", def));
+ BOOST_CHECK(test_args.GetBoolArg("-ggg", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-h", def));
+ BOOST_CHECK(test_args.GetBoolArg("-i", def));
+ BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def);
+ BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def);
+ }
+
+ BOOST_CHECK(test_args.GetArgs("-a").size() == 1
+ && test_args.GetArgs("-a").front() == "");
+ BOOST_CHECK(test_args.GetArgs("-b").size() == 1
+ && test_args.GetArgs("-b").front() == "1");
+ BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2
+ && test_args.GetArgs("-ccc").front() == "argument"
+ && test_args.GetArgs("-ccc").back() == "multiple");
+ BOOST_CHECK(test_args.GetArgs("-fff").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1
+ && test_args.GetArgs("-ggg").front() == "1");
+ BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-h").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-noh").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-i").size() == 1
+ && test_args.GetArgs("-i").front() == "1");
+ BOOST_CHECK(test_args.GetArgs("-noi").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0);
+
+ BOOST_CHECK(!test_args.IsArgNegated("-a"));
+ BOOST_CHECK(!test_args.IsArgNegated("-b"));
+ BOOST_CHECK(!test_args.IsArgNegated("-ccc"));
+ BOOST_CHECK(!test_args.IsArgNegated("-d"));
+ BOOST_CHECK(test_args.IsArgNegated("-fff"));
+ BOOST_CHECK(!test_args.IsArgNegated("-ggg"));
+ BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence
+ BOOST_CHECK(!test_args.IsArgNegated("-i")); // last setting takes precedence
+ BOOST_CHECK(!test_args.IsArgNegated("-zzz"));
+
+ // Test sections work
+ test_args.SelectConfigNetwork("sec1");
+
+ // same as original
+ BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
+ // d is overridden
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
+ // section-specific setting
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
+ // section takes priority for multiple values
+ BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1");
+ // check multiple values works
+ const std::vector<std::string> sec1_ccc_expected = {"extend1","extend2","argument","multiple"};
+ const auto& sec1_ccc_res = test_args.GetArgs("-ccc");
+ BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end());
+
+ test_args.SelectConfigNetwork("sec2");
+
+ // same as original
+ BOOST_CHECK(test_args.GetArg("-a", "xxx") == "");
+ BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1");
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
+ BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0");
+ BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1");
+ BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx");
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
+ // section-specific setting
+ BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2");
+ // section takes priority for multiple values
+ BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3");
+ // check multiple values works
+ const std::vector<std::string> sec2_ccc_expected = {"extend3","argument","multiple"};
+ const auto& sec2_ccc_res = test_args.GetArgs("-ccc");
+ BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end());
+
+ // Test section only options
+
+ test_args.SetNetworkOnlyArg("-d");
+ test_args.SetNetworkOnlyArg("-ccc");
+ test_args.SetNetworkOnlyArg("-h");
+
+ test_args.SelectConfigNetwork(CBaseChainParams::MAIN);
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
+ BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
+
+ test_args.SelectConfigNetwork("sec1");
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
+ BOOST_CHECK(test_args.GetArgs("-d").size() == 1);
+ BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
+
+ test_args.SelectConfigNetwork("sec2");
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx");
+ BOOST_CHECK(test_args.GetArgs("-d").size() == 0);
+ BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1);
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
+}
+
+BOOST_AUTO_TEST_CASE(util_GetArg)
+{
+ TestArgsManager testArgs;
+ LOCK(testArgs.cs_args);
+ testArgs.m_settings.command_line_options.clear();
+ testArgs.m_settings.command_line_options["strtest1"] = {"string..."};
+ // strtest2 undefined on purpose
+ testArgs.m_settings.command_line_options["inttest1"] = {"12345"};
+ testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"};
+ // inttest3 undefined on purpose
+ testArgs.m_settings.command_line_options["booltest1"] = {""};
+ // booltest2 undefined on purpose
+ testArgs.m_settings.command_line_options["booltest3"] = {"0"};
+ testArgs.m_settings.command_line_options["booltest4"] = {"1"};
+
+ // priorities
+ testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"};
+ testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"};
+ testArgs.m_settings.command_line_options["pritest3"] = {"a"};
+ testArgs.m_settings.ro_config[""]["pritest3"] = {"b"};
+ testArgs.m_settings.command_line_options["pritest4"] = {"a","b"};
+ testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"};
+
+ BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string...");
+ BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default");
+ BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest1", -1), 12345);
+ BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest2", -1), 81985529216486895LL);
+ BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest3", -1), -1);
+ BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true);
+ BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false);
+ BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false);
+ BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true);
+
+ BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b");
+ BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a");
+ BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a");
+ BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b");
+}
+
+BOOST_AUTO_TEST_CASE(util_GetChainName)
+{
+ TestArgsManager test_args;
+ const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY);
+ const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY);
+ test_args.SetupArgs({testnet, regtest});
+
+ const char* argv_testnet[] = {"cmd", "-testnet"};
+ const char* argv_regtest[] = {"cmd", "-regtest"};
+ const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"};
+ const char* argv_both[] = {"cmd", "-testnet", "-regtest"};
+
+ // equivalent to "-testnet"
+ // regtest in testnet section is ignored
+ const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1";
+ std::string error;
+
+ BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "main");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest");
+
+ BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error));
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
+ BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
+
+ BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
+
+ BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
+
+ // check setting the network to test (and thus making
+ // [test] regtest=1 potentially relevant) doesn't break things
+ test_args.SelectConfigNetwork("test");
+
+ BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
+
+ BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_test_no_reg, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
+
+ BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
+ test_args.ReadConfigString(testnetconf);
+ BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
+}
+
+// Test different ways settings can be merged, and verify results. This test can
+// be used to confirm that updates to settings code don't change behavior
+// unintentionally.
+//
+// The test covers:
+//
+// - Combining different setting actions. Possible actions are: configuring a
+// setting, negating a setting (adding "-no" prefix), and configuring/negating
+// settings in a network section (adding "main." or "test." prefixes).
+//
+// - Combining settings from command line arguments and a config file.
+//
+// - Combining SoftSet and ForceSet calls.
+//
+// - Testing "main" and "test" network values to make sure settings from network
+// sections are applied and to check for mainnet-specific behaviors like
+// inheriting settings from the default section.
+//
+// - Testing network-specific settings like "-wallet", that may be ignored
+// outside a network section, and non-network specific settings like "-server"
+// that aren't sensitive to the network.
+//
+struct ArgsMergeTestingSetup : public BasicTestingSetup {
+ //! Max number of actions to sequence together. Can decrease this when
+ //! debugging to make test results easier to understand.
+ static constexpr int MAX_ACTIONS = 3;
+
+ enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
+ using ActionList = Action[MAX_ACTIONS];
+
+ //! Enumerate all possible test configurations.
+ template <typename Fn>
+ void ForEachMergeSetup(Fn&& fn)
+ {
+ ActionList arg_actions = {};
+ // command_line_options do not have sections. Only iterate over SET and NEGATE
+ ForEachNoDup(arg_actions, SET, NEGATE, [&] {
+ ActionList conf_actions = {};
+ ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
+ for (bool soft_set : {false, true}) {
+ for (bool force_set : {false, true}) {
+ for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
+ for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
+ for (bool net_specific : {false, true}) {
+ fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
+ }
+ }
+ }
+ }
+ }
+ });
+ });
+ }
+
+ //! Translate actions into a list of <key>=<value> setting strings.
+ std::vector<std::string> GetValues(const ActionList& actions,
+ const std::string& section,
+ const std::string& name,
+ const std::string& value_prefix)
+ {
+ std::vector<std::string> values;
+ int suffix = 0;
+ for (Action action : actions) {
+ if (action == NONE) break;
+ std::string prefix;
+ if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + ".";
+ if (action == SET || action == SECTION_SET) {
+ for (int i = 0; i < 2; ++i) {
+ values.push_back(prefix + name + "=" + value_prefix + ToString(++suffix));
+ }
+ }
+ if (action == NEGATE || action == SECTION_NEGATE) {
+ values.push_back(prefix + "no" + name + "=1");
+ }
+ }
+ return values;
+ }
+};
+
+// Regression test covering different ways config settings can be merged. The
+// test parses and merges settings, representing the results as strings that get
+// compared against an expected hash. To debug, the result strings can be dumped
+// to a file (see comments below).
+BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
+{
+ CHash256 out_sha;
+ FILE* out_file = nullptr;
+ if (const char* out_path = getenv("ARGS_MERGE_TEST_OUT")) {
+ out_file = fsbridge::fopen(out_path, "w");
+ if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
+ }
+
+ ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set,
+ const std::string& section, const std::string& network, bool net_specific) {
+ TestArgsManager parser;
+ LOCK(parser.cs_args);
+
+ std::string desc = "net=";
+ desc += network;
+ parser.m_network = network;
+
+ const std::string& name = net_specific ? "wallet" : "server";
+ const std::string key = "-" + name;
+ parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ if (net_specific) parser.SetNetworkOnlyArg(key);
+
+ auto args = GetValues(arg_actions, section, name, "a");
+ std::vector<const char*> argv = {"ignored"};
+ for (auto& arg : args) {
+ arg.insert(0, "-");
+ desc += " ";
+ desc += arg;
+ argv.push_back(arg.c_str());
+ }
+ std::string error;
+ BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
+ BOOST_CHECK_EQUAL(error, "");
+
+ std::string conf;
+ for (auto& conf_val : GetValues(conf_actions, section, name, "c")) {
+ desc += " ";
+ desc += conf_val;
+ conf += conf_val;
+ conf += "\n";
+ }
+ std::istringstream conf_stream(conf);
+ BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
+ BOOST_CHECK_EQUAL(error, "");
+
+ if (soft_set) {
+ desc += " soft";
+ parser.SoftSetArg(key, "soft1");
+ parser.SoftSetArg(key, "soft2");
+ }
+
+ if (force_set) {
+ desc += " force";
+ parser.ForceSetArg(key, "force1");
+ parser.ForceSetArg(key, "force2");
+ }
+
+ desc += " || ";
+
+ if (!parser.IsArgSet(key)) {
+ desc += "unset";
+ BOOST_CHECK(!parser.IsArgNegated(key));
+ BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default");
+ BOOST_CHECK(parser.GetArgs(key).empty());
+ } else if (parser.IsArgNegated(key)) {
+ desc += "negated";
+ BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0");
+ BOOST_CHECK(parser.GetArgs(key).empty());
+ } else {
+ desc += parser.GetArg(key, "default");
+ desc += " |";
+ for (const auto& arg : parser.GetArgs(key)) {
+ desc += " ";
+ desc += arg;
+ }
+ }
+
+ std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs();
+ if (!ignored.empty()) {
+ desc += " | ignored";
+ for (const auto& arg : ignored) {
+ desc += " ";
+ desc += arg;
+ }
+ }
+
+ desc += "\n";
+
+ out_sha.Write(MakeUCharSpan(desc));
+ if (out_file) {
+ BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
+ }
+ });
+
+ if (out_file) {
+ if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
+ out_file = nullptr;
+ }
+
+ unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
+ out_sha.Finalize(out_sha_bytes);
+ std::string out_sha_hex = HexStr(out_sha_bytes);
+
+ // If check below fails, should manually dump the results with:
+ //
+ // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge
+ //
+ // And verify diff against previous results to make sure the changes are expected.
+ //
+ // Results file is formatted like:
+ //
+ // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
+ BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82");
+}
+
+// Similar test as above, but for ArgsManager::GetChainName function.
+struct ChainMergeTestingSetup : public BasicTestingSetup {
+ static constexpr int MAX_ACTIONS = 2;
+
+ enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG };
+ using ActionList = Action[MAX_ACTIONS];
+
+ //! Enumerate all possible test configurations.
+ template <typename Fn>
+ void ForEachMergeSetup(Fn&& fn)
+ {
+ ActionList arg_actions = {};
+ ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] {
+ ActionList conf_actions = {};
+ ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); });
+ });
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
+{
+ CHash256 out_sha;
+ FILE* out_file = nullptr;
+ if (const char* out_path = getenv("CHAIN_MERGE_TEST_OUT")) {
+ out_file = fsbridge::fopen(out_path, "w");
+ if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
+ }
+
+ ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) {
+ TestArgsManager parser;
+ LOCK(parser.cs_args);
+ parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+
+ auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" :
+ action == DISABLE_TEST ? "-testnet=0" :
+ action == NEGATE_TEST ? "-notestnet=1" :
+ action == ENABLE_REG ? "-regtest=1" :
+ action == DISABLE_REG ? "-regtest=0" :
+ action == NEGATE_REG ? "-noregtest=1" : nullptr; };
+
+ std::string desc;
+ std::vector<const char*> argv = {"ignored"};
+ for (Action action : arg_actions) {
+ const char* argstr = arg(action);
+ if (!argstr) break;
+ argv.push_back(argstr);
+ desc += " ";
+ desc += argv.back();
+ }
+ std::string error;
+ BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
+ BOOST_CHECK_EQUAL(error, "");
+
+ std::string conf;
+ for (Action action : conf_actions) {
+ const char* argstr = arg(action);
+ if (!argstr) break;
+ desc += " ";
+ desc += argstr + 1;
+ conf += argstr + 1;
+ conf += "\n";
+ }
+ std::istringstream conf_stream(conf);
+ BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
+ BOOST_CHECK_EQUAL(error, "");
+
+ desc += " || ";
+ try {
+ desc += parser.GetChainName();
+ } catch (const std::runtime_error& e) {
+ desc += "error: ";
+ desc += e.what();
+ }
+ desc += "\n";
+
+ out_sha.Write(MakeUCharSpan(desc));
+ if (out_file) {
+ BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
+ }
+ });
+
+ if (out_file) {
+ if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
+ out_file = nullptr;
+ }
+
+ unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
+ out_sha.Finalize(out_sha_bytes);
+ std::string out_sha_hex = HexStr(out_sha_bytes);
+
+ // If check below fails, should manually dump the results with:
+ //
+ // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge
+ //
+ // And verify diff against previous results to make sure the changes are expected.
+ //
+ // Results file is formatted like:
+ //
+ // <input> || <output>
+ BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c");
+}
+
+BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
+{
+ // Test writing setting.
+ TestArgsManager args1;
+ args1.ForceSetArg("-datadir", fs::PathToString(m_path_root));
+ args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; });
+ args1.WriteSettingsFile();
+
+ // Test reading setting.
+ TestArgsManager args2;
+ args2.ForceSetArg("-datadir", fs::PathToString(m_path_root));
+ args2.ReadSettingsFile();
+ args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); });
+
+ // Test error logging, and remove previously written setting.
+ {
+ ASSERT_DEBUG_LOG("Failed renaming settings file");
+ fs::remove(args1.GetDataDirBase() / "settings.json");
+ fs::create_directory(args1.GetDataDirBase() / "settings.json");
+ args2.WriteSettingsFile();
+ fs::remove(args1.GetDataDirBase() / "settings.json");
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
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 2a6a777cfe..8a2b0792fd 100644
--- a/src/test/coinstatsindex_tests.cpp
+++ b/src/test/coinstatsindex_tests.cpp
@@ -76,10 +76,16 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
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,
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index f6a35da4fc..5a4df735da 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -130,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/rbf.cpp b/src/test/fuzz/rbf.cpp
index e06e57d919..678fc7a5aa 100644
--- a/src/test/fuzz/rbf.cpp
+++ b/src/test/fuzz/rbf.cpp
@@ -11,6 +11,7 @@
#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/string.cpp b/src/test/fuzz/string.cpp
index 94399faf04..f6373351d8 100644
--- a/src/test/fuzz/string.cpp
+++ b/src/test/fuzz/string.cpp
@@ -4,6 +4,7 @@
#include <blockfilter.h>
#include <clientversion.h>
+#include <common/url.h>
#include <logging.h>
#include <netaddress.h>
#include <netbase.h>
@@ -27,7 +28,6 @@
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <version.h>
#include <cstdint>
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index a34e501fcc..46ca8e47e9 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -13,6 +13,7 @@
#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>
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
index 0080b1e56e..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{
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index a354df2bf7..dfe4855326 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -47,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);
@@ -72,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;
diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp
index d96609416b..9a90de8911 100644
--- a/src/test/fuzz/validation_load_mempool.cpp
+++ b/src/test/fuzz/validation_load_mempool.cpp
@@ -12,6 +12,7 @@
#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/getarg_tests.cpp b/src/test/getarg_tests.cpp
index 70dd137e22..3643b80d5f 100644
--- a/src/test/getarg_tests.cpp
+++ b/src/test/getarg_tests.cpp
@@ -429,7 +429,7 @@ BOOST_AUTO_TEST_CASE(logargs)
const auto okaylog = std::make_pair("-okaylog", ArgsManager::ALLOW_ANY);
const auto dontlog = std::make_pair("-dontlog", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE);
SetupArgs(local_args, {okaylog_bool, okaylog_negbool, okaylog, dontlog});
- ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private");
+ ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private42");
// Everything logged to debug.log will also append to str
std::string str;
@@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(logargs)
BOOST_CHECK(str.find("Command-line arg: okaylog-negbool=false") != std::string::npos);
BOOST_CHECK(str.find("Command-line arg: okaylog=\"public\"") != std::string::npos);
BOOST_CHECK(str.find("dontlog=****") != std::string::npos);
- BOOST_CHECK(str.find("private") == std::string::npos);
+ BOOST_CHECK(str.find("private42") == std::string::npos);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 19f457b8dd..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>
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 696a799872..9bc29e3599 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -9,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>
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/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/streams_tests.cpp b/src/test/streams_tests.cpp
index 0925e2e9ee..b1b262eade 100644
--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
BOOST_CHECK(false);
} catch (const std::exception& e) {
BOOST_CHECK(strstr(e.what(),
- "Read attempted past buffer limit") != nullptr);
+ "Attempt to position past buffer limit") != nullptr);
}
// The default argument removes the limit completely.
BOOST_CHECK(bf.SetLimit());
@@ -322,7 +322,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
BOOST_CHECK(!bf.SetPos(0));
// But we should now be positioned at least as far back as allowed
// by the rewind window (relative to our farthest read position, 40).
- BOOST_CHECK(bf.GetPos() <= 30);
+ BOOST_CHECK(bf.GetPos() <= 30U);
// We can explicitly close the file, or the destructor will do it.
bf.fclose();
@@ -330,6 +330,55 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
fs::remove(streams_test_filename);
}
+BOOST_AUTO_TEST_CASE(streams_buffered_file_skip)
+{
+ fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp";
+ FILE* file = fsbridge::fopen(streams_test_filename, "w+b");
+ // The value at each offset is the byte offset (e.g. byte 1 in the file has the value 0x01).
+ for (uint8_t j = 0; j < 40; ++j) {
+ fwrite(&j, 1, 1, file);
+ }
+ rewind(file);
+
+ // The buffer is 25 bytes, allow rewinding 10 bytes.
+ CBufferedFile bf(file, 25, 10, 222, 333);
+
+ uint8_t i;
+ // This is like bf >> (7-byte-variable), in that it will cause data
+ // to be read from the file into memory, but it's not copied to us.
+ bf.SkipTo(7);
+ BOOST_CHECK_EQUAL(bf.GetPos(), 7U);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 7);
+
+ // The bytes in the buffer up to offset 7 are valid and can be read.
+ BOOST_CHECK(bf.SetPos(0));
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 0);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 1);
+
+ bf.SkipTo(11);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 11);
+
+ // SkipTo() honors the transfer limit; we can't position beyond the limit.
+ bf.SetLimit(13);
+ try {
+ bf.SkipTo(14);
+ BOOST_CHECK(false);
+ } catch (const std::exception& e) {
+ BOOST_CHECK(strstr(e.what(), "Attempt to position past buffer limit") != nullptr);
+ }
+
+ // We can position exactly to the transfer limit.
+ bf.SkipTo(13);
+ BOOST_CHECK_EQUAL(bf.GetPos(), 13U);
+
+ bf.fclose();
+ fs::remove(streams_test_filename);
+}
+
BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
{
// Make this test deterministic.
@@ -361,7 +410,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
// sizes; the boundaries of the objects can interact arbitrarily
// with the CBufferFile's internal buffer. These first three
// cases simulate objects of various sizes (1, 2, 5 bytes).
- switch (InsecureRandRange(5)) {
+ switch (InsecureRandRange(6)) {
case 0: {
uint8_t a[1];
if (currentPos + 1 > fileSize)
@@ -399,6 +448,16 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
break;
}
case 3: {
+ // SkipTo is similar to the "read" cases above, except
+ // we don't receive the data.
+ size_t skip_length{static_cast<size_t>(InsecureRandRange(5))};
+ if (currentPos + skip_length > fileSize) continue;
+ bf.SetLimit(currentPos + skip_length);
+ bf.SkipTo(currentPos + skip_length);
+ currentPos += skip_length;
+ break;
+ }
+ case 4: {
// Find a byte value (that is at or ahead of the current position).
size_t find = currentPos + InsecureRandRange(8);
if (find >= fileSize)
@@ -415,7 +474,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
currentPos++;
break;
}
- case 4: {
+ case 5: {
size_t requestPos = InsecureRandRange(maxPos + 4);
bool okay = bf.SetPos(requestPos);
// The new position may differ from the requested position
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index 11f4be7fef..d5b65b9c08 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -51,15 +51,9 @@ BOOST_AUTO_TEST_CASE(run_command)
}
{
// An invalid command is handled by Boost
-#ifdef WIN32
- const std::string expected{"The system cannot find the file specified."};
-#else
- const std::string expected{"No such file or directory"};
-#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) {
- const std::string what(e.what());
- BOOST_CHECK(what.find("RunCommandParseJSON error:") == std::string::npos);
- BOOST_CHECK(what.find(expected) != std::string::npos);
+ BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos);
+ BOOST_CHECK_EQUAL(e.code().value(), 2);
return true;
});
}
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 2f0021b114..0ca63810f3 100644
--- a/src/test/util/chainstate.h
+++ b/src/test/util/chainstate.h
@@ -11,6 +11,7 @@
#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>
@@ -20,11 +21,24 @@ 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;
@@ -47,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.h b/src/test/util/net.h
index 73543de4ca..9ae7981980 100644
--- a/src/test/util/net.h
+++ b/src/test/util/net.h
@@ -166,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 74b055ee45..bdcff1076b 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -9,6 +9,7 @@
#include <addrman.h>
#include <banman.h>
#include <chainparams.h>
+#include <common/url.h>
#include <consensus/consensus.h>
#include <consensus/params.h>
#include <consensus/validation.h>
@@ -36,6 +37,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>
@@ -45,7 +47,6 @@
#include <util/threadnames.h>
#include <util/time.h>
#include <util/translation.h>
-#include <util/url.h>
#include <util/vector.h>
#include <validation.h>
#include <validationinterface.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 = {
@@ -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 136ee1fd62..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
@@ -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/wallet.cpp b/src/test/util/wallet.cpp
index b54774cbb9..2dadffafb4 100644
--- a/src/test/util/wallet.cpp
+++ b/src/test/util/wallet.cpp
@@ -21,7 +21,12 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq
std::string getnewaddress(CWallet& w)
{
constexpr auto output_type = OutputType::BECH32;
- return EncodeDestination(*Assert(w.GetNewDestination(output_type, "")));
+ return EncodeDestination(getNewDestination(w, output_type));
+}
+
+CTxDestination getNewDestination(CWallet& w, OutputType output_type)
+{
+ return *Assert(w.GetNewDestination(output_type, ""));
}
#endif // ENABLE_WALLET
diff --git a/src/test/util/wallet.h b/src/test/util/wallet.h
index 31281bf70e..d8f1db3fd7 100644
--- a/src/test/util/wallet.h
+++ b/src/test/util/wallet.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_TEST_UTIL_WALLET_H
#define BITCOIN_TEST_UTIL_WALLET_H
+#include <outputtype.h>
#include <string>
namespace wallet {
@@ -19,8 +20,10 @@ extern const std::string ADDRESS_BCRT1_UNSPENDABLE;
/** Import the address to the wallet */
void importaddress(wallet::CWallet& wallet, const std::string& address);
-/** Returns a new address from the wallet */
+/** Returns a new encoded destination from the wallet (hardcoded to BECH32) */
std::string getnewaddress(wallet::CWallet& w);
+/** Returns a new destination, of an specific type, from the wallet */
+CTxDestination getNewDestination(wallet::CWallet& w, OutputType output_type);
#endif // BITCOIN_TEST_UTIL_WALLET_H
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 6e59d2e8e6..602c848c2a 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -9,9 +9,7 @@
#include <hash.h> // For Hash()
#include <key.h> // For CKey
#include <sync.h>
-#include <test/util/logging.h>
#include <test/util/setup_common.h>
-#include <test/util/str.h>
#include <uint256.h>
#include <util/getuniquepath.h>
#include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC
@@ -26,6 +24,7 @@
#include <util/bitdeque.h>
#include <array>
+#include <cmath>
#include <fstream>
#include <limits>
#include <map>
@@ -54,31 +53,6 @@ namespace BCLog {
BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup)
-BOOST_AUTO_TEST_CASE(util_datadir)
-{
- // Use local args variable instead of m_args to avoid making assumptions about test setup
- ArgsManager args;
- args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
-
- const fs::path dd_norm = args.GetDataDirBase();
-
- args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/");
- args.ClearPathCache();
- BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
-
- args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.");
- args.ClearPathCache();
- BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
-
- args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/./");
- args.ClearPathCache();
- BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
-
- args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.//");
- args.ClearPathCache();
- BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
-}
-
namespace {
class NoCopyOrMove
{
@@ -123,6 +97,11 @@ BOOST_AUTO_TEST_CASE(util_check)
// Check nested Asserts
BOOST_CHECK_EQUAL(Assert((Assert(x).test() ? 3 : 0)), 3);
+
+ // Check -Wdangling-gsl does not trigger when copying the int. (It would
+ // trigger on "const int&")
+ const int nine{*Assert(std::optional<int>{9})};
+ BOOST_CHECK_EQUAL(9, nine);
}
BOOST_AUTO_TEST_CASE(util_criticalsection)
@@ -293,1000 +272,6 @@ BOOST_AUTO_TEST_CASE(util_FormatISO8601Date)
BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30");
}
-struct TestArgsManager : public ArgsManager
-{
- TestArgsManager() { m_network_only_args.clear(); }
- void ReadConfigString(const std::string str_config)
- {
- std::istringstream streamConfig(str_config);
- {
- LOCK(cs_args);
- m_settings.ro_config.clear();
- m_config_sections.clear();
- }
- std::string error;
- BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error));
- }
- void SetNetworkOnlyArg(const std::string arg)
- {
- LOCK(cs_args);
- m_network_only_args.insert(arg);
- }
- void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args)
- {
- for (const auto& arg : args) {
- AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS);
- }
- }
- using ArgsManager::GetSetting;
- using ArgsManager::GetSettingsList;
- using ArgsManager::ReadConfigStream;
- using ArgsManager::cs_args;
- using ArgsManager::m_network;
- using ArgsManager::m_settings;
-};
-
-//! Test GetSetting and GetArg type coercion, negation, and default value handling.
-class CheckValueTest : public TestChain100Setup
-{
-public:
- struct Expect {
- util::SettingsValue setting;
- bool default_string = false;
- bool default_int = false;
- bool default_bool = false;
- const char* string_value = nullptr;
- std::optional<int64_t> int_value;
- std::optional<bool> bool_value;
- std::optional<std::vector<std::string>> list_value;
- const char* error = nullptr;
-
- explicit Expect(util::SettingsValue s) : setting(std::move(s)) {}
- Expect& DefaultString() { default_string = true; return *this; }
- Expect& DefaultInt() { default_int = true; return *this; }
- Expect& DefaultBool() { default_bool = true; return *this; }
- Expect& String(const char* s) { string_value = s; return *this; }
- Expect& Int(int64_t i) { int_value = i; return *this; }
- Expect& Bool(bool b) { bool_value = b; return *this; }
- Expect& List(std::vector<std::string> m) { list_value = std::move(m); return *this; }
- Expect& Error(const char* e) { error = e; return *this; }
- };
-
- void CheckValue(unsigned int flags, const char* arg, const Expect& expect)
- {
- TestArgsManager test;
- test.SetupArgs({{"-value", flags}});
- const char* argv[] = {"ignored", arg};
- std::string error;
- bool success = test.ParseParameters(arg ? 2 : 1, (char**)argv, error);
-
- BOOST_CHECK_EQUAL(test.GetSetting("-value").write(), expect.setting.write());
- auto settings_list = test.GetSettingsList("-value");
- if (expect.setting.isNull() || expect.setting.isFalse()) {
- BOOST_CHECK_EQUAL(settings_list.size(), 0U);
- } else {
- BOOST_CHECK_EQUAL(settings_list.size(), 1U);
- BOOST_CHECK_EQUAL(settings_list[0].write(), expect.setting.write());
- }
-
- if (expect.error) {
- BOOST_CHECK(!success);
- BOOST_CHECK_NE(error.find(expect.error), std::string::npos);
- } else {
- BOOST_CHECK(success);
- BOOST_CHECK_EQUAL(error, "");
- }
-
- if (expect.default_string) {
- BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), "zzzzz");
- } else if (expect.string_value) {
- BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), expect.string_value);
- } else {
- BOOST_CHECK(!success);
- }
-
- if (expect.default_int) {
- BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), 99999);
- } else if (expect.int_value) {
- BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), *expect.int_value);
- } else {
- BOOST_CHECK(!success);
- }
-
- if (expect.default_bool) {
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), false);
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), true);
- } else if (expect.bool_value) {
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), *expect.bool_value);
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), *expect.bool_value);
- } else {
- BOOST_CHECK(!success);
- }
-
- if (expect.list_value) {
- auto l = test.GetArgs("-value");
- BOOST_CHECK_EQUAL_COLLECTIONS(l.begin(), l.end(), expect.list_value->begin(), expect.list_value->end());
- } else {
- BOOST_CHECK(!success);
- }
- }
-};
-
-BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest)
-{
- using M = ArgsManager;
-
- CheckValue(M::ALLOW_ANY, nullptr, Expect{{}}.DefaultString().DefaultInt().DefaultBool().List({}));
- CheckValue(M::ALLOW_ANY, "-novalue", Expect{false}.String("0").Int(0).Bool(false).List({}));
- CheckValue(M::ALLOW_ANY, "-novalue=", Expect{false}.String("0").Int(0).Bool(false).List({}));
- CheckValue(M::ALLOW_ANY, "-novalue=0", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
- CheckValue(M::ALLOW_ANY, "-novalue=1", Expect{false}.String("0").Int(0).Bool(false).List({}));
- CheckValue(M::ALLOW_ANY, "-novalue=2", Expect{false}.String("0").Int(0).Bool(false).List({}));
- CheckValue(M::ALLOW_ANY, "-novalue=abc", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
- CheckValue(M::ALLOW_ANY, "-value", Expect{""}.String("").Int(0).Bool(true).List({""}));
- CheckValue(M::ALLOW_ANY, "-value=", Expect{""}.String("").Int(0).Bool(true).List({""}));
- CheckValue(M::ALLOW_ANY, "-value=0", Expect{"0"}.String("0").Int(0).Bool(false).List({"0"}));
- CheckValue(M::ALLOW_ANY, "-value=1", Expect{"1"}.String("1").Int(1).Bool(true).List({"1"}));
- CheckValue(M::ALLOW_ANY, "-value=2", Expect{"2"}.String("2").Int(2).Bool(true).List({"2"}));
- CheckValue(M::ALLOW_ANY, "-value=abc", Expect{"abc"}.String("abc").Int(0).Bool(false).List({"abc"}));
-}
-
-struct NoIncludeConfTest {
- std::string Parse(const char* arg)
- {
- TestArgsManager test;
- test.SetupArgs({{"-includeconf", ArgsManager::ALLOW_ANY}});
- std::array argv{"ignored", arg};
- std::string error;
- (void)test.ParseParameters(argv.size(), argv.data(), error);
- return error;
- }
-};
-
-BOOST_FIXTURE_TEST_CASE(util_NoIncludeConf, NoIncludeConfTest)
-{
- BOOST_CHECK_EQUAL(Parse("-noincludeconf"), "");
- BOOST_CHECK_EQUAL(Parse("-includeconf"), "-includeconf cannot be used from commandline; -includeconf=\"\"");
- BOOST_CHECK_EQUAL(Parse("-includeconf=file"), "-includeconf cannot be used from commandline; -includeconf=\"file\"");
-}
-
-BOOST_AUTO_TEST_CASE(util_ParseParameters)
-{
- TestArgsManager testArgs;
- const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
- const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
- const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
- const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
-
- const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"};
-
- std::string error;
- LOCK(testArgs.cs_args);
- testArgs.SetupArgs({a, b, ccc, d});
- BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error));
- BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
-
- BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error));
- BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
-
- BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error));
- // expectation: -ignored is ignored (program name argument),
- // -a, -b and -ccc end up in map, -d ignored because it is after
- // a non-option argument (non-GNU option parsing)
- BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty());
- BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc")
- && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d"));
- BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc")
- && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d"));
-
- BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1);
- BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == "");
- BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2);
- BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument");
- BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple");
- BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2);
-}
-
-BOOST_AUTO_TEST_CASE(util_ParseInvalidParameters)
-{
- TestArgsManager test;
- test.SetupArgs({{"-registered", ArgsManager::ALLOW_ANY}});
-
- const char* argv[] = {"ignored", "-registered"};
- std::string error;
- BOOST_CHECK(test.ParseParameters(2, (char**)argv, error));
- BOOST_CHECK_EQUAL(error, "");
-
- argv[1] = "-unregistered";
- BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error));
- BOOST_CHECK_EQUAL(error, "Invalid parameter -unregistered");
-
- // Make sure registered parameters prefixed with a chain name trigger errors.
- // (Previously, they were accepted and ignored.)
- argv[1] = "-test.registered";
- BOOST_CHECK(!test.ParseParameters(2, (char**)argv, error));
- BOOST_CHECK_EQUAL(error, "Invalid parameter -test.registered");
-}
-
-static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int)
-{
- TestArgsManager test;
- test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}});
- std::string arg = "-value=" + str;
- const char* argv[] = {"ignored", arg.c_str()};
- std::string error;
- BOOST_CHECK(test.ParseParameters(2, (char**)argv, error));
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool);
- BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool);
- BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99998), expected_int);
- BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), expected_int);
-}
-
-// Test bool and int parsing.
-BOOST_AUTO_TEST_CASE(util_ArgParsing)
-{
- // Some of these cases could be ambiguous or surprising to users, and might
- // be worth triggering errors or warnings in the future. But for now basic
- // test coverage is useful to avoid breaking backwards compatibility
- // unintentionally.
- TestParse("", true, 0);
- TestParse(" ", false, 0);
- TestParse("0", false, 0);
- TestParse("0 ", false, 0);
- TestParse(" 0", false, 0);
- TestParse("+0", false, 0);
- TestParse("-0", false, 0);
- TestParse("5", true, 5);
- TestParse("5 ", true, 5);
- TestParse(" 5", true, 5);
- TestParse("+5", true, 5);
- TestParse("-5", true, -5);
- TestParse("0 5", false, 0);
- TestParse("5 0", true, 5);
- TestParse("050", true, 50);
- TestParse("0.", false, 0);
- TestParse("5.", true, 5);
- TestParse("0.0", false, 0);
- TestParse("0.5", false, 0);
- TestParse("5.0", true, 5);
- TestParse("5.5", true, 5);
- TestParse("x", false, 0);
- TestParse("x0", false, 0);
- TestParse("x5", false, 0);
- TestParse("0x", false, 0);
- TestParse("5x", true, 5);
- TestParse("0x5", false, 0);
- TestParse("false", false, 0);
- TestParse("true", false, 0);
- TestParse("yes", false, 0);
- TestParse("no", false, 0);
-}
-
-BOOST_AUTO_TEST_CASE(util_GetBoolArg)
-{
- TestArgsManager testArgs;
- const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
- const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
- const auto c = std::make_pair("-c", ArgsManager::ALLOW_ANY);
- const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
- const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
- const auto f = std::make_pair("-f", ArgsManager::ALLOW_ANY);
-
- const char *argv_test[] = {
- "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"};
- std::string error;
- LOCK(testArgs.cs_args);
- testArgs.SetupArgs({a, b, c, d, e, f});
- BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error));
-
- // Each letter should be set.
- for (const char opt : "abcdef")
- BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt);
-
- // Nothing else should be in the map
- BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 &&
- testArgs.m_settings.ro_config.empty());
-
- // The -no prefix should get stripped on the way in.
- BOOST_CHECK(!testArgs.IsArgSet("-nob"));
-
- // The -b option is flagged as negated, and nothing else is
- BOOST_CHECK(testArgs.IsArgNegated("-b"));
- BOOST_CHECK(!testArgs.IsArgNegated("-a"));
-
- // Check expected values.
- BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true);
- BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false);
- BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false);
- BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true);
- BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false);
- BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false);
-}
-
-BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
-{
- // Test some awful edge cases that hopefully no user will ever exercise.
- TestArgsManager testArgs;
-
- // Params test
- const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY);
- const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY);
- const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"};
- testArgs.SetupArgs({foo, bar});
- std::string error;
- BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error));
-
- // This was passed twice, second one overrides the negative setting.
- BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
- BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "");
-
- // A double negative is a positive, and not marked as negated.
- BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
- BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
-
- // Config test
- const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n";
- BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error));
- testArgs.ReadConfigString(conf_test);
-
- // This was passed twice, second one overrides the negative setting,
- // and the value.
- BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
- BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1");
-
- // A double negative is a positive, and does not count as negated.
- BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
- BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
-
- // Combined test
- const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"};
- const char *combo_test_conf = "foo=1\nnobar=1\n";
- BOOST_CHECK(testArgs.ParseParameters(3, (char**)combo_test_args, error));
- testArgs.ReadConfigString(combo_test_conf);
-
- // Command line overrides, but doesn't erase old setting
- BOOST_CHECK(testArgs.IsArgNegated("-foo"));
- BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0");
- BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0);
-
- // Command line overrides, but doesn't erase old setting
- BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
- BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "");
- BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1
- && testArgs.GetArgs("-bar").front() == "");
-}
-
-BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
-{
- const char *str_config =
- "a=\n"
- "b=1\n"
- "ccc=argument\n"
- "ccc=multiple\n"
- "d=e\n"
- "nofff=1\n"
- "noggg=0\n"
- "h=1\n"
- "noh=1\n"
- "noi=1\n"
- "i=1\n"
- "sec1.ccc=extend1\n"
- "\n"
- "[sec1]\n"
- "ccc=extend2\n"
- "d=eee\n"
- "h=1\n"
- "[sec2]\n"
- "ccc=extend3\n"
- "iii=2\n";
-
- TestArgsManager test_args;
- LOCK(test_args.cs_args);
- const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
- const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
- const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
- const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
- const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
- const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_ANY);
- const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_ANY);
- const auto h = std::make_pair("-h", ArgsManager::ALLOW_ANY);
- const auto i = std::make_pair("-i", ArgsManager::ALLOW_ANY);
- const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_ANY);
- test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii});
-
- test_args.ReadConfigString(str_config);
- // expectation: a, b, ccc, d, fff, ggg, h, i end up in map
- // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii
-
- BOOST_CHECK(test_args.m_settings.command_line_options.empty());
- BOOST_CHECK(test_args.m_settings.ro_config.size() == 3);
- BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8);
- BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3);
- BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2);
-
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("a"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("b"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("d"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("h"));
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("i"));
- BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc"));
- BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h"));
- BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc"));
- BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii"));
-
- BOOST_CHECK(test_args.IsArgSet("-a"));
- BOOST_CHECK(test_args.IsArgSet("-b"));
- BOOST_CHECK(test_args.IsArgSet("-ccc"));
- BOOST_CHECK(test_args.IsArgSet("-d"));
- BOOST_CHECK(test_args.IsArgSet("-fff"));
- BOOST_CHECK(test_args.IsArgSet("-ggg"));
- BOOST_CHECK(test_args.IsArgSet("-h"));
- BOOST_CHECK(test_args.IsArgSet("-i"));
- BOOST_CHECK(!test_args.IsArgSet("-zzz"));
- BOOST_CHECK(!test_args.IsArgSet("-iii"));
-
- BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
- BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
- BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument");
- BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e");
- BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
- BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
- BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0");
- BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1");
- BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
- BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
-
- for (const bool def : {false, true}) {
- BOOST_CHECK(test_args.GetBoolArg("-a", def));
- BOOST_CHECK(test_args.GetBoolArg("-b", def));
- BOOST_CHECK(!test_args.GetBoolArg("-ccc", def));
- BOOST_CHECK(!test_args.GetBoolArg("-d", def));
- BOOST_CHECK(!test_args.GetBoolArg("-fff", def));
- BOOST_CHECK(test_args.GetBoolArg("-ggg", def));
- BOOST_CHECK(!test_args.GetBoolArg("-h", def));
- BOOST_CHECK(test_args.GetBoolArg("-i", def));
- BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def);
- BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def);
- }
-
- BOOST_CHECK(test_args.GetArgs("-a").size() == 1
- && test_args.GetArgs("-a").front() == "");
- BOOST_CHECK(test_args.GetArgs("-b").size() == 1
- && test_args.GetArgs("-b").front() == "1");
- BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2
- && test_args.GetArgs("-ccc").front() == "argument"
- && test_args.GetArgs("-ccc").back() == "multiple");
- BOOST_CHECK(test_args.GetArgs("-fff").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1
- && test_args.GetArgs("-ggg").front() == "1");
- BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-h").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-noh").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-i").size() == 1
- && test_args.GetArgs("-i").front() == "1");
- BOOST_CHECK(test_args.GetArgs("-noi").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0);
-
- BOOST_CHECK(!test_args.IsArgNegated("-a"));
- BOOST_CHECK(!test_args.IsArgNegated("-b"));
- BOOST_CHECK(!test_args.IsArgNegated("-ccc"));
- BOOST_CHECK(!test_args.IsArgNegated("-d"));
- BOOST_CHECK(test_args.IsArgNegated("-fff"));
- BOOST_CHECK(!test_args.IsArgNegated("-ggg"));
- BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence
- BOOST_CHECK(!test_args.IsArgNegated("-i")); // last setting takes precedence
- BOOST_CHECK(!test_args.IsArgNegated("-zzz"));
-
- // Test sections work
- test_args.SelectConfigNetwork("sec1");
-
- // same as original
- BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
- BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
- BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
- BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
- BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
- BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
- // d is overridden
- BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
- // section-specific setting
- BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
- // section takes priority for multiple values
- BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1");
- // check multiple values works
- const std::vector<std::string> sec1_ccc_expected = {"extend1","extend2","argument","multiple"};
- const auto& sec1_ccc_res = test_args.GetArgs("-ccc");
- BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end());
-
- test_args.SelectConfigNetwork("sec2");
-
- // same as original
- BOOST_CHECK(test_args.GetArg("-a", "xxx") == "");
- BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1");
- BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
- BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0");
- BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1");
- BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx");
- BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
- // section-specific setting
- BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2");
- // section takes priority for multiple values
- BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3");
- // check multiple values works
- const std::vector<std::string> sec2_ccc_expected = {"extend3","argument","multiple"};
- const auto& sec2_ccc_res = test_args.GetArgs("-ccc");
- BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end());
-
- // Test section only options
-
- test_args.SetNetworkOnlyArg("-d");
- test_args.SetNetworkOnlyArg("-ccc");
- test_args.SetNetworkOnlyArg("-h");
-
- test_args.SelectConfigNetwork(CBaseChainParams::MAIN);
- BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
- BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
- BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
-
- test_args.SelectConfigNetwork("sec1");
- BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
- BOOST_CHECK(test_args.GetArgs("-d").size() == 1);
- BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
- BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
-
- test_args.SelectConfigNetwork("sec2");
- BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx");
- BOOST_CHECK(test_args.GetArgs("-d").size() == 0);
- BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1);
- BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
-}
-
-BOOST_AUTO_TEST_CASE(util_GetArg)
-{
- TestArgsManager testArgs;
- LOCK(testArgs.cs_args);
- testArgs.m_settings.command_line_options.clear();
- testArgs.m_settings.command_line_options["strtest1"] = {"string..."};
- // strtest2 undefined on purpose
- testArgs.m_settings.command_line_options["inttest1"] = {"12345"};
- testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"};
- // inttest3 undefined on purpose
- testArgs.m_settings.command_line_options["booltest1"] = {""};
- // booltest2 undefined on purpose
- testArgs.m_settings.command_line_options["booltest3"] = {"0"};
- testArgs.m_settings.command_line_options["booltest4"] = {"1"};
-
- // priorities
- testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"};
- testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"};
- testArgs.m_settings.command_line_options["pritest3"] = {"a"};
- testArgs.m_settings.ro_config[""]["pritest3"] = {"b"};
- testArgs.m_settings.command_line_options["pritest4"] = {"a","b"};
- testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"};
-
- BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string...");
- BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default");
- BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest1", -1), 12345);
- BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest2", -1), 81985529216486895LL);
- BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest3", -1), -1);
- BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true);
- BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false);
- BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false);
- BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true);
-
- BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b");
- BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a");
- BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a");
- BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b");
-}
-
-BOOST_AUTO_TEST_CASE(util_GetChainName)
-{
- TestArgsManager test_args;
- const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY);
- const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY);
- test_args.SetupArgs({testnet, regtest});
-
- const char* argv_testnet[] = {"cmd", "-testnet"};
- const char* argv_regtest[] = {"cmd", "-regtest"};
- const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"};
- const char* argv_both[] = {"cmd", "-testnet", "-regtest"};
-
- // equivalent to "-testnet"
- // regtest in testnet section is ignored
- const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1";
- std::string error;
-
- BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "main");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest");
-
- BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error));
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
- BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
-
- BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
-
- BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
-
- // check setting the network to test (and thus making
- // [test] regtest=1 potentially relevant) doesn't break things
- test_args.SelectConfigNetwork("test");
-
- BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
-
- BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_test_no_reg, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
-
- BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error));
- test_args.ReadConfigString(testnetconf);
- BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
-}
-
-// Test different ways settings can be merged, and verify results. This test can
-// be used to confirm that updates to settings code don't change behavior
-// unintentionally.
-//
-// The test covers:
-//
-// - Combining different setting actions. Possible actions are: configuring a
-// setting, negating a setting (adding "-no" prefix), and configuring/negating
-// settings in a network section (adding "main." or "test." prefixes).
-//
-// - Combining settings from command line arguments and a config file.
-//
-// - Combining SoftSet and ForceSet calls.
-//
-// - Testing "main" and "test" network values to make sure settings from network
-// sections are applied and to check for mainnet-specific behaviors like
-// inheriting settings from the default section.
-//
-// - Testing network-specific settings like "-wallet", that may be ignored
-// outside a network section, and non-network specific settings like "-server"
-// that aren't sensitive to the network.
-//
-struct ArgsMergeTestingSetup : public BasicTestingSetup {
- //! Max number of actions to sequence together. Can decrease this when
- //! debugging to make test results easier to understand.
- static constexpr int MAX_ACTIONS = 3;
-
- enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
- using ActionList = Action[MAX_ACTIONS];
-
- //! Enumerate all possible test configurations.
- template <typename Fn>
- void ForEachMergeSetup(Fn&& fn)
- {
- ActionList arg_actions = {};
- // command_line_options do not have sections. Only iterate over SET and NEGATE
- ForEachNoDup(arg_actions, SET, NEGATE, [&] {
- ActionList conf_actions = {};
- ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
- for (bool soft_set : {false, true}) {
- for (bool force_set : {false, true}) {
- for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
- for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) {
- for (bool net_specific : {false, true}) {
- fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
- }
- }
- }
- }
- }
- });
- });
- }
-
- //! Translate actions into a list of <key>=<value> setting strings.
- std::vector<std::string> GetValues(const ActionList& actions,
- const std::string& section,
- const std::string& name,
- const std::string& value_prefix)
- {
- std::vector<std::string> values;
- int suffix = 0;
- for (Action action : actions) {
- if (action == NONE) break;
- std::string prefix;
- if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + ".";
- if (action == SET || action == SECTION_SET) {
- for (int i = 0; i < 2; ++i) {
- values.push_back(prefix + name + "=" + value_prefix + ToString(++suffix));
- }
- }
- if (action == NEGATE || action == SECTION_NEGATE) {
- values.push_back(prefix + "no" + name + "=1");
- }
- }
- return values;
- }
-};
-
-// Regression test covering different ways config settings can be merged. The
-// test parses and merges settings, representing the results as strings that get
-// compared against an expected hash. To debug, the result strings can be dumped
-// to a file (see comments below).
-BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
-{
- CHash256 out_sha;
- FILE* out_file = nullptr;
- if (const char* out_path = getenv("ARGS_MERGE_TEST_OUT")) {
- out_file = fsbridge::fopen(out_path, "w");
- if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
- }
-
- ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set,
- const std::string& section, const std::string& network, bool net_specific) {
- TestArgsManager parser;
- LOCK(parser.cs_args);
-
- std::string desc = "net=";
- desc += network;
- parser.m_network = network;
-
- const std::string& name = net_specific ? "wallet" : "server";
- const std::string key = "-" + name;
- parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- if (net_specific) parser.SetNetworkOnlyArg(key);
-
- auto args = GetValues(arg_actions, section, name, "a");
- std::vector<const char*> argv = {"ignored"};
- for (auto& arg : args) {
- arg.insert(0, "-");
- desc += " ";
- desc += arg;
- argv.push_back(arg.c_str());
- }
- std::string error;
- BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
- BOOST_CHECK_EQUAL(error, "");
-
- std::string conf;
- for (auto& conf_val : GetValues(conf_actions, section, name, "c")) {
- desc += " ";
- desc += conf_val;
- conf += conf_val;
- conf += "\n";
- }
- std::istringstream conf_stream(conf);
- BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
- BOOST_CHECK_EQUAL(error, "");
-
- if (soft_set) {
- desc += " soft";
- parser.SoftSetArg(key, "soft1");
- parser.SoftSetArg(key, "soft2");
- }
-
- if (force_set) {
- desc += " force";
- parser.ForceSetArg(key, "force1");
- parser.ForceSetArg(key, "force2");
- }
-
- desc += " || ";
-
- if (!parser.IsArgSet(key)) {
- desc += "unset";
- BOOST_CHECK(!parser.IsArgNegated(key));
- BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default");
- BOOST_CHECK(parser.GetArgs(key).empty());
- } else if (parser.IsArgNegated(key)) {
- desc += "negated";
- BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0");
- BOOST_CHECK(parser.GetArgs(key).empty());
- } else {
- desc += parser.GetArg(key, "default");
- desc += " |";
- for (const auto& arg : parser.GetArgs(key)) {
- desc += " ";
- desc += arg;
- }
- }
-
- std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs();
- if (!ignored.empty()) {
- desc += " | ignored";
- for (const auto& arg : ignored) {
- desc += " ";
- desc += arg;
- }
- }
-
- desc += "\n";
-
- out_sha.Write(MakeUCharSpan(desc));
- if (out_file) {
- BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
- }
- });
-
- if (out_file) {
- if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
- out_file = nullptr;
- }
-
- unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
- out_sha.Finalize(out_sha_bytes);
- std::string out_sha_hex = HexStr(out_sha_bytes);
-
- // If check below fails, should manually dump the results with:
- //
- // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge
- //
- // And verify diff against previous results to make sure the changes are expected.
- //
- // Results file is formatted like:
- //
- // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
- BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82");
-}
-
-// Similar test as above, but for ArgsManager::GetChainName function.
-struct ChainMergeTestingSetup : public BasicTestingSetup {
- static constexpr int MAX_ACTIONS = 2;
-
- enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG };
- using ActionList = Action[MAX_ACTIONS];
-
- //! Enumerate all possible test configurations.
- template <typename Fn>
- void ForEachMergeSetup(Fn&& fn)
- {
- ActionList arg_actions = {};
- ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] {
- ActionList conf_actions = {};
- ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); });
- });
- }
-};
-
-BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
-{
- CHash256 out_sha;
- FILE* out_file = nullptr;
- if (const char* out_path = getenv("CHAIN_MERGE_TEST_OUT")) {
- out_file = fsbridge::fopen(out_path, "w");
- if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
- }
-
- ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) {
- TestArgsManager parser;
- LOCK(parser.cs_args);
- parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
-
- auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" :
- action == DISABLE_TEST ? "-testnet=0" :
- action == NEGATE_TEST ? "-notestnet=1" :
- action == ENABLE_REG ? "-regtest=1" :
- action == DISABLE_REG ? "-regtest=0" :
- action == NEGATE_REG ? "-noregtest=1" : nullptr; };
-
- std::string desc;
- std::vector<const char*> argv = {"ignored"};
- for (Action action : arg_actions) {
- const char* argstr = arg(action);
- if (!argstr) break;
- argv.push_back(argstr);
- desc += " ";
- desc += argv.back();
- }
- std::string error;
- BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
- BOOST_CHECK_EQUAL(error, "");
-
- std::string conf;
- for (Action action : conf_actions) {
- const char* argstr = arg(action);
- if (!argstr) break;
- desc += " ";
- desc += argstr + 1;
- conf += argstr + 1;
- conf += "\n";
- }
- std::istringstream conf_stream(conf);
- BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
- BOOST_CHECK_EQUAL(error, "");
-
- desc += " || ";
- try {
- desc += parser.GetChainName();
- } catch (const std::runtime_error& e) {
- desc += "error: ";
- desc += e.what();
- }
- desc += "\n";
-
- out_sha.Write(MakeUCharSpan(desc));
- if (out_file) {
- BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
- }
- });
-
- if (out_file) {
- if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
- out_file = nullptr;
- }
-
- unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
- out_sha.Finalize(out_sha_bytes);
- std::string out_sha_hex = HexStr(out_sha_bytes);
-
- // If check below fails, should manually dump the results with:
- //
- // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge
- //
- // And verify diff against previous results to make sure the changes are expected.
- //
- // Results file is formatted like:
- //
- // <input> || <output>
- BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c");
-}
-
-BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
-{
- // Test writing setting.
- TestArgsManager args1;
- args1.ForceSetArg("-datadir", fs::PathToString(m_path_root));
- args1.LockSettings([&](util::Settings& settings) { settings.rw_settings["name"] = "value"; });
- args1.WriteSettingsFile();
-
- // Test reading setting.
- TestArgsManager args2;
- args2.ForceSetArg("-datadir", fs::PathToString(m_path_root));
- args2.ReadSettingsFile();
- args2.LockSettings([&](util::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); });
-
- // Test error logging, and remove previously written setting.
- {
- ASSERT_DEBUG_LOG("Failed renaming settings file");
- fs::remove(args1.GetDataDirBase() / "settings.json");
- fs::create_directory(args1.GetDataDirBase() / "settings.json");
- args2.WriteSettingsFile();
- fs::remove(args1.GetDataDirBase() / "settings.json");
- }
-}
-
BOOST_AUTO_TEST_CASE(util_FormatMoney)
{
BOOST_CHECK_EQUAL(FormatMoney(0), "0.00");
diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp
index 347a967b33..f868c0d4e6 100644
--- a/src/test/validation_chainstate_tests.cpp
+++ b/src/test/validation_chainstate_tests.cpp
@@ -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()));
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 9fcb7d315a..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>
@@ -63,7 +64,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a snapshot-based chainstate.
//
const uint256 snapshot_blockhash = GetRandHash();
- Chainstate& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(
+ Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
&mempool, snapshot_blockhash));
chainstates.push_back(&c2);
@@ -133,7 +134,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
// Create a snapshot-based chainstate.
//
- Chainstate& 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,162 +155,240 @@ 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 (Chainstate* 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 (Chainstate* 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.
@@ -374,7 +453,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
Chainstate& cs2 = WITH_LOCK(::cs_main,
- return chainman.InitializeChainstate(&mempool, GetRandHash()));
+ 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/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 84ed2e9ef5..6a4cd842fb 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -1182,3 +1182,17 @@ void CTxMemPool::SetLoadTried(bool load_tried)
LOCK(cs);
m_load_tried = load_tried;
}
+
+
+const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept
+{
+ switch (r) {
+ case MemPoolRemovalReason::EXPIRY: return "expiry";
+ case MemPoolRemovalReason::SIZELIMIT: return "sizelimit";
+ case MemPoolRemovalReason::REORG: return "reorg";
+ case MemPoolRemovalReason::BLOCK: return "block";
+ case MemPoolRemovalReason::CONFLICT: return "conflict";
+ case MemPoolRemovalReason::REPLACED: return "replaced";
+ }
+ assert(false);
+}
diff --git a/src/txmempool.h b/src/txmempool.h
index 4afaac0506..50d9a8236b 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -355,6 +355,8 @@ enum class MemPoolRemovalReason {
REPLACED, //!< Removed for replacement
};
+const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept;
+
/**
* CTxMemPool stores valid-according-to-the-current-best-chain transactions
* that may be included in the next block.
diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h
index 850d0a1cbc..1af7df079e 100644
--- a/src/univalue/include/univalue.h
+++ b/src/univalue/include/univalue.h
@@ -25,10 +25,7 @@ public:
};
UniValue() { typ = VNULL; }
- UniValue(UniValue::VType initialType, const std::string& initialStr = "") {
- typ = initialType;
- val = initialStr;
- }
+ UniValue(UniValue::VType type, std::string str = {}) : typ{type}, val{std::move(str)} {}
template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>,
std::enable_if_t<std::is_floating_point_v<T> || // setFloat
std::is_same_v<bool, T> || // setBool
@@ -54,12 +51,12 @@ public:
void setNull();
void setBool(bool val);
- void setNumStr(const std::string& val);
+ void setNumStr(std::string str);
void setInt(uint64_t val);
void setInt(int64_t val);
void setInt(int val_) { return setInt(int64_t{val_}); }
void setFloat(double val);
- void setStr(const std::string& val);
+ void setStr(std::string str);
void setArray();
void setObject();
diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp
index 4448981d3e..5aa39edb75 100644
--- a/src/univalue/lib/univalue.cpp
+++ b/src/univalue/lib/univalue.cpp
@@ -44,15 +44,15 @@ static bool validNumStr(const std::string& s)
return (tt == JTOK_NUMBER);
}
-void UniValue::setNumStr(const std::string& val_)
+void UniValue::setNumStr(std::string str)
{
- if (!validNumStr(val_)) {
- throw std::runtime_error{"The string '" + val_ + "' is not a valid JSON number"};
+ if (!validNumStr(str)) {
+ throw std::runtime_error{"The string '" + str + "' is not a valid JSON number"};
}
clear();
typ = VNUM;
- val = val_;
+ val = std::move(str);
}
void UniValue::setInt(uint64_t val_)
@@ -82,11 +82,11 @@ void UniValue::setFloat(double val_)
return setNumStr(oss.str());
}
-void UniValue::setStr(const std::string& val_)
+void UniValue::setStr(std::string str)
{
clear();
typ = VSTR;
- val = val_;
+ val = std::move(str);
}
void UniValue::setArray()
diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp
index 94d7343ff3..65e82543e4 100644
--- a/src/univalue/test/object.cpp
+++ b/src/univalue/test/object.cpp
@@ -11,6 +11,7 @@
#include <memory>
#include <stdexcept>
#include <string>
+#include <string_view>
#include <vector>
#define BOOST_CHECK(expr) assert(expr)
@@ -160,6 +161,14 @@ void univalue_set()
BOOST_CHECK(v.isStr());
BOOST_CHECK_EQUAL(v.getValStr(), "zum");
+ {
+ std::string_view sv{"ab\0c", 4};
+ UniValue j{sv};
+ BOOST_CHECK(j.isStr());
+ BOOST_CHECK_EQUAL(j.getValStr(), sv);
+ BOOST_CHECK_EQUAL(j.write(), "\"ab\\u0000c\"");
+ }
+
v.setFloat(-1.01);
BOOST_CHECK(v.isNum());
BOOST_CHECK_EQUAL(v.getValStr(), "-1.01");
diff --git a/src/util/check.cpp b/src/util/check.cpp
index 2a9f885560..e50d6087d0 100644
--- a/src/util/check.cpp
+++ b/src/util/check.cpp
@@ -4,8 +4,23 @@
#include <util/check.h>
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <tinyformat.h>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+
+NonFatalCheckError::NonFatalCheckError(const char* msg, const char* file, int line, const char* func)
+ : std::runtime_error{
+ strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", msg, file, line, func, PACKAGE_BUGREPORT)}
+{
+}
+
void assertion_fail(const char* file, int line, const char* func, const char* assertion)
{
auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", file, line, func, assertion);
diff --git a/src/util/check.h b/src/util/check.h
index aca957925a..b6c03bed2a 100644
--- a/src/util/check.h
+++ b/src/util/check.h
@@ -5,30 +5,23 @@
#ifndef BITCOIN_UTIL_CHECK_H
#define BITCOIN_UTIL_CHECK_H
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
-
-#include <tinyformat.h>
+#include <attributes.h>
#include <stdexcept>
+#include <utility>
class NonFatalCheckError : public std::runtime_error
{
- using std::runtime_error::runtime_error;
+public:
+ NonFatalCheckError(const char* msg, const char* file, int line, const char* func);
};
-#define format_internal_error(msg, file, line, func, report) \
- strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", \
- msg, file, line, func, report)
-
/** Helper for CHECK_NONFATAL() */
template <typename T>
-T&& inline_check_non_fatal(T&& val, const char* file, int line, const char* func, const char* assertion)
+T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const char* file, int line, const char* func, const char* assertion)
{
- if (!(val)) {
- throw NonFatalCheckError(
- format_internal_error(assertion, file, line, func, PACKAGE_BUGREPORT));
+ if (!val) {
+ throw NonFatalCheckError{assertion, file, line, func};
}
return std::forward<T>(val);
}
@@ -56,7 +49,7 @@ void assertion_fail(const char* file, int line, const char* func, const char* as
/** Helper for Assert()/Assume() */
template <bool IS_ASSERT, typename T>
-T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
+T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
{
if constexpr (IS_ASSERT
#ifdef ABORT_ON_FAILED_ASSUME
@@ -87,11 +80,9 @@ T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_u
/**
* NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError.
- * This is used to mark code that is not yet implemented or is not yet reachable.
*/
#define NONFATAL_UNREACHABLE() \
throw NonFatalCheckError( \
- format_internal_error("Unreachable code reached (non-fatal)", \
- __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT))
+ "Unreachable code reached (non-fatal)", __FILE__, __LINE__, __func__)
#endif // BITCOIN_UTIL_CHECK_H
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 ce5d846eb9..d7a0793ea8 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -922,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);
@@ -932,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) {
diff --git a/src/validation.cpp b/src/validation.cpp
index 514a528314..6967ad24c2 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -120,13 +120,6 @@ 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* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const
{
@@ -1414,7 +1407,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
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()};
@@ -1444,7 +1437,7 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
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) {
@@ -1502,7 +1495,6 @@ Chainstate::Chainstate(
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) {}
@@ -1513,7 +1505,7 @@ void Chainstate::InitCoinsDB(
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>(
@@ -1546,10 +1538,12 @@ bool Chainstate::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;
@@ -1838,11 +1832,21 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn
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.
@@ -1852,7 +1856,9 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn
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
+ }
}
}
}
@@ -1983,8 +1989,10 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
uint256 block_hash{block.GetHash()};
assert(*pindex->phashBlock == block_hash);
+ const bool parallel_script_checks{scriptcheckqueue.HasThreads()};
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
@@ -1999,7 +2007,7 @@ bool Chainstate::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
@@ -2017,24 +2025,24 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
// 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
@@ -2047,9 +2055,9 @@ bool Chainstate::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);
}
}
}
@@ -2130,9 +2138,9 @@ bool Chainstate::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
@@ -2171,7 +2179,7 @@ bool Chainstate::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;
@@ -2230,7 +2238,7 @@ bool Chainstate::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());
@@ -2254,7 +2262,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
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");
@@ -2275,7 +2283,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
if (fJustCheck)
return true;
- if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, m_params)) {
+ if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, params)) {
return false;
}
@@ -2394,7 +2402,7 @@ bool Chainstate::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()) {
@@ -2548,13 +2556,15 @@ 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;
}
@@ -2575,7 +2585,7 @@ void Chainstate::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) {
@@ -2586,7 +2596,7 @@ void Chainstate::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.
@@ -2610,7 +2620,7 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra
// 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.
@@ -2727,7 +2737,7 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
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;
@@ -3518,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().
@@ -3666,12 +3676,12 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
CBlockIndex* pindexPrev = nullptr;
BlockMap::iterator mi{m_blockman.m_block_index.find(block.hashPrevBlock)};
if (mi == m_blockman.m_block_index.end()) {
- LogPrint(BCLog::VALIDATION, "%s: %s prev block not found\n", __func__, hash.ToString());
+ LogPrint(BCLog::VALIDATION, "header %s has prev block not found: %s\n", hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found");
}
pindexPrev = &((*mi).second);
if (pindexPrev->nStatus & BLOCK_FAILED_MASK) {
- LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString());
+ LogPrint(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk");
}
if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_options.adjusted_time_callback())) {
@@ -3712,7 +3722,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
m_blockman.m_dirty_blockindex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
- LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString());
+ LogPrint(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk");
}
}
@@ -3832,10 +3842,12 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
// 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;
@@ -3852,7 +3864,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
// 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;
@@ -3996,7 +4008,7 @@ bool Chainstate::LoadChainTip()
tip->GetBlockHash().ToString(),
m_chain.Height(),
FormatISO8601DateTime(tip->GetBlockTime()),
- GuessVerificationProgress(m_params.TxData(), tip));
+ GuessVerificationProgress(m_chainman.GetParams().TxData(), tip));
return true;
}
@@ -4132,7 +4144,7 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in
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());
}
@@ -4184,7 +4196,7 @@ bool Chainstate::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);
@@ -4336,16 +4348,18 @@ 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__);
}
@@ -4369,11 +4383,14 @@ void Chainstate::LoadExternalBlockFile(
assert(!dbp == !blocks_with_unknown_parent);
const auto start{SteadyClock::now()};
+ const CChainParams& params{m_chainman.GetParams()};
int nLoaded = 0;
try {
// This takes over fileIn and calls fclose() on it in the CBufferedFile destructor
CBufferedFile blkdat(fileIn, 2*MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE+8, SER_DISK, CLIENT_VERSION);
+ // nRewind indicates where to resume scanning in case something goes wrong,
+ // such as a block fails to deserialize.
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
if (ShutdownRequested()) return;
@@ -4385,10 +4402,10 @@ void Chainstate::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
@@ -4397,28 +4414,30 @@ void Chainstate::LoadExternalBlockFile(
continue;
} catch (const std::exception&) {
// no valid block header found; don't complain
+ // (this happens at the end of every blk.dat file)
break;
}
try {
- // read block
- uint64_t nBlockPos = blkdat.GetPos();
+ // read block header
+ const uint64_t nBlockPos{blkdat.GetPos()};
if (dbp)
dbp->nPos = nBlockPos;
blkdat.SetLimit(nBlockPos + nSize);
- std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
- CBlock& block = *pblock;
- blkdat >> block;
- nRewind = blkdat.GetPos();
-
- uint256 hash = block.GetHash();
+ CBlockHeader header;
+ blkdat >> header;
+ const uint256 hash{header.GetHash()};
+ // Skip the rest of this block (this may read from disk into memory); position to the marker before the
+ // next block, but it's still possible to rewind to the start of the current block (without a disk read).
+ nRewind = nBlockPos + nSize;
+ blkdat.SkipTo(nRewind);
{
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(header.hashPrevBlock)) {
LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(),
- block.hashPrevBlock.ToString());
+ header.hashPrevBlock.ToString());
if (dbp && blocks_with_unknown_parent) {
- blocks_with_unknown_parent->emplace(block.hashPrevBlock, *dbp);
+ blocks_with_unknown_parent->emplace(header.hashPrevBlock, *dbp);
}
continue;
}
@@ -4426,20 +4445,26 @@ void Chainstate::LoadExternalBlockFile(
// process in case the block isn't known yet
const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) {
- BlockValidationState state;
- if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) {
- nLoaded++;
- }
- if (state.IsError()) {
- break;
- }
- } else if (hash != m_params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
+ // This block can be processed immediately; rewind to its start, read and deserialize it.
+ blkdat.SetPos(nBlockPos);
+ std::shared_ptr<CBlock> pblock{std::make_shared<CBlock>()};
+ blkdat >> *pblock;
+ nRewind = blkdat.GetPos();
+
+ BlockValidationState state;
+ if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) {
+ nLoaded++;
+ }
+ if (state.IsError()) {
+ break;
+ }
+ } 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;
@@ -4460,7 +4485,7 @@ void Chainstate::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);
@@ -4476,7 +4501,18 @@ void Chainstate::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) {
@@ -4487,7 +4523,7 @@ void Chainstate::LoadExternalBlockFile(
void Chainstate::CheckBlockIndex()
{
- if (!fCheckBlockIndex) {
+ if (!m_chainman.ShouldCheckBlockIndex()) {
return;
}
@@ -4560,7 +4596,7 @@ void Chainstate::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)
@@ -4793,28 +4829,15 @@ std::vector<Chainstate*> ChainstateManager::GetAll()
return out;
}
-Chainstate& 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<Chainstate>& 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 Chainstate(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(
@@ -4829,6 +4852,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,
@@ -4886,11 +4949,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;
}
@@ -5164,6 +5250,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);
@@ -5175,3 +5284,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 c882eac408..b8151dc1fc 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -63,8 +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;
/** 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. */
@@ -93,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;
@@ -472,10 +456,6 @@ public:
//! 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.
@@ -702,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();
@@ -867,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.
@@ -926,17 +906,11 @@ 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.
- Chainstate& 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<Chainstate*> GetAll();
@@ -1050,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/validationinterface.cpp b/src/validationinterface.cpp
index 613c5b65ef..740c39d99d 100644
--- a/src/validationinterface.cpp
+++ b/src/validationinterface.cpp
@@ -17,6 +17,8 @@
#include <unordered_map>
#include <utility>
+const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept;
+
/**
* MainSignalsImpl manages a list of shared_ptr<CValidationInterface> callbacks.
*
@@ -215,9 +217,10 @@ void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemP
auto event = [tx, reason, mempool_sequence, this] {
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); });
};
- ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
+ ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s reason=%s", __func__,
tx->GetHash().ToString(),
- tx->GetWitnessHash().ToString());
+ tx->GetWitnessHash().ToString(),
+ RemovalReasonToString(reason));
}
void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex) {
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index d08d3664c4..b56a6d3aee 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -37,7 +37,7 @@ public:
bool m_include_unsafe_inputs = false;
//! If true, the selection process can add extra unselected inputs from the wallet
//! while requires all selected inputs be used
- bool m_allow_other_inputs = false;
+ bool m_allow_other_inputs = true;
//! Includes watch only addresses which are solvable
bool fAllowWatchOnly = false;
//! Override automatic min/max checks on fee, m_feerate must be set if true
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index b568e90998..a8be6cd83a 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -444,6 +444,12 @@ void SelectionResult::AddInput(const OutputGroup& group)
m_use_effective = !group.m_subtract_fee_outputs;
}
+void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs)
+{
+ util::insert(m_selected_inputs, inputs);
+ m_use_effective = !subtract_fee_outputs;
+}
+
void SelectionResult::Merge(const SelectionResult& other)
{
m_target += other.m_target;
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 761c2be0b3..b23dd10867 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -308,6 +308,7 @@ public:
void Clear();
void AddInput(const OutputGroup& group);
+ void AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs);
/** Calculates and stores the waste for this selection via GetSelectionWaste */
void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 8babbb4298..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;
@@ -1406,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.");
@@ -1598,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",
{
@@ -1700,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.");
@@ -1899,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 9c0c953a7a..6021e4bf4f 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -515,6 +515,7 @@ RPCHelpMan listunspent()
{"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
{"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 + ""},
+ {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase UTXOs"}
},
RPCArgOptions{.oneline_description="query_options"}},
},
@@ -590,10 +591,8 @@ RPCHelpMan listunspent()
include_unsafe = request.params[3].get_bool();
}
- CAmount nMinimumAmount = 0;
- CAmount nMaximumAmount = MAX_MONEY;
- CAmount nMinimumSumAmount = MAX_MONEY;
- uint64_t nMaximumCount = 0;
+ CoinFilterParams filter_coins;
+ filter_coins.min_amount = 0;
if (!request.params[4].isNull()) {
const UniValue& options = request.params[4].get_obj();
@@ -604,20 +603,25 @@ RPCHelpMan listunspent()
{"maximumAmount", UniValueType()},
{"minimumSumAmount", UniValueType()},
{"maximumCount", UniValueType(UniValue::VNUM)},
+ {"include_immature_coinbase", UniValueType(UniValue::VBOOL)}
},
true, true);
if (options.exists("minimumAmount"))
- nMinimumAmount = AmountFromValue(options["minimumAmount"]);
+ filter_coins.min_amount = AmountFromValue(options["minimumAmount"]);
if (options.exists("maximumAmount"))
- nMaximumAmount = AmountFromValue(options["maximumAmount"]);
+ filter_coins.max_amount = AmountFromValue(options["maximumAmount"]);
if (options.exists("minimumSumAmount"))
- nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]);
+ filter_coins.min_sum_amount = AmountFromValue(options["minimumSumAmount"]);
if (options.exists("maximumCount"))
- nMaximumCount = options["maximumCount"].getInt<int64_t>();
+ filter_coins.max_count = options["maximumCount"].getInt<int64_t>();
+
+ if (options.exists("include_immature_coinbase")) {
+ filter_coins.include_immature_coinbase = options["include_immature_coinbase"].get_bool();
+ }
}
// Make sure the results are valid at least up to the most recent block
@@ -633,7 +637,7 @@ RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
- vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All();
+ vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, filter_coins).All();
}
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index ebf694157b..0fa693e7e7 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -746,7 +746,7 @@ RPCHelpMan fundrawtransaction()
"If that happens, you will need to fund the transaction with different inputs and republish it."},
{"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
- {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+ {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
@@ -1143,7 +1143,7 @@ RPCHelpMan send()
{"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
{"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
- {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+ {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\" and \"bech32m\"."},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
@@ -1379,13 +1379,15 @@ 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;
}
} else {
- for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).All()) {
+ CoinFilterParams coins_params;
+ coins_params.min_amount = 0;
+ for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) {
CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
continue;
@@ -1596,7 +1598,7 @@ RPCHelpMan walletcreatefundedpsbt()
"If that happens, you will need to fund the transaction with different inputs and republish it."},
{"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
- {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
+ {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"},
{"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 0e13e4756b..3c10b47082 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -447,7 +447,7 @@ RPCHelpMan listtransactions()
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
- {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
{RPCResult::Type::STR, "category", "The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase transactions received.\n"
@@ -561,7 +561,7 @@ RPCHelpMan listsinceblock()
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
- {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
{RPCResult::Type::STR, "category", "The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase transactions received.\n"
@@ -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 1aa2a87e99..26270f23ed 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -4,9 +4,9 @@
#include <wallet/rpc/util.h>
+#include <common/url.h>
#include <rpc/util.h>
#include <util/translation.h>
-#include <util/url.h>
#include <wallet/context.h>
#include <wallet/wallet.h>
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 6da0741d59..896ade77dd 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")};
}
@@ -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);
@@ -2499,14 +2502,23 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
keys->Merge(std::move(*script_keys));
} else {
// Maybe there are pubkeys listed that we can sign for
- script_keys = std::make_unique<FlatSigningProvider>();
- for (const auto& pk_pair : input.hd_keypaths) {
- const CPubKey& pubkey = pk_pair.first;
- std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
- if (pk_keys) {
- keys->Merge(std::move(*pk_keys));
- }
+ std::vector<CPubKey> pubkeys;
+
+ // ECDSA Pubkeys
+ for (const auto& [pk, _] : input.hd_keypaths) {
+ pubkeys.push_back(pk);
+ }
+
+ // Taproot output pubkey
+ std::vector<std::vector<unsigned char>> sols;
+ if (Solver(script, sols) == TxoutType::WITNESS_V1_TAPROOT) {
+ sols[0].insert(sols[0].begin(), 0x02);
+ pubkeys.emplace_back(sols[0]);
+ sols[0][0] = 0x03;
+ pubkeys.emplace_back(sols[0]);
}
+
+ // Taproot pubkeys
for (const auto& pk_pair : input.m_tap_bip32_paths) {
const XOnlyPubKey& pubkey = pk_pair.first;
for (unsigned char prefix : {0x02, 0x03}) {
@@ -2514,10 +2526,14 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
std::copy(pubkey.begin(), pubkey.end(), b + 1);
CPubKey fullpubkey;
fullpubkey.Set(b, b + 33);
- std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey);
- if (pk_keys) {
- keys->Merge(std::move(*pk_keys));
- }
+ pubkeys.push_back(fullpubkey);
+ }
+ }
+
+ for (const auto& pubkey : pubkeys) {
+ std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
+ if (pk_keys) {
+ keys->Merge(std::move(*pk_keys));
}
}
}
@@ -2642,16 +2658,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 6833f9a095..8c0d56a1cb 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -143,14 +143,55 @@ static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
}
}
+// Fetch and validate the coin control selected inputs.
+// Coins could be internal (from the wallet) or external.
+util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ PreSelectedInputs result;
+ std::vector<COutPoint> vPresetInputs;
+ coin_control.ListSelected(vPresetInputs);
+ for (const COutPoint& outpoint : vPresetInputs) {
+ int input_bytes = -1;
+ CTxOut txout;
+ if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
+ // Clearly invalid input, fail
+ if (ptr_wtx->tx->vout.size() <= outpoint.n) {
+ return util::Error{strprintf(_("Invalid pre-selected input %s"), outpoint.ToString())};
+ }
+ txout = ptr_wtx->tx->vout.at(outpoint.n);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
+ } else {
+ // The input is external. We did not find the tx in mapWallet.
+ if (!coin_control.GetExternalOutput(outpoint, txout)) {
+ return util::Error{strprintf(_("Not found pre-selected input %s"), outpoint.ToString())};
+ }
+ }
+
+ if (input_bytes == -1) {
+ input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
+ }
+
+ // If available, override calculated size with coin control specified size
+ if (coin_control.HasInputWeight(outpoint)) {
+ input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
+ }
+
+ if (input_bytes == -1) {
+ return util::Error{strprintf(_("Not solvable pre-selected input %s"), outpoint.ToString())}; // Not solvable, can't estimate size for fee
+ }
+
+ /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
+ COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
+ result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
+ }
+ return result;
+}
+
CoinsResult AvailableCoins(const CWallet& wallet,
const CCoinControl* coinControl,
std::optional<CFeeRate> feerate,
- const CAmount& nMinimumAmount,
- const CAmount& nMaximumAmount,
- const CAmount& nMinimumSumAmount,
- const uint64_t nMaximumCount,
- bool only_spendable)
+ const CoinFilterParams& params)
{
AssertLockHeld(wallet.cs_wallet);
@@ -168,7 +209,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second;
- if (wallet.IsTxImmatureCoinBase(wtx))
+ if (wallet.IsTxImmatureCoinBase(wtx) && !params.include_immature_coinbase)
continue;
int nDepth = wallet.GetTxDepthInMainChain(wtx);
@@ -227,10 +268,11 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const CTxOut& output = wtx.tx->vout[i];
const COutPoint outpoint(wtxid, i);
- if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount)
+ if (output.nValue < params.min_amount || output.nValue > params.max_amount)
continue;
- if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint))
+ // Skip manually selected coins (the caller can fetch them directly)
+ if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
continue;
if (wallet.IsLockedCoin(outpoint))
@@ -258,7 +300,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
// Filter by spendable outputs only
- if (!spendable && only_spendable) continue;
+ if (!spendable && params.only_spendable) continue;
// Obtain script type
std::vector<std::vector<uint8_t>> script_solutions;
@@ -282,14 +324,14 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Cache total amount as we go
result.total_amount += output.nValue;
// Checks the sum amount of all UTXO's.
- if (nMinimumSumAmount != MAX_MONEY) {
- if (result.total_amount >= nMinimumSumAmount) {
+ if (params.min_sum_amount != MAX_MONEY) {
+ if (result.total_amount >= params.min_sum_amount) {
return result;
}
}
// Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && result.Size() >= nMaximumCount) {
+ if (params.max_count > 0 && result.Size() >= params.max_count) {
return result;
}
}
@@ -298,21 +340,16 @@ CoinsResult AvailableCoins(const CWallet& wallet,
return result;
}
-CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
+CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, CoinFilterParams params)
{
- return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false);
+ params.only_spendable = false;
+ return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, params);
}
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
{
LOCK(wallet.cs_wallet);
- return AvailableCoins(wallet, coinControl,
- /*feerate=*/ std::nullopt,
- /*nMinimumAmount=*/ 1,
- /*nMaximumAmount=*/ MAX_MONEY,
- /*nMinimumSumAmount=*/ MAX_MONEY,
- /*nMaximumCount=*/ 0
- ).total_amount;
+ return AvailableCoins(wallet, coinControl).total_amount;
}
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output)
@@ -522,82 +559,42 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
return best_result;
}
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params)
{
- CAmount value_to_select = nTargetValue;
-
- OutputGroup preset_inputs(coin_selection_params);
+ // Deduct preset inputs amount from the search target
+ CAmount selection_target = nTargetValue - pre_set_inputs.total_amount;
- // calculate value from preset inputs and store them
- std::set<COutPoint> preset_coins;
+ // Return if automatic coin selection is disabled, and we don't cover the selection target
+ if (!coin_control.m_allow_other_inputs && selection_target > 0) return std::nullopt;
- std::vector<COutPoint> vPresetInputs;
- coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs) {
- int input_bytes = -1;
- CTxOut txout;
- auto ptr_wtx = wallet.GetWalletTx(outpoint.hash);
- if (ptr_wtx) {
- // Clearly invalid input, fail
- if (ptr_wtx->tx->vout.size() <= outpoint.n) {
- return std::nullopt;
- }
- txout = ptr_wtx->tx->vout.at(outpoint.n);
- input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
- } else {
- // The input is external. We did not find the tx in mapWallet.
- if (!coin_control.GetExternalOutput(outpoint, txout)) {
- return std::nullopt;
- }
- }
-
- if (input_bytes == -1) {
- input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
- }
-
- // If available, override calculated size with coin control specified size
- if (coin_control.HasInputWeight(outpoint)) {
- input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
- }
-
- if (input_bytes == -1) {
- return std::nullopt; // Not solvable, can't estimate size for fee
- }
-
- /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
- COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
- if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= output.txout.nValue;
- } else {
- value_to_select -= output.GetEffectiveValue();
- }
- preset_coins.insert(outpoint);
- /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
- * positive_only is set to false because we want to include all preset inputs, even if they are dust.
- */
- preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
- }
-
- // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
- if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) {
+ // Return if we can cover the target only with the preset inputs
+ if (selection_target <= 0) {
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
- result.AddInput(preset_inputs);
-
- if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) {
- return std::nullopt;
- } else if (result.GetSelectedValue() < nTargetValue) {
- return std::nullopt;
- }
-
+ result.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
return result;
}
- // remove preset inputs from coins so that Coin Selection doesn't pick them.
- if (coin_control.HasSelected()) {
- available_coins.Erase(preset_coins);
+ // Start wallet Coin Selection procedure
+ auto op_selection_result = AutomaticCoinSelection(wallet, available_coins, selection_target, coin_control, coin_selection_params);
+ if (!op_selection_result) return op_selection_result;
+
+ // If needed, add preset inputs to the automatic coin selection result
+ if (!pre_set_inputs.coins.empty()) {
+ SelectionResult preselected(pre_set_inputs.total_amount, SelectionAlgorithm::MANUAL);
+ preselected.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
+ op_selection_result->Merge(preselected);
+ op_selection_result->ComputeAndSetWaste(coin_selection_params.min_viable_change,
+ coin_selection_params.m_cost_of_change,
+ coin_selection_params.m_change_fee);
}
+ return op_selection_result;
+}
+std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+{
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
@@ -614,16 +611,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
available_coins.Shuffle(coin_selection_params.rng_fast);
}
- SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL);
- preselected.AddInput(preset_inputs);
-
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
// permissive CoinEligibilityFilter.
std::optional<SelectionResult> res = [&] {
- // Pre-selected inputs already cover the target amount.
- if (value_to_select <= 0) return std::make_optional(SelectionResult(value_to_select, SelectionAlgorithm::MANUAL));
-
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1;
@@ -673,14 +664,6 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
return std::optional<SelectionResult>();
}();
- if (!res) return std::nullopt;
-
- // Add preset inputs to result
- res->Merge(preselected);
- if (res->GetAlgo() == SelectionAlgorithm::MANUAL) {
- res->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
- }
-
return res;
}
@@ -893,17 +876,23 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
CAmount selection_target = recipients_sum + not_input_fees;
- // Get available coins
- auto available_coins = AvailableCoins(wallet,
- &coin_control,
- coin_selection_params.m_effective_feerate,
- 1, /*nMinimumAmount*/
- MAX_MONEY, /*nMaximumAmount*/
- MAX_MONEY, /*nMinimumSumAmount*/
- 0); /*nMaximumCount*/
+ // Fetch manually selected coins
+ PreSelectedInputs preset_inputs;
+ if (coin_control.HasSelected()) {
+ auto res_fetch_inputs = FetchSelectedInputs(wallet, coin_control, coin_selection_params);
+ if (!res_fetch_inputs) return util::Error{util::ErrorString(res_fetch_inputs)};
+ preset_inputs = *res_fetch_inputs;
+ }
+
+ // Fetch wallet available coins if "other inputs" are
+ // allowed (coins automatically selected by the wallet)
+ CoinsResult available_coins;
+ if (coin_control.m_allow_other_inputs) {
+ available_coins = AvailableCoins(wallet, &coin_control, coin_selection_params.m_effective_feerate);
+ }
// Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
+ std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
if (!result) {
return util::Error{_("Insufficient funds")};
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index c29e5be5c7..ba2c6638c8 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -55,23 +55,34 @@ struct CoinsResult {
CAmount total_amount{0};
};
+struct CoinFilterParams {
+ // Outputs below the minimum amount will not get selected
+ CAmount min_amount{1};
+ // Outputs above the maximum amount will not get selected
+ CAmount max_amount{MAX_MONEY};
+ // Return outputs until the minimum sum amount is covered
+ CAmount min_sum_amount{MAX_MONEY};
+ // Maximum number of outputs that can be returned
+ uint64_t max_count{0};
+ // By default, return only spendable outputs
+ bool only_spendable{true};
+ // By default, do not include immature coinbase outputs
+ bool include_immature_coinbase{false};
+};
+
/**
* Populate the CoinsResult struct with vectors of available COutputs, organized by OutputType.
*/
CoinsResult AvailableCoins(const CWallet& wallet,
const CCoinControl* coinControl = nullptr,
std::optional<CFeeRate> feerate = std::nullopt,
- const CAmount& nMinimumAmount = 1,
- const CAmount& nMaximumAmount = MAX_MONEY,
- const CAmount& nMinimumSumAmount = MAX_MONEY,
- const uint64_t nMaximumCount = 0,
- bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+ const CoinFilterParams& params = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
- * Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function
+ * Wrapper function for AvailableCoins which skips the `feerate` and `CoinFilterParams::only_spendable` parameters. Use this function
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction.
*/
-CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, CoinFilterParams params = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
@@ -121,9 +132,35 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
const CoinSelectionParams& coin_selection_params);
+// User manually selected inputs that must be part of the transaction
+struct PreSelectedInputs
+{
+ std::set<COutput> coins;
+ // If subtract fee from outputs is disabled, the 'total_amount'
+ // will be the sum of each output effective value
+ // instead of the sum of the outputs amount
+ CAmount total_amount{0};
+
+ void Insert(const COutput& output, bool subtract_fee_outputs)
+ {
+ if (subtract_fee_outputs) {
+ total_amount += output.txout.nValue;
+ } else {
+ total_amount += output.GetEffectiveValue();
+ }
+ coins.insert(output);
+ }
+};
+
+/**
+ * Fetch and validate coin control selected inputs.
+ * Coins could be internal (from the wallet) or external.
+*/
+util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+
/**
- * Select a set of coins such that nTargetValue is met and at least
- * all coins from coin_control are selected; never select unconfirmed coins if they are not ours
+ * Select a set of coins such that nTargetValue is met; never select unconfirmed coins if they are not ours
* param@[in] wallet The wallet which provides data necessary to spend the selected coins
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
* param@[in] nTargetValue The target value
@@ -132,9 +169,17 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
* returns If successful, a SelectionResult containing the selected coins
* If failed, a nullopt.
*/
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
+std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+/**
+ * Select all coins from coin_control, and if coin_control 'm_allow_other_inputs=true', call 'AutomaticCoinSelection' to
+ * select a set of coins such that nTargetValue - pre_set_inputs.total_amount is met.
+ */
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+
struct CreatedTransactionResult
{
CTransactionRef tx;
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 23f024247d..f9c8c8ee9d 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -338,9 +338,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.m_allow_other_inputs = true;
- coin_control.Select(available_coins.All().at(0).outpoint);
+ COutput select_coin = available_coins.All().at(0);
+ coin_control.Select(select_coin.outpoint);
+ PreSelectedInputs selected_input;
+ selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
+ available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin());
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
- const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result10 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(result10);
}
{
@@ -363,7 +367,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
expected_result.Clear();
add_coin(10 * CENT, 2, expected_result);
CCoinControl coin_control;
- const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result11 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result11));
available_coins.Clear();
@@ -378,7 +382,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
expected_result.Clear();
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
- const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result12 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result12));
available_coins.Clear();
@@ -394,8 +398,12 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
coin_control.m_allow_other_inputs = true;
- coin_control.Select(available_coins.All().at(1).outpoint); // pre select 9 coin
- const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ COutput select_coin = available_coins.All().at(1); // pre select 9 coin
+ coin_control.Select(select_coin.outpoint);
+ PreSelectedInputs selected_input;
+ selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
+ available_coins.coins[OutputType::BECH32].erase(++available_coins.coins[OutputType::BECH32].begin());
+ const auto result13 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result13));
}
}
@@ -783,7 +791,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
cs_params.m_cost_of_change = 1;
cs_params.min_viable_change = 1;
CCoinControl cc;
- const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
+ const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, target, cc, cs_params);
BOOST_CHECK(result);
BOOST_CHECK_GE(result->GetSelectedValue(), target);
}
@@ -965,7 +973,10 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
cc.SetInputWeight(output.outpoint, 148);
cc.SelectExternal(output.outpoint, output.txout);
- const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
+ const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params));
+ available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin());
+
+ const auto result = SelectCoins(*wallet, available_coins, preset_inputs, target, cc, cs_params);
BOOST_CHECK(!result);
}
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/wallet.cpp b/src/wallet/wallet.cpp
index 461e64751a..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>
@@ -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)
@@ -1754,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)
@@ -1781,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.
@@ -1792,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;
@@ -1903,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 m_next_resend 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.
@@ -1934,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() < m_next_resend) return;
- // resend 12-36 hours from now, ~1 day on average.
- m_next_resend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
-
int submitted_tx_count = 0;
{ // cs_wallet scope
@@ -1957,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);
@@ -1979,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();
}
}
@@ -2376,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");
@@ -2470,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;
@@ -3198,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 953d3e5bde..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>
@@ -250,7 +251,7 @@ private:
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
/** The next scheduled rebroadcast of wallet transactions. */
- std::atomic<int64_t> m_next_resend{};
+ 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/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 b9b7019a3c..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 <primitives/block.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 51c8ad515e..eaf3455296 100644
--- a/src/zmq/zmqpublishnotifier.cpp
+++ b/src/zmq/zmqpublishnotifier.cpp
@@ -6,21 +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 <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()