diff options
Diffstat (limited to 'src')
255 files changed, 3654 insertions, 2610 deletions
diff --git a/src/.clang-tidy b/src/.clang-tidy index b1f543f610..e9807d4cb7 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -1,11 +1,13 @@ Checks: ' -*, bugprone-argument-comment, +modernize-use-default-member-init, modernize-use-nullptr, readability-redundant-declaration, ' WarningsAsErrors: ' bugprone-argument-comment, +modernize-use-default-member-init, modernize-use-nullptr, readability-redundant-declaration, ' diff --git a/src/Makefile.am b/src/Makefile.am index 357e562c69..89329f5f69 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ noinst_PROGRAMS = TESTS = BENCHMARKS = -BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) +BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) LIBBITCOIN_NODE=libbitcoin_node.a LIBBITCOIN_COMMON=libbitcoin_common.a @@ -139,7 +139,6 @@ BITCOIN_CORE_H = \ compat/byteswap.h \ compat/cpuid.h \ compat/endian.h \ - compat/sanity.h \ compressor.h \ consensus/consensus.h \ consensus/tx_check.h \ @@ -171,6 +170,10 @@ BITCOIN_CORE_H = \ interfaces/ipc.h \ interfaces/node.h \ interfaces/wallet.h \ + kernel/chainstatemanager_opts.h \ + kernel/checks.h \ + kernel/coinstats.h \ + kernel/context.h \ key.h \ key_io.h \ logging.h \ @@ -190,7 +193,6 @@ BITCOIN_CORE_H = \ node/caches.h \ node/chainstate.h \ node/coin.h \ - node/coinstats.h \ node/context.h \ node/miner.h \ node/minisketchwrapper.h \ @@ -254,6 +256,7 @@ BITCOIN_CORE_H = \ util/bip32.h \ util/bytevectorhash.h \ util/check.h \ + util/designator.h \ util/epochguard.h \ util/error.h \ util/fastrange.h \ @@ -329,7 +332,6 @@ obj/build.h: FORCE "$(abs_top_srcdir)" libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h -ipc/capnp/libbitcoin_ipc_a-ipc.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) # server: shared between bitcoind and bitcoin-qt # Contains code accessing mempool and chain state that is meant to be separated @@ -356,6 +358,9 @@ libbitcoin_node_a_SOURCES = \ index/coinstatsindex.cpp \ index/txindex.cpp \ init.cpp \ + kernel/checks.cpp \ + kernel/coinstats.cpp \ + kernel/context.cpp \ mapport.cpp \ net.cpp \ netgroup.cpp \ @@ -364,7 +369,6 @@ libbitcoin_node_a_SOURCES = \ node/caches.cpp \ node/chainstate.cpp \ node/coin.cpp \ - node/coinstats.cpp \ node/context.cpp \ node/interfaces.cpp \ node/miner.cpp \ @@ -407,6 +411,7 @@ libbitcoin_node_a_SOURCES = \ if ENABLE_WALLET libbitcoin_node_a_SOURCES += wallet/init.cpp +libbitcoin_node_a_CPPFLAGS += $(BDB_CPPFLAGS) endif if !ENABLE_WALLET libbitcoin_node_a_SOURCES += dummywallet.cpp @@ -426,7 +431,7 @@ endif # wallet: shared between bitcoind and bitcoin-qt, but only linked # when wallet enabled -libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(SQLITE_CFLAGS) +libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ wallet/coincontrol.cpp \ @@ -629,15 +634,12 @@ libbitcoin_common_a_SOURCES = \ $(BITCOIN_CORE_H) # util: shared between all executables. -# This library *must* be included to make sure that the glibc -# sanity checks are linked. libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ chainparamsbase.cpp \ clientversion.cpp \ - compat/glibcxx_sanity.cpp \ fs.cpp \ interfaces/echo.cpp \ interfaces/handler.cpp \ @@ -852,13 +854,11 @@ endif libbitcoinkernel_la_SOURCES = \ kernel/bitcoinkernel.cpp \ arith_uint256.cpp \ - blockfilter.cpp \ chain.cpp \ chainparamsbase.cpp \ chainparams.cpp \ clientversion.cpp \ coins.cpp \ - compat/glibcxx_sanity.cpp \ compressor.cpp \ consensus/merkle.cpp \ consensus/tx_check.cpp \ @@ -870,16 +870,13 @@ libbitcoinkernel_la_SOURCES = \ flatfile.cpp \ fs.cpp \ hash.cpp \ - index/base.cpp \ - index/blockfilterindex.cpp \ - index/coinstatsindex.cpp \ - init/common.cpp \ + kernel/checks.cpp \ + kernel/coinstats.cpp \ + kernel/context.cpp \ key.cpp \ logging.cpp \ - netaddress.cpp \ node/blockstorage.cpp \ node/chainstate.cpp \ - node/coinstats.cpp \ node/ui_interface.cpp \ policy/feerate.cpp \ policy/fees.cpp \ @@ -905,11 +902,9 @@ libbitcoinkernel_la_SOURCES = \ support/lockedpool.cpp \ sync.cpp \ threadinterrupt.cpp \ - timedata.cpp \ txdb.cpp \ txmempool.cpp \ uint256.cpp \ - util/asmap.cpp \ util/bytevectorhash.cpp \ util/check.cpp \ util/getuniquepath.cpp \ @@ -1018,6 +1013,10 @@ libbitcoin_ipc_mpgen_input = \ EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) %.capnp: +# Explicitly list dependencies on generated headers as described in +# https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually +ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) + if BUILD_MULTIPROCESS LIBBITCOIN_IPC=libbitcoin_ipc.a libbitcoin_ipc_a_SOURCES = \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 02a3f9ae7d..ebd9e860cf 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -201,6 +201,7 @@ test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EV test_test_bitcoin_LDADD = $(LIBTEST_UTIL) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) +test_test_bitcoin_CPPFLAGS += $(BDB_CPPFLAGS) endif test_test_bitcoin_LDADD += $(LIBBITCOIN_NODE) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ @@ -269,7 +270,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/locale.cpp \ test/fuzz/merkleblock.cpp \ test/fuzz/message.cpp \ - test/fuzz/miniscript_decode.cpp \ + test/fuzz/miniscript.cpp \ test/fuzz/minisketch.cpp \ test/fuzz/muhash.cpp \ test/fuzz/multiplication_overflow.cpp \ @@ -364,8 +365,8 @@ endif if TARGET_WINDOWS else if ENABLE_BENCH - @echo "Running bench/bench_bitcoin ..." - $(BENCH_BINARY) > /dev/null + @echo "Running bench/bench_bitcoin (one iteration sanity check)..." + $(BENCH_BINARY) --sanity-check > /dev/null endif endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check diff --git a/src/addrdb.cpp b/src/addrdb.cpp index b9eae292d7..31f8eadf98 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -49,8 +49,7 @@ template <typename Data> bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version) { // Generate random temporary filename - uint16_t randv = 0; - GetRandBytes({(unsigned char*)&randv, sizeof(randv)}); + const uint16_t randv{GetRand<uint16_t>()}; std::string tmpfn = strprintf("%s.%04x", prefix, randv); // open temp output file, and associate with CAutoFile diff --git a/src/addrman.cpp b/src/addrman.cpp index f74729d47b..ed823eeb05 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -866,25 +866,27 @@ void AddrManImpl::ResolveCollisions_() int id_old = vvTried[tried_bucket][tried_bucket_pos]; AddrInfo& info_old = mapInfo[id_old]; + const auto current_time{GetAdjustedTime()}; + // Has successfully connected in last X hours - if (GetAdjustedTime() - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { + if (current_time - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { erase_collision = true; - } else if (GetAdjustedTime() - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours + } else if (current_time - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours // Give address at least 60 seconds to successfully connect - if (GetAdjustedTime() - info_old.nLastTry > 60) { + if (current_time - info_old.nLastTry > 60) { LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToString(), info_new.ToString()); // Replaces an existing address already in the tried table with the new address - Good_(info_new, false, GetAdjustedTime()); + Good_(info_new, false, current_time); erase_collision = true; } - } else if (GetAdjustedTime() - info_new.nLastSuccess > ADDRMAN_TEST_WINDOW) { + } else if (current_time - info_new.nLastSuccess > ADDRMAN_TEST_WINDOW) { // If the collision hasn't resolved in some reasonable amount of time, // just evict the old entry -- we must not be able to // connect to it for some reason. LogPrint(BCLog::ADDRMAN, "Unable to test; replacing %s with %s in tried table anyway\n", info_old.ToString(), info_new.ToString()); - Good_(info_new, false, GetAdjustedTime()); + Good_(info_new, false, current_time); erase_collision = true; } } else { // Collision is not actually a collision anymore diff --git a/src/banman.cpp b/src/banman.cpp index b28e3f7f7c..2a6e0e010f 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -16,6 +16,19 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) { + LoadBanlist(); + DumpBanlist(); +} + +BanMan::~BanMan() +{ + DumpBanlist(); +} + +void BanMan::LoadBanlist() +{ + LOCK(m_cs_banned); + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); int64_t n_start = GetTimeMillis(); @@ -29,13 +42,6 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t m_banned = {}; m_is_dirty = true; } - - DumpBanlist(); -} - -BanMan::~BanMan() -{ - DumpBanlist(); } void BanMan::DumpBanlist() @@ -173,23 +179,24 @@ void BanMan::GetBanned(banmap_t& banmap) void BanMan::SweepBanned() { + AssertLockHeld(m_cs_banned); + int64_t now = GetTime(); bool notify_ui = false; - { - LOCK(m_cs_banned); - banmap_t::iterator it = m_banned.begin(); - while (it != m_banned.end()) { - CSubNet sub_net = (*it).first; - CBanEntry ban_entry = (*it).second; - if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { - m_banned.erase(it++); - m_is_dirty = true; - notify_ui = true; - LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); - } else - ++it; + banmap_t::iterator it = m_banned.begin(); + while (it != m_banned.end()) { + CSubNet sub_net = (*it).first; + CBanEntry ban_entry = (*it).second; + if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { + m_banned.erase(it++); + m_is_dirty = true; + notify_ui = true; + LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); + } else { + ++it; } } + // update UI if (notify_ui && m_client_interface) { m_client_interface->BannedListChanged(); diff --git a/src/banman.h b/src/banman.h index f268fffa5a..77b043f081 100644 --- a/src/banman.h +++ b/src/banman.h @@ -80,11 +80,12 @@ public: void DumpBanlist(); private: + void LoadBanlist() EXCLUSIVE_LOCKS_REQUIRED(!m_cs_banned); bool BannedSetIsDirty(); //!set the "dirty" flag for the banlist void SetBannedSetDirty(bool dirty = true); //!clean unused entries (if bantime has expired) - void SweepBanned(); + void SweepBanned() EXCLUSIVE_LOCKS_REQUIRED(m_cs_banned); RecursiveMutex m_cs_banned; banmap_t m_banned GUARDED_BY(m_cs_banned); diff --git a/src/base58.h b/src/base58.h index 9ba5af73e0..d2a8d5e3bc 100644 --- a/src/base58.h +++ b/src/base58.h @@ -14,7 +14,6 @@ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H -#include <attributes.h> #include <span.h> #include <string> diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp index 033d319750..26975bb59d 100644 --- a/src/bench/bench.cpp +++ b/src/bench/bench.cpp @@ -57,6 +57,10 @@ void benchmark::BenchRunner::RunAll(const Args& args) std::regex reFilter(args.regex_filter); std::smatch baseMatch; + if (args.sanity_check) { + std::cout << "Running with --sanity-check option, benchmark results will be useless." << std::endl; + } + std::vector<ankerl::nanobench::Result> benchmarkResults; for (const auto& p : benchmarks()) { if (!std::regex_match(p.first, baseMatch, reFilter)) { @@ -69,6 +73,9 @@ void benchmark::BenchRunner::RunAll(const Args& args) } Bench bench; + if (args.sanity_check) { + bench.epochs(1).epochIterations(1); + } bench.name(p.first); if (args.min_time > 0ms) { // convert to nanos before dividing to reduce rounding errors diff --git a/src/bench/bench.h b/src/bench/bench.h index 6634138beb..17535e4e81 100644 --- a/src/bench/bench.h +++ b/src/bench/bench.h @@ -43,6 +43,7 @@ typedef std::function<void(Bench&)> BenchFunction; struct Args { bool is_list_only; + bool sanity_check; std::chrono::milliseconds min_time; std::vector<double> asymptote; fs::path output_csv; diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index d6f9c0f8b5..1bb4d34db9 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -26,9 +26,10 @@ static void SetupBenchArgs(ArgsManager& argsman) argsman.AddArg("-asymptote=<n1,n2,n3,...>", "Test asymptotic growth of the runtime of an algorithm, if supported by the benchmark", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-list", "List benchmarks without executing them", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-min_time=<milliseconds>", strprintf("Minimum runtime per benchmark, in milliseconds (default: %d)", DEFAULT_MIN_TIME_MS), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); - argsman.AddArg("-output_csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-output_json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-min-time=<milliseconds>", strprintf("Minimum runtime per benchmark, in milliseconds (default: %d)", DEFAULT_MIN_TIME_MS), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); + argsman.AddArg("-output-csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-output-json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-sanity-check", "Run benchmarks for only one iteration", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } // parses a comma separated list like "10,20,30,50" @@ -73,7 +74,7 @@ int main(int argc, char** argv) " sure each run has exactly the same preconditions.\n" "\n" " * If results are still not reliable, increase runtime with e.g.\n" - " -min_time=5000 to let a benchmark run for at least 5 seconds.\n" + " -min-time=5000 to let a benchmark run for at least 5 seconds.\n" "\n" " * bench_bitcoin uses nanobench [3] for which there is extensive\n" " documentation available online.\n" @@ -108,10 +109,11 @@ int main(int argc, char** argv) 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.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); benchmark::BenchRunner::RunAll(args); diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 53591f8905..602081fb9b 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -30,8 +30,7 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) struct PrevectorJob { prevector<PREVECTOR_SIZE, uint8_t> p; - PrevectorJob(){ - } + PrevectorJob() = default; explicit PrevectorJob(FastRandomContext& insecure_rand){ p.resize(insecure_rand.randrange(PREVECTOR_SIZE*2)); } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index de8ab9807c..b2958bcc9f 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -58,7 +58,7 @@ static void CoinSelection(benchmark::Bench& bench) // Create coins std::vector<COutput> coins; for (const auto& wtx : wtxs) { - coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true); + coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); } const CoinEligibilityFilter filter_standard(1, 6, 0); @@ -88,7 +88,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup> CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; - COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true, /*fees=*/ 0); set.emplace_back(); set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index a58658c4f1..725a6f8f5b 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -88,7 +88,7 @@ static void ComplexMemPool(benchmark::Bench& bench) } std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/1); const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN); - CTxMemPool pool; + CTxMemPool& pool = *testing_setup.get()->m_node.mempool; LOCK2(cs_main, pool.cs); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { for (auto& tx : ordered_coins) { @@ -102,16 +102,15 @@ static void ComplexMemPool(benchmark::Bench& bench) static void MempoolCheck(benchmark::Bench& bench) { FastRandomContext det_rand{true}; - const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000; - const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/5); - const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"}); - CTxMemPool pool; + auto testing_setup = MakeNoLogFileContext<TestChain100Setup>(CBaseChainParams::REGTEST, {"-checkmempool=1"}); + CTxMemPool& pool = *testing_setup.get()->m_node.mempool; LOCK2(cs_main, pool.cs); + testing_setup->PopulateMempool(det_rand, 400, true); const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip(); - for (auto& tx : ordered_coins) AddTx(tx, pool); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { - pool.check(coins_tip, /*spendheight=*/2); + // Bump up the spendheight so we don't hit premature coinbase spend errors. + pool.check(coins_tip, /*spendheight=*/300); }); } diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index 6343ed7848..b3688bab1b 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -10,8 +10,8 @@ #include <bench/bench.h> struct nontrivial_t { - int x; - nontrivial_t() :x(-1) {} + int x{-1}; + nontrivial_t() = default; SERIALIZE_METHODS(nontrivial_t, obj) { READWRITE(obj.x); } }; static_assert(!std::is_trivially_default_constructible<nontrivial_t>::value, diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index e5b6875a36..1817aa1a53 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -11,10 +11,12 @@ // // It is part of the libbitcoinkernel project. +#include <kernel/checks.h> +#include <kernel/context.h> + #include <chainparams.h> #include <consensus/validation.h> #include <core_io.h> -#include <init/common.h> #include <node/blockstorage.h> #include <node/chainstate.h> #include <scheduler.h> @@ -24,6 +26,7 @@ #include <validation.h> #include <validationinterface.h> +#include <cassert> #include <filesystem> #include <functional> #include <iosfwd> @@ -49,7 +52,11 @@ int main(int argc, char* argv[]) SelectParams(CBaseChainParams::MAIN); const CChainParams& chainparams = Params(); - init::SetGlobals(); // ECC_Start, etc. + kernel::Context kernel_context{}; + // We can't use a goto here, but we can use an assert since none of the + // things instantiated so far requires running the epilogue to be torn down + // properly + assert(!kernel::SanityChecks(kernel_context).has_value()); // Necessary for CheckInputScripts (eventually called by ProcessNewBlock), // which will try the script cache first and fall back to actually @@ -70,13 +77,16 @@ int main(int argc, char* argv[]) // SETUP: Chainstate - ChainstateManager chainman; + const ChainstateManager::Options chainman_opts{ + chainparams, + static_cast<int64_t(*)()>(GetTime), + }; + ChainstateManager chainman{chainman_opts}; auto rv = node::LoadChainstate(false, std::ref(chainman), nullptr, false, - chainparams.GetConsensus(), false, 2 << 20, 2 << 22, @@ -91,10 +101,8 @@ int main(int argc, char* argv[]) auto maybe_verify_error = node::VerifyLoadedChainstate(std::ref(chainman), false, false, - chainparams.GetConsensus(), DEFAULT_CHECKBLOCKS, - DEFAULT_CHECKLEVEL, - /*get_unix_time_seconds=*/static_cast<int64_t (*)()>(GetTime)); + DEFAULT_CHECKLEVEL); if (maybe_verify_error.has_value()) { std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; goto epilogue; @@ -163,7 +171,7 @@ int main(int argc, char* argv[]) LOCK(cs_main); const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); if (pindex) { - UpdateUncommittedBlockStructures(block, pindex, chainparams.GetConsensus()); + chainman.UpdateUncommittedBlockStructures(block, pindex); } } @@ -190,7 +198,7 @@ int main(int argc, char* argv[]) bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(chainparams, blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { std::cerr << "duplicate" << std::endl; @@ -253,6 +261,4 @@ epilogue: } } GetMainSignals().UnregisterBackgroundSignalScheduler(); - - init::UnsetGlobals(); } diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index dea46693bc..b9e5a81f8d 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,23 +9,26 @@ #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/stdin.h> #include <policy/feerate.h> #include <rpc/client.h> #include <rpc/mining.h> #include <rpc/protocol.h> #include <rpc/request.h> #include <tinyformat.h> +#include <univalue.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> #include <util/url.h> #include <algorithm> +#include <chrono> #include <cmath> +#include <cstdio> #include <functional> #include <memory> #include <optional> -#include <stdio.h> #include <string> #include <tuple> @@ -37,8 +40,10 @@ #include <event2/keyvalq_struct.h> #include <support/events.h> -#include <univalue.h> -#include <compat/stdin.h> +// The server returns time values from a mockable system clock, but it is not +// trivial to get the mocked time from the server, nor is it needed for now, so +// just use a plain system_clock. +using CliClock = std::chrono::system_clock; const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = urlDecode; @@ -174,10 +179,10 @@ static int AppInitRPC(int argc, char* argv[]) /** Reply structure for request_done to fill in */ struct HTTPReply { - HTTPReply(): status(0), error(-1) {} + HTTPReply() = default; - int status; - int error; + int status{0}; + int error{-1}; std::string body; }; @@ -238,7 +243,7 @@ static void http_error_cb(enum evhttp_request_error err, void *ctx) class BaseRequestHandler { public: - virtual ~BaseRequestHandler() {} + virtual ~BaseRequestHandler() = default; virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0; virtual UniValue ProcessReply(const UniValue &batch_in) = 0; }; @@ -407,8 +412,8 @@ private: bool is_addr_relay_enabled; bool is_bip152_hb_from; bool is_bip152_hb_to; - bool is_block_relay; bool is_outbound; + bool is_tx_relay; bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); } }; std::vector<Peer> m_peers; @@ -433,7 +438,6 @@ private: if (conn_type == "addr-fetch") return "addr"; return ""; } - const int64_t m_time_now{GetTimeSeconds()}; public: static constexpr int ID_PEERINFO = 0; @@ -462,9 +466,10 @@ public: if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO]; const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]}; - if (networkinfo["version"].get_int() < 209900) { + if (networkinfo["version"].getInt<int>() < 209900) { throw std::runtime_error("-netinfo requires bitcoind server to be running v0.21.0 and up"); } + const int64_t time_now{TicksSinceEpoch<std::chrono::seconds>(CliClock::now())}; // Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs. for (const UniValue& peer : batch[ID_PEERINFO]["result"].getValues()) { @@ -472,7 +477,7 @@ public: const int8_t network_id{NetworkStringToId(network)}; if (network_id == UNKNOWN_NETWORK) continue; const bool is_outbound{!peer["inbound"].get_bool()}; - const bool is_block_relay{!peer["relaytxes"].get_bool()}; + const bool is_tx_relay{peer["relaytxes"].isNull() ? true : peer["relaytxes"].get_bool()}; const std::string conn_type{peer["connection_type"].get_str()}; ++m_counts.at(is_outbound).at(network_id); // in/out by network ++m_counts.at(is_outbound).at(NETWORKS.size()); // in/out overall @@ -482,25 +487,25 @@ public: if (conn_type == "manual") ++m_manual_peers_count; if (DetailsRequested()) { // Push data for this peer to the peers vector. - const int peer_id{peer["id"].get_int()}; - const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()}; - const int version{peer["version"].get_int()}; - const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].get_int64()}; - const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].get_int64()}; - const int64_t conn_time{peer["conntime"].get_int64()}; - const int64_t last_blck{peer["last_block"].get_int64()}; - const int64_t last_recv{peer["lastrecv"].get_int64()}; - const int64_t last_send{peer["lastsend"].get_int64()}; - const int64_t last_trxn{peer["last_transaction"].get_int64()}; + const int peer_id{peer["id"].getInt<int>()}; + const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].getInt<int>()}; + const int version{peer["version"].getInt<int>()}; + const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].getInt<int64_t>()}; + const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].getInt<int64_t>()}; + const int64_t conn_time{peer["conntime"].getInt<int64_t>()}; + const int64_t last_blck{peer["last_block"].getInt<int64_t>()}; + const int64_t last_recv{peer["lastrecv"].getInt<int64_t>()}; + const int64_t last_send{peer["lastsend"].getInt<int64_t>()}; + const int64_t last_trxn{peer["last_transaction"].getInt<int64_t>()}; const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()}; const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()}; const std::string addr{peer["addr"].get_str()}; - const std::string age{conn_time == 0 ? "" : ToString((m_time_now - conn_time) / 60)}; + const std::string age{conn_time == 0 ? "" : ToString((time_now - conn_time) / 60)}; const std::string sub_version{peer["subver"].get_str()}; const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound}); + m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); @@ -511,7 +516,7 @@ public: } // Generate report header. - std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())}; + std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].getInt<int>(), networkinfo["subversion"].get_str())}; // Report detailed peer connections list sorted by direction and minimum ping time. if (DetailsRequested() && !m_peers.empty()) { @@ -531,10 +536,10 @@ public: peer.network, PingTimeToString(peer.min_ping), PingTimeToString(peer.ping), - peer.last_send ? ToString(m_time_now - peer.last_send) : "", - peer.last_recv ? ToString(m_time_now - peer.last_recv) : "", - peer.last_trxn ? ToString((m_time_now - peer.last_trxn) / 60) : peer.is_block_relay ? "*" : "", - peer.last_blck ? ToString((m_time_now - peer.last_blck) / 60) : "", + peer.last_send ? ToString(time_now - peer.last_send) : "", + peer.last_recv ? ToString(time_now - peer.last_recv) : "", + peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_tx_relay ? "" : "*", + peer.last_blck ? ToString((time_now - peer.last_blck) / 60) : "", strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "), m_max_addr_processed_length, // variable spacing peer.addr_processed ? ToString(peer.addr_processed) : peer.is_addr_relay_enabled ? "" : ".", @@ -592,7 +597,7 @@ public: max_addr_size = std::max(addr["address"].get_str().length() + 1, max_addr_size); } for (const UniValue& addr : local_addrs) { - result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].get_int(), addr["score"].get_int()); + result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].getInt<int>(), addr["score"].getInt<int>()); } } @@ -838,21 +843,20 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str // Execute and handle connection failures with -rpcwait. const bool fWait = gArgs.GetBoolArg("-rpcwait", false); const int timeout = gArgs.GetIntArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT); - const auto deadline{GetTime<std::chrono::microseconds>() + 1s * timeout}; + const auto deadline{std::chrono::steady_clock::now() + 1s * timeout}; do { try { response = CallRPC(rh, strMethod, args, rpcwallet); if (fWait) { const UniValue& error = find_value(response, "error"); - if (!error.isNull() && error["code"].get_int() == RPC_IN_WARMUP) { + if (!error.isNull() && error["code"].getInt<int>() == RPC_IN_WARMUP) { throw CConnectionFailed("server in warmup"); } } break; // Connection succeeded, no need to retry. } catch (const CConnectionFailed& e) { - const auto now{GetTime<std::chrono::microseconds>()}; - if (fWait && (timeout <= 0 || now < deadline)) { + if (fWait && (timeout <= 0 || std::chrono::steady_clock::now() < deadline)) { UninterruptibleSleep(1s); } else { throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what())); @@ -881,13 +885,13 @@ static void ParseError(const UniValue& error, std::string& strPrint, int& nRet) if (err_msg.isStr()) { strPrint += ("error message:\n" + err_msg.get_str()); } - if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) { + if (err_code.isNum() && err_code.getInt<int>() == RPC_WALLET_NOT_SPECIFIED) { strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line."; } } else { strPrint = "error: " + error.write(); } - nRet = abs(error["code"].get_int()); + nRet = abs(error["code"].getInt<int>()); } /** diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 580cc5839a..bcefb3f18e 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -612,7 +612,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) throw std::runtime_error("txid must be hexadecimal string (not '" + prevOut["txid"].get_str() + "')"); } - const int nOut = prevOut["vout"].get_int(); + const int nOut = prevOut["vout"].getInt<int>(); if (nOut < 0) throw std::runtime_error("vout cannot be negative"); diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp index a2f3fca26c..1aeac3cef0 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -15,6 +15,7 @@ #include <streams.h> #include <util/system.h> #include <util/translation.h> +#include <version.h> #include <atomic> #include <cstdio> diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index bc063faed1..92e73d7c2a 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -188,11 +188,14 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) // InitError will have been called with detailed error, which ends up on console return false; } - if (!AppInitSanityChecks()) + + node.kernel = std::make_unique<kernel::Context>(); + if (!AppInitSanityChecks(*node.kernel)) { // InitError will have been called with detailed error, which ends up on console return false; } + if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { #if HAVE_DECL_FORK tfm::format(std::cout, PACKAGE_NAME " starting\n"); diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index aa111b5939..f96353510f 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -16,15 +16,15 @@ #include <unordered_map> -CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, bool fUseWTXID) : - nonce(GetRand(std::numeric_limits<uint64_t>::max())), +CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) : + nonce(GetRand<uint64_t>()), shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) { FillShortTxIDSelector(); //TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase prefilledtxn[0] = {0, block.vtx[0]}; for (size_t i = 1; i < block.vtx.size(); i++) { const CTransaction& tx = *block.vtx[i]; - shorttxids[i - 1] = GetShortID(fUseWTXID ? tx.GetWitnessHash() : tx.GetHash()); + shorttxids[i - 1] = GetShortID(tx.GetWitnessHash()); } } diff --git a/src/blockencodings.h b/src/blockencodings.h index 326db1b4a7..67c4e57156 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -104,7 +104,7 @@ public: // Dummy for deserialization CBlockHeaderAndShortTxIDs() {} - CBlockHeaderAndShortTxIDs(const CBlock& block, bool fUseWTXID); + CBlockHeaderAndShortTxIDs(const CBlock& block); uint64_t GetShortID(const uint256& txhash) const; diff --git a/src/checkqueue.h b/src/checkqueue.h index d0e88a3410..bead6f0c6f 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -66,7 +66,7 @@ private: bool m_request_stop GUARDED_BY(m_mutex){false}; /** Internal function that does bulk of the verification work. */ - bool Loop(bool fMaster) + bool Loop(bool fMaster) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { std::condition_variable& cond = fMaster ? m_master_cv : m_worker_cv; std::vector<T> vChecks; @@ -140,7 +140,7 @@ public: } //! Create a pool of new worker threads. - void StartWorkerThreads(const int threads_num) + void StartWorkerThreads(const int threads_num) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { { LOCK(m_mutex); @@ -159,13 +159,13 @@ public: } //! Wait until execution finishes, and return whether all evaluations were successful. - bool Wait() + bool Wait() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { return Loop(true /* master thread */); } //! Add a batch of checks to the queue - void Add(std::vector<T>& vChecks) + void Add(std::vector<T>& vChecks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { if (vChecks.empty()) { return; @@ -188,7 +188,7 @@ public: } //! Stop all of the worker threads. - void StopWorkerThreads() + void StopWorkerThreads() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { WITH_LOCK(m_mutex, m_request_stop = true); m_worker_cv.notify_all(); diff --git a/src/common/bloom.cpp b/src/common/bloom.cpp index 8b32a6c94a..aa3fcf1ce2 100644 --- a/src/common/bloom.cpp +++ b/src/common/bloom.cpp @@ -239,7 +239,7 @@ bool CRollingBloomFilter::contains(Span<const unsigned char> vKey) const void CRollingBloomFilter::reset() { - nTweak = GetRand(std::numeric_limits<unsigned int>::max()); + nTweak = GetRand<unsigned int>(); nEntriesThisGeneration = 0; nGeneration = 1; std::fill(data.begin(), data.end(), 0); diff --git a/src/compat/glibcxx_sanity.cpp b/src/compat/glibcxx_sanity.cpp deleted file mode 100644 index f2ceeeeb9c..0000000000 --- a/src/compat/glibcxx_sanity.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2009-2018 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include <list> -#include <locale> -#include <stdexcept> -#include <string> - -namespace -{ -// trigger: use ctype<char>::widen to trigger ctype<char>::_M_widen_init(). -// test: convert a char from narrow to wide and back. Verify that the result -// matches the original. -bool sanity_test_widen(char testchar) -{ - const std::ctype<char>& test(std::use_facet<std::ctype<char> >(std::locale())); - return test.narrow(test.widen(testchar), 'b') == testchar; -} - -// trigger: use list::push_back and list::pop_back to trigger _M_hook and -// _M_unhook. -// test: Push a sequence of integers into a list. Pop them off and verify that -// they match the original sequence. -bool sanity_test_list(unsigned int size) -{ - std::list<unsigned int> test; - for (unsigned int i = 0; i != size; ++i) - test.push_back(i + 1); - - if (test.size() != size) - return false; - - while (!test.empty()) { - if (test.back() != test.size()) - return false; - test.pop_back(); - } - return true; -} - -} // namespace - -// trigger: string::at(x) on an empty string to trigger __throw_out_of_range_fmt. -// test: force std::string to throw an out_of_range exception. Verify that -// it's caught correctly. -bool sanity_test_range_fmt() -{ - std::string test; - try { - test.at(1); - } catch (const std::out_of_range&) { - return true; - } catch (...) { - } - return false; -} - -bool glibcxx_sanity_test() -{ - return sanity_test_widen('a') && sanity_test_list(100) && sanity_test_range_fmt(); -} diff --git a/src/compat/sanity.h b/src/compat/sanity.h deleted file mode 100644 index 8e5811f1fd..0000000000 --- a/src/compat/sanity.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2009-2021 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_COMPAT_SANITY_H -#define BITCOIN_COMPAT_SANITY_H - -bool glibcxx_sanity_test(); - -#endif // BITCOIN_COMPAT_SANITY_H diff --git a/src/consensus/params.h b/src/consensus/params.h index 0881f2e388..794e0f5383 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -40,11 +40,11 @@ constexpr bool ValidDeployment(DeploymentPos dep) { return dep < MAX_VERSION_BIT */ struct BIP9Deployment { /** Bit position to select the particular bit in nVersion. */ - int bit; + int bit{28}; /** Start MedianTime for version bits miner confirmation. Can be a date in the past */ - int64_t nStartTime; + int64_t nStartTime{NEVER_ACTIVE}; /** Timeout/expiry MedianTime for the deployment attempt. */ - int64_t nTimeout; + int64_t nTimeout{NEVER_ACTIVE}; /** If lock in occurs, delay activation until at least this block * height. Note that activation will only occur on a retarget * boundary. diff --git a/src/core_io.h b/src/core_io.h index aa1381c374..c91c8199d8 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -6,7 +6,6 @@ #define BITCOIN_CORE_IO_H #include <consensus/amount.h> -#include <attributes.h> #include <string> #include <vector> diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 50a601c684..a2f1f32780 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -19,7 +19,7 @@ public: // This code is adapted from posix_logger.h, which is why it is using vsprintf. // Please do not do this in normal code void Logv(const char * format, va_list ap) override { - if (!LogAcceptCategory(BCLog::LEVELDB)) { + if (!LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug)) { return; } char buffer[500]; @@ -63,7 +63,7 @@ public: assert(p <= limit); base[std::min(bufsize - 1, (int)(p - base))] = '\0'; - LogPrintf("leveldb: %s", base); /* Continued */ + LogPrintLevel(BCLog::LEVELDB, BCLog::Level::Debug, "%s", base); /* Continued */ if (base != buffer) { delete[] base; } @@ -186,7 +186,7 @@ CDBWrapper::~CDBWrapper() bool CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync) { - const bool log_memory = LogAcceptCategory(BCLog::LEVELDB); + const bool log_memory = LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug); double mem_before = 0; if (log_memory) { mem_before = DynamicMemoryUsage() / 1024.0 / 1024; diff --git a/src/deploymentstatus.cpp b/src/deploymentstatus.cpp index ae19a6e40d..3a524af29e 100644 --- a/src/deploymentstatus.cpp +++ b/src/deploymentstatus.cpp @@ -9,8 +9,6 @@ #include <type_traits> -VersionBitsCache g_versionbitscache; - /* Basic sanity checking for BuriedDeployment/DeploymentPos enums and * ValidDeployment check */ diff --git a/src/deploymentstatus.h b/src/deploymentstatus.h index ba5103de74..9f5919f71b 100644 --- a/src/deploymentstatus.h +++ b/src/deploymentstatus.h @@ -10,33 +10,30 @@ #include <limits> -/** Global cache for versionbits deployment status */ -extern VersionBitsCache g_versionbitscache; - /** Determine if a deployment is active for the next block */ -inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::BuriedDeployment dep) +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::BuriedDeployment dep, [[maybe_unused]] VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); return (pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1) >= params.DeploymentHeight(dep); } -inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep) +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); - return ThresholdState::ACTIVE == g_versionbitscache.State(pindexPrev, params, dep); + return ThresholdState::ACTIVE == versionbitscache.State(pindexPrev, params, dep); } /** Determine if a deployment is active for this block */ -inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::BuriedDeployment dep) +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::BuriedDeployment dep, [[maybe_unused]] VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); return index.nHeight >= params.DeploymentHeight(dep); } -inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::DeploymentPos dep) +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); - return DeploymentActiveAfter(index.pprev, params, dep); + return DeploymentActiveAfter(index.pprev, params, dep, versionbitscache); } /** Determine if a deployment is enabled (can ever be active) */ diff --git a/src/external_signer.cpp b/src/external_signer.cpp index 75070899c6..d125fe479b 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -74,11 +74,12 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; - + // parse ExternalSigner master fingerprint + std::vector<unsigned char> parsed_m_fingerprint = ParseHex(m_fingerprint); // Check if signer fingerprint matches any input master key fingerprint auto matches_signer_fingerprint = [&](const PSBTInput& input) { for (const auto& entry : input.hd_keypaths) { - if (m_fingerprint == strprintf("%08x", ReadBE32(entry.second.fingerprint))) return true; + if (parsed_m_fingerprint == MakeUCharSpan(entry.second.fingerprint)) return true; } return false; }; diff --git a/src/hash.h b/src/hash.h index 9f582842c1..0ccef2105f 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H -#include <attributes.h> #include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 3bf165495c..4e7e72b037 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -75,7 +75,7 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni { // Send error reply from json-rpc error object int nStatus = HTTP_INTERNAL_SERVER_ERROR; - int code = find_value(objError, "code").get_int(); + int code = find_value(objError, "code").getInt<int>(); if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 96bee8640d..44937dc523 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -73,21 +73,18 @@ private: Mutex cs; std::condition_variable cond GUARDED_BY(cs); std::deque<std::unique_ptr<WorkItem>> queue GUARDED_BY(cs); - bool running GUARDED_BY(cs); + bool running GUARDED_BY(cs){true}; const size_t maxDepth; public: - explicit WorkQueue(size_t _maxDepth) : running(true), - maxDepth(_maxDepth) + explicit WorkQueue(size_t _maxDepth) : maxDepth(_maxDepth) { } /** Precondition: worker threads have all stopped (they have been joined). */ - ~WorkQueue() - { - } + ~WorkQueue() = default; /** Enqueue a work item */ - bool Enqueue(WorkItem* item) + bool Enqueue(WorkItem* item) EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); if (!running || queue.size() >= maxDepth) { @@ -98,7 +95,7 @@ public: return true; } /** Thread function */ - void Run() + void Run() EXCLUSIVE_LOCKS_REQUIRED(!cs) { while (true) { std::unique_ptr<WorkItem> i; @@ -115,7 +112,7 @@ public: } } /** Interrupt and exit loops */ - void Interrupt() + void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); running = false; @@ -347,10 +344,22 @@ static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue, int worker_num) /** libevent event log callback */ static void libevent_log_cb(int severity, const char *msg) { - if (severity >= EVENT_LOG_WARN) // Log warn messages and higher without debug category - LogPrintf("libevent: %s\n", msg); - else - LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg); + BCLog::Level level; + switch (severity) { + case EVENT_LOG_DEBUG: + level = BCLog::Level::Debug; + break; + case EVENT_LOG_MSG: + level = BCLog::Level::Info; + break; + case EVENT_LOG_WARN: + level = BCLog::Level::Warning; + break; + default: // EVENT_LOG_ERR and others are mapped to error + level = BCLog::Level::Error; + break; + } + LogPrintLevel(BCLog::LIBEVENT, level, "%s\n", msg); } bool InitHTTPServer() diff --git a/src/i2p.cpp b/src/i2p.cpp index e08b5461fe..9b8f83caef 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -242,7 +242,7 @@ std::string Session::Reply::Get(const std::string& key) const template <typename... Args> void Session::Log(const std::string& fmt, const Args&... args) const { - LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...)); + LogPrint(BCLog::I2P, "%s\n", tfm::format(fmt, args...)); } Session::Reply Session::SendRequestAndGetReply(const Sock& sock, @@ -84,7 +84,7 @@ public: * to the listening socket and address. * @return true on success */ - bool Listen(Connection& conn); + bool Listen(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** * Wait for and accept a new incoming connection. @@ -103,7 +103,7 @@ public: * it is set to `false`. Only set if `false` is returned. * @return true on success */ - bool Connect(const CService& to, Connection& conn, bool& proxy_error); + bool Connect(const CService& to, Connection& conn, bool& proxy_error) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); private: /** @@ -172,7 +172,7 @@ private: /** * Check the control socket for errors and possibly disconnect. */ - void CheckControlSock(); + void CheckControlSock() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** * Generate a new destination with the SAM proxy and set `m_private_key` to it. diff --git a/src/index/base.cpp b/src/index/base.cpp index f3c9395928..9f0c1dea24 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -18,8 +18,8 @@ using node::ReadBlockFromDisk; constexpr uint8_t DB_BEST_BLOCK{'B'}; -constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds -constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds +constexpr auto SYNC_LOG_INTERVAL{30s}; +constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s}; template <typename... Args> static void FatalError(const char* fmt, const Args&... args) @@ -130,8 +130,8 @@ void BaseIndex::ThreadSync() if (!m_synced) { auto& consensus_params = Params().GetConsensus(); - int64_t last_log_time = 0; - int64_t last_locator_write_time = 0; + std::chrono::steady_clock::time_point last_log_time{0s}; + std::chrono::steady_clock::time_point last_locator_write_time{0s}; while (true) { if (m_interrupt) { SetBestBlockIndex(pindex); @@ -160,7 +160,7 @@ void BaseIndex::ThreadSync() pindex = pindex_next; } - int64_t current_time = GetTime(); + auto current_time{std::chrono::steady_clock::now()}; if (last_log_time + SYNC_LOG_INTERVAL < current_time) { LogPrintf("Syncing %s with block chain from height %d\n", GetName(), pindex->nHeight); @@ -168,7 +168,7 @@ void BaseIndex::ThreadSync() } if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { - SetBestBlockIndex(pindex); + SetBestBlockIndex(pindex->pprev); last_locator_write_time = current_time; // No need to handle errors in Commit. See rationale above. Commit(); diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h index b1836fe12f..6deff59000 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -64,7 +64,7 @@ public: bool LookupFilter(const CBlockIndex* block_index, BlockFilter& filter_out) const; /** Get a single filter header by block. */ - bool LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out); + bool LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out) EXCLUSIVE_LOCKS_REQUIRED(!m_cs_headers_cache); /** Get a range of filters between two heights on a chain. */ bool LookupFilterRange(int start_height, const CBlockIndex* stop_index, diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp index 69078708f9..687e330fe0 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -12,10 +12,11 @@ #include <undo.h> #include <validation.h> -using node::CCoinsStats; -using node::GetBogoSize; +using kernel::CCoinsStats; +using kernel::GetBogoSize; +using kernel::TxOutSer; + using node::ReadBlockFromDisk; -using node::TxOutSer; using node::UndoReadFromDisk; static constexpr uint8_t DB_BLOCK_HASH{'s'}; @@ -316,28 +317,31 @@ static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVa return db.Read(DBHashKey(block_index->GetBlockHash()), result); } -bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const +std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const { + CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()}; + stats.index_used = true; + DBVal entry; if (!LookUpOne(*m_db, block_index, entry)) { - return false; + return std::nullopt; } - coins_stats.hashSerialized = entry.muhash; - coins_stats.nTransactionOutputs = entry.transaction_output_count; - coins_stats.nBogoSize = entry.bogo_size; - coins_stats.total_amount = entry.total_amount; - coins_stats.total_subsidy = entry.total_subsidy; - coins_stats.total_unspendable_amount = entry.total_unspendable_amount; - coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; - coins_stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; - coins_stats.total_coinbase_amount = entry.total_coinbase_amount; - coins_stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; - coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30; - coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts; - coins_stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; - - return true; + stats.hashSerialized = entry.muhash; + stats.nTransactionOutputs = entry.transaction_output_count; + stats.nBogoSize = entry.bogo_size; + stats.total_amount = entry.total_amount; + stats.total_subsidy = entry.total_subsidy; + stats.total_unspendable_amount = entry.total_unspendable_amount; + stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; + stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; + stats.total_coinbase_amount = entry.total_coinbase_amount; + stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; + stats.total_unspendables_bip30 = entry.total_unspendables_bip30; + stats.total_unspendables_scripts = entry.total_unspendables_scripts; + stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; + + return stats; } bool CoinStatsIndex::Init() diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index 6f53bb74fb..cae052d913 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -9,7 +9,7 @@ #include <crypto/muhash.h> #include <flatfile.h> #include <index/base.h> -#include <node/coinstats.h> +#include <kernel/coinstats.h> /** * CoinStatsIndex maintains statistics on the UTXO set. @@ -56,7 +56,7 @@ public: explicit CoinStatsIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); // Look up stats for a specific block using CBlockIndex - bool LookUpStats(const CBlockIndex* block_index, node::CCoinsStats& coins_stats) const; + std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex* block_index) const; }; /// The global UTXO set hash object. diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index e1d807f39a..97c11c4383 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -52,7 +52,7 @@ TxIndex::TxIndex(size_t n_cache_size, bool f_memory, bool f_wipe) : m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) {} -TxIndex::~TxIndex() {} +TxIndex::~TxIndex() = default; bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) { diff --git a/src/init.cpp b/src/init.cpp index 713598f411..d0fd6074b1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -9,12 +9,13 @@ #include <init.h> +#include <kernel/checks.h> + #include <addrman.h> #include <banman.h> #include <blockfilter.h> #include <chain.h> #include <chainparams.h> -#include <compat/sanity.h> #include <consensus/amount.h> #include <deploymentstatus.h> #include <fs.h> @@ -86,7 +87,6 @@ #include <vector> #ifndef WIN32 -#include <attributes.h> #include <cerrno> #include <signal.h> #include <sys/stat.h> @@ -306,7 +306,7 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - init::UnsetGlobals(); + node.kernel.reset(); node.mempool.reset(); node.fee_estimator.reset(); node.chainman.reset(); @@ -320,7 +320,6 @@ void Shutdown(NodeContext& node) LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } - node.args = nullptr; LogPrintf("%s: done\n", __func__); } @@ -471,7 +470,7 @@ void SetupServerArgs(ArgsManager& argsman) // TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from // https://github.com/bitcoin/bitcoin/pull/23542 have become widespread. argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); - argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -570,6 +569,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC); + argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); @@ -1028,10 +1028,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb nMaxTipAge = args.GetIntArg("-maxtipage", DEFAULT_MAX_TIP_AGE); - if (args.IsArgSet("-proxy") && args.GetArg("-proxy", "").empty()) { - return InitError(_("No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.")); - } - if (args.GetBoolArg("-reindex-chainstate", false)) { // indexes that must be deactivated to prevent index corruption, see #24630 if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { @@ -1095,13 +1091,24 @@ static bool LockDataDirectory(bool probeOnly) return true; } -bool AppInitSanityChecks() +bool AppInitSanityChecks(const kernel::Context& kernel) { // ********************************************************* Step 4: sanity checks + auto maybe_error = kernel::SanityChecks(kernel); + + if (maybe_error.has_value()) { + switch (maybe_error.value()) { + case kernel::SanityCheckError::ERROR_ECC: + InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); + break; + case kernel::SanityCheckError::ERROR_RANDOM: + InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting.")); + break; + case kernel::SanityCheckError::ERROR_CHRONO: + InitError(Untranslated("Clock epoch mismatch. Aborting.")); + break; + } // no default case, so the compiler can warn about missing cases - init::SetGlobals(); - - if (!init::SanityChecks()) { return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } @@ -1280,8 +1287,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.banman); node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); - node.connman = std::make_unique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), - GetRand(std::numeric_limits<uint64_t>::max()), + node.connman = std::make_unique<CConnman>(GetRand<uint64_t>(), + GetRand<uint64_t>(), *node.addrman, *node.netgroupman, args.GetBoolArg("-networkactive", true)); assert(!node.fee_estimator); @@ -1424,7 +1431,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), mempool_check_ratio); - node.chainman = std::make_unique<ChainstateManager>(); + const ChainstateManager::Options chainman_opts{ + chainparams, + GetAdjustedTime, + }; + node.chainman = std::make_unique<ChainstateManager>(chainman_opts); ChainstateManager& chainman = *node.chainman; const bool fReset = fReindex; @@ -1438,7 +1449,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) chainman, Assert(node.mempool.get()), fPruneMode, - chainparams.GetConsensus(), fReindexChainState, cache_sizes.block_tree_db, cache_sizes.coins_db, @@ -1485,7 +1495,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) break; case ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED: strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), - chainparams.GetConsensus().SegwitHeight); + chainman.GetConsensus().SegwitHeight); break; case ChainstateLoadingError::SHUTDOWN_PROBED: break; @@ -1502,10 +1512,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) maybe_verify_error = VerifyLoadedChainstate(chainman, fReset, fReindexChainState, - chainparams.GetConsensus(), check_blocks, - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - /*get_unix_time_seconds=*/static_cast<int64_t(*)()>(GetTime)); + args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); maybe_verify_error = ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; @@ -1561,7 +1569,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) ChainstateManager& chainman = *Assert(node.chainman); assert(!node.peerman); - node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), + node.peerman = PeerManager::make(*node.connman, *node.addrman, node.banman.get(), chainman, *node.mempool, ignores_incoming_txs); RegisterValidationInterface(node.peerman.get()); @@ -1683,8 +1691,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) chain_active_height = chainman.ActiveChain().Height(); if (tip_info) { tip_info->block_height = chain_active_height; - tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : Params().GenesisBlock().GetBlockTime(); - tip_info->verification_progress = GuessVerificationProgress(Params().TxData(), chainman.ActiveChain().Tip()); + tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime(); + tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip()); } if (tip_info && chainman.m_best_header) { tip_info->header_height = chainman.m_best_header->nHeight; diff --git a/src/init.h b/src/init.h index 2250ae20a0..e8e6a55eba 100644 --- a/src/init.h +++ b/src/init.h @@ -19,6 +19,9 @@ class ArgsManager; namespace interfaces { struct BlockAndHeaderTipInfo; } +namespace kernel { +struct Context; +} namespace node { struct NodeContext; } // namespace node @@ -43,11 +46,11 @@ bool AppInitBasicSetup(const ArgsManager& args); */ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandbox = true); /** - * Initialization sanity checks: ecc init, sanity checks, dir lock. + * Initialization sanity checks. * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitParameterInteraction should have been called. */ -bool AppInitSanityChecks(); +bool AppInitSanityChecks(const kernel::Context& kernel); /** * Lock bitcoin core data directory. * @note This should only be done after daemonization. Do not call Shutdown() if this function fails. diff --git a/src/init/common.cpp b/src/init/common.cpp index eac6732968..d4e45454d2 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -7,62 +7,19 @@ #endif #include <clientversion.h> -#include <compat/sanity.h> -#include <crypto/sha256.h> #include <fs.h> -#include <key.h> #include <logging.h> #include <node/ui_interface.h> -#include <pubkey.h> -#include <random.h> #include <tinyformat.h> #include <util/system.h> #include <util/time.h> #include <util/translation.h> #include <algorithm> -#include <memory> #include <string> #include <vector> -static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; - namespace init { -void SetGlobals() -{ - std::string sha256_algo = SHA256AutoDetect(); - LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); - RandomInit(); - ECC_Start(); - globalVerifyHandle.reset(new ECCVerifyHandle()); -} - -void UnsetGlobals() -{ - globalVerifyHandle.reset(); - ECC_Stop(); -} - -bool SanityChecks() -{ - if (!ECC_InitSanityCheck()) { - return InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); - } - - if (!glibcxx_sanity_test()) - return false; - - if (!Random_SanityCheck()) { - return InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting.")); - } - - if (!ChronoSanityCheck()) { - return InitError(Untranslated("Clock epoch mismatch. Aborting.")); - } - - return true; -} - void AddLoggingArgs(ArgsManager& argsman) { argsman.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/init/common.h b/src/init/common.h index fc4bc1b280..2c7f485908 100644 --- a/src/init/common.h +++ b/src/init/common.h @@ -11,13 +11,6 @@ class ArgsManager; namespace init { -void SetGlobals(); -void UnsetGlobals(); -/** - * Ensure a usable environment with all - * necessary library support. - */ -bool SanityChecks(); void AddLoggingArgs(ArgsManager& args); void SetLoggingOptions(const ArgsManager& args); void SetLoggingCategories(const ArgsManager& args); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index c4dc303dd5..2c31e12ada 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -5,12 +5,13 @@ #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H -#include <consensus/amount.h> -#include <net.h> // For NodeId -#include <net_types.h> // For banmap_t -#include <netaddress.h> // For Network -#include <netbase.h> // For ConnectionDirection +#include <consensus/amount.h> // For CAmount +#include <net.h> // For NodeId +#include <net_types.h> // For banmap_t +#include <netaddress.h> // For Network +#include <netbase.h> // For ConnectionDirection #include <support/allocators/secure.h> // For SecureString +#include <util/settings.h> // For util::SettingsValue #include <util/translation.h> #include <functional> @@ -97,6 +98,24 @@ public: //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; + //! Return whether a particular setting in <datadir>/settings.json is or + //! would be ignored because it is also specified in the command line. + virtual bool isSettingIgnored(const std::string& name) = 0; + + //! Return setting value from <datadir>/settings.json or bitcoin.conf. + virtual util::SettingsValue getPersistentSetting(const std::string& name) = 0; + + //! Update a setting in <datadir>/settings.json. + virtual void updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Force a setting value to be applied, overriding any other configuration + //! source, but not being persisted. + virtual void forceSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Clear all settings in <datadir>/settings.json and store a backup of + //! previous settings in <datadir>/settings.json.bak. + virtual void resetSettings() = 0; + //! Map port. virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h new file mode 100644 index 0000000000..575d94e2e9 --- /dev/null +++ b/src/kernel/chainstatemanager_opts.h @@ -0,0 +1,23 @@ +// 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_KERNEL_CHAINSTATEMANAGER_OPTS_H +#define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H + +#include <cstdint> +#include <functional> + +class CChainParams; + +/** + * An options struct for `ChainstateManager`, more ergonomically referred to as + * `ChainstateManager::Options` due to the using-declaration in + * `ChainstateManager`. + */ +struct ChainstateManagerOpts { + const CChainParams& chainparams; + const std::function<int64_t()> adjusted_time_callback{nullptr}; +}; + +#endif // BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp new file mode 100644 index 0000000000..2a1dd3bfa2 --- /dev/null +++ b/src/kernel/checks.cpp @@ -0,0 +1,30 @@ +// 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 <kernel/checks.h> + +#include <key.h> +#include <random.h> +#include <util/time.h> + +namespace kernel { + +std::optional<SanityCheckError> SanityChecks(const Context&) +{ + if (!ECC_InitSanityCheck()) { + return SanityCheckError::ERROR_ECC; + } + + if (!Random_SanityCheck()) { + return SanityCheckError::ERROR_RANDOM; + } + + if (!ChronoSanityCheck()) { + return SanityCheckError::ERROR_CHRONO; + } + + return std::nullopt; +} + +} diff --git a/src/kernel/checks.h b/src/kernel/checks.h new file mode 100644 index 0000000000..80b207f607 --- /dev/null +++ b/src/kernel/checks.h @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KERNEL_CHECKS_H +#define BITCOIN_KERNEL_CHECKS_H + +#include <optional> + +namespace kernel { + +struct Context; + +enum class SanityCheckError { + ERROR_ECC, + ERROR_RANDOM, + ERROR_CHRONO, +}; + +/** + * Ensure a usable environment with all necessary library support. + */ +std::optional<SanityCheckError> SanityChecks(const Context&); + +} + +#endif // BITCOIN_KERNEL_CHECKS_H diff --git a/src/node/coinstats.cpp b/src/kernel/coinstats.cpp index eed43a1bc7..f380871627 100644 --- a/src/node/coinstats.cpp +++ b/src/kernel/coinstats.cpp @@ -1,14 +1,12 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// 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/coinstats.h> +#include <kernel/coinstats.h> #include <coins.h> #include <crypto/muhash.h> #include <hash.h> -#include <index/coinstatsindex.h> #include <serialize.h> #include <uint256.h> #include <util/overflow.h> @@ -17,7 +15,12 @@ #include <map> -namespace node { +namespace kernel { + +CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash) + : nHeight(block_height), + hashBlock(block_hash) {} + // Database-independent metric indicating the UTXO set size uint64_t GetBogoSize(const CScript& script_pub_key) { @@ -93,24 +96,11 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u //! Calculate statistics about the unspent transaction output set template <typename T> -static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex) +static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) { std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); assert(pcursor); - if (!pindex) { - LOCK(cs_main); - pindex = blockman.LookupBlockIndex(view->GetBestBlock()); - } - stats.nHeight = Assert(pindex)->nHeight; - stats.hashBlock = pindex->GetBlockHash(); - - // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested - if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) { - stats.index_used = true; - return g_coin_stats_index->LookUpStats(pindex, stats); - } - PrepareHash(hash_obj, stats); uint256 prevkey; @@ -141,25 +131,36 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& FinalizeHash(hash_obj, stats); stats.nDiskSize = view->EstimateSize(); + return true; } -bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex) +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point) { - switch (stats.m_hash_type) { - case(CoinStatsHashType::HASH_SERIALIZED): { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex); - } - case(CoinStatsHashType::MUHASH): { - MuHash3072 muhash; - return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex); - } - case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex); + CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()}; + + bool success = [&]() -> bool { + switch (hash_type) { + case(CoinStatsHashType::HASH_SERIALIZED): { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + return ComputeUTXOStats(view, stats, ss, interruption_point); + } + case(CoinStatsHashType::MUHASH): { + MuHash3072 muhash; + return ComputeUTXOStats(view, stats, muhash, interruption_point); + } + case(CoinStatsHashType::NONE): { + return ComputeUTXOStats(view, stats, nullptr, interruption_point); + } + } // no default case, so the compiler can warn about missing cases + assert(false); + }(); + + if (!success) { + return std::nullopt; } - } // no default case, so the compiler can warn about missing cases - assert(false); + return stats; } // The legacy hash serializes the hashBlock @@ -182,4 +183,5 @@ static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) stats.hashSerialized = out; } static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} -} // namespace node + +} // namespace kernel diff --git a/src/node/coinstats.h b/src/kernel/coinstats.h index aa771b18b0..a15957233f 100644 --- a/src/node/coinstats.h +++ b/src/kernel/coinstats.h @@ -1,10 +1,9 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// 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_COINSTATS_H -#define BITCOIN_NODE_COINSTATS_H +#ifndef BITCOIN_KERNEL_COINSTATS_H +#define BITCOIN_KERNEL_COINSTATS_H #include <chain.h> #include <coins.h> @@ -20,7 +19,7 @@ namespace node { class BlockManager; } // namespace node -namespace node { +namespace kernel { enum class CoinStatsHashType { HASH_SERIALIZED, MUHASH, @@ -28,9 +27,6 @@ enum class CoinStatsHashType { }; struct CCoinsStats { - //! Which hash type to use - const CoinStatsHashType m_hash_type; - int nHeight{0}; uint256 hashBlock{}; uint64_t nTransactions{0}; @@ -44,8 +40,6 @@ struct CCoinsStats { //! The number of coins contained. uint64_t coins_count{0}; - //! Signals if the coinstatsindex should be used (when available). - bool index_requested{true}; //! Signals if the coinstatsindex was used to retrieve the statistics. bool index_used{false}; @@ -70,15 +64,15 @@ struct CCoinsStats { //! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block CAmount total_unspendables_unclaimed_rewards{0}; - CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} + CCoinsStats() = default; + CCoinsStats(int block_height, const uint256& block_hash); }; -//! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr); - uint64_t GetBogoSize(const CScript& script_pub_key); CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin); -} // namespace node -#endif // BITCOIN_NODE_COINSTATS_H +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point = {}); +} // namespace kernel + +#endif // BITCOIN_KERNEL_COINSTATS_H diff --git a/src/kernel/context.cpp b/src/kernel/context.cpp new file mode 100644 index 0000000000..15413c1840 --- /dev/null +++ b/src/kernel/context.cpp @@ -0,0 +1,33 @@ +// 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 <kernel/context.h> + +#include <crypto/sha256.h> +#include <key.h> +#include <logging.h> +#include <pubkey.h> +#include <random.h> + +#include <string> + + +namespace kernel { + +Context::Context() +{ + std::string sha256_algo = SHA256AutoDetect(); + LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); + RandomInit(); + ECC_Start(); + ecc_verify_handle.reset(new ECCVerifyHandle()); +} + +Context::~Context() +{ + ecc_verify_handle.reset(); + ECC_Stop(); +} + +} // namespace kernel diff --git a/src/kernel/context.h b/src/kernel/context.h new file mode 100644 index 0000000000..0a08511564 --- /dev/null +++ b/src/kernel/context.h @@ -0,0 +1,31 @@ +// 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_KERNEL_CONTEXT_H +#define BITCOIN_KERNEL_CONTEXT_H + +#include <memory> + +class ECCVerifyHandle; + +namespace kernel { +//! Context struct holding the kernel library's logically global state, and +//! passed to external libbitcoin_kernel functions which need access to this +//! state. The kernel libary API is a work in progress, so state organization +//! and member list will evolve over time. +//! +//! State stored directly in this struct should be simple. More complex state +//! should be stored to std::unique_ptr members pointing to opaque types. +struct Context { + std::unique_ptr<ECCVerifyHandle> ecc_verify_handle; + + //! Declare default constructor and destructor that are not inline, so code + //! instantiating the kernel::Context struct doesn't need to #include class + //! definitions for all the unique_ptr members. + Context(); + ~Context(); +}; +} // namespace kernel + +#endif // BITCOIN_KERNEL_CONTEXT_H diff --git a/src/key.cpp b/src/key.cpp index d1d521f97d..9b0971a2dd 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -340,11 +340,11 @@ bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { return key.Derive(out.key, out.chaincode, _nChild, chaincode); } -void CExtKey::SetSeed(Span<const uint8_t> seed) +void CExtKey::SetSeed(Span<const std::byte> seed) { static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; std::vector<unsigned char, secure_allocator<unsigned char>> vout(64); - CHMAC_SHA512{hashkey, sizeof(hashkey)}.Write(seed.data(), seed.size()).Finalize(vout.data()); + CHMAC_SHA512{hashkey, sizeof(hashkey)}.Write(UCharCast(seed.data()), seed.size()).Finalize(vout.data()); key.Set(vout.data(), vout.data() + 32, true); memcpy(chaincode.begin(), vout.data() + 32, 32); nDepth = 0; @@ -85,7 +85,7 @@ public: //! Simple read-only vector-like interface. unsigned int size() const { return (fValid ? keydata.size() : 0); } - const unsigned char* data() const { return keydata.data(); } + const std::byte* data() const { return reinterpret_cast<const std::byte*>(keydata.data()); } const unsigned char* begin() const { return keydata.data(); } const unsigned char* end() const { return keydata.data() + size(); } @@ -178,7 +178,7 @@ struct CExtKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; - void SetSeed(Span<const uint8_t> seed); + void SetSeed(Span<const std::byte> seed); }; /** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ diff --git a/src/logging.cpp b/src/logging.cpp index a5e5fdb4cd..1e2c1d5a77 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -171,7 +171,7 @@ const CLogCategoryDesc LogCategories[] = bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) { - if (str == "") { + if (str.empty()) { flag = BCLog::ALL; return true; } @@ -184,6 +184,91 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) return false; } +std::string LogLevelToStr(BCLog::Level level) +{ + switch (level) { + case BCLog::Level::None: + return "none"; + case BCLog::Level::Debug: + return "debug"; + case BCLog::Level::Info: + return "info"; + case BCLog::Level::Warning: + return "warning"; + case BCLog::Level::Error: + return "error"; + } + assert(false); +} + +std::string LogCategoryToStr(BCLog::LogFlags category) +{ + // Each log category string representation should sync with LogCategories + switch (category) { + case BCLog::LogFlags::NONE: + return "none"; + case BCLog::LogFlags::NET: + return "net"; + case BCLog::LogFlags::TOR: + return "tor"; + case BCLog::LogFlags::MEMPOOL: + return "mempool"; + case BCLog::LogFlags::HTTP: + return "http"; + case BCLog::LogFlags::BENCH: + return "bench"; + case BCLog::LogFlags::ZMQ: + return "zmq"; + case BCLog::LogFlags::WALLETDB: + return "walletdb"; + case BCLog::LogFlags::RPC: + return "rpc"; + case BCLog::LogFlags::ESTIMATEFEE: + return "estimatefee"; + case BCLog::LogFlags::ADDRMAN: + return "addrman"; + case BCLog::LogFlags::SELECTCOINS: + return "selectcoins"; + case BCLog::LogFlags::REINDEX: + return "reindex"; + case BCLog::LogFlags::CMPCTBLOCK: + return "cmpctblock"; + case BCLog::LogFlags::RAND: + return "rand"; + case BCLog::LogFlags::PRUNE: + return "prune"; + case BCLog::LogFlags::PROXY: + return "proxy"; + case BCLog::LogFlags::MEMPOOLREJ: + return "mempoolrej"; + case BCLog::LogFlags::LIBEVENT: + return "libevent"; + case BCLog::LogFlags::COINDB: + return "coindb"; + case BCLog::LogFlags::QT: + return "qt"; + case BCLog::LogFlags::LEVELDB: + return "leveldb"; + case BCLog::LogFlags::VALIDATION: + return "validation"; + case BCLog::LogFlags::I2P: + return "i2p"; + case BCLog::LogFlags::IPC: + return "ipc"; +#ifdef DEBUG_LOCKCONTENTION + case BCLog::LogFlags::LOCK: + return "lock"; +#endif + case BCLog::LogFlags::UTIL: + return "util"; + case BCLog::LogFlags::BLOCKSTORE: + return "blockstorage"; + case BCLog::LogFlags::ALL: + return "all"; + } + assert(false); +} + std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const { // Sort log categories by alphabetical order. @@ -249,17 +334,38 @@ namespace BCLog { } } // namespace BCLog -void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line) +void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level) { StdLockGuard scoped_lock(m_cs); std::string str_prefixed = LogEscapeMessage(str); + if ((category != LogFlags::NONE || level != Level::None) && m_started_new_line) { + std::string s{"["}; + + if (category != LogFlags::NONE) { + s += LogCategoryToStr(category); + } + + if (category != LogFlags::NONE && level != Level::None) { + // Only add separator if both flag and level are not NONE + s += ":"; + } + + if (level != Level::None) { + s += LogLevelToStr(level); + } + + s += "] "; + str_prefixed.insert(0, s); + } + if (m_log_sourcelocations && m_started_new_line) { str_prefixed.insert(0, "[" + RemovePrefix(source_file, "./") + ":" + ToString(source_line) + "] [" + logging_function + "] "); } if (m_log_threadnames && m_started_new_line) { - str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] "); + const auto threadname = util::ThreadGetInternalName(); + str_prefixed.insert(0, "[" + (threadname.empty() ? "unknown" : threadname) + "] "); } str_prefixed = LogTimestampStr(str_prefixed); diff --git a/src/logging.h b/src/logging.h index ae8cad906c..8a896b6b33 100644 --- a/src/logging.h +++ b/src/logging.h @@ -67,6 +67,13 @@ namespace BCLog { BLOCKSTORE = (1 << 26), ALL = ~(uint32_t)0, }; + enum class Level { + Debug = 0, + None = 1, + Info = 2, + Warning = 3, + Error = 4, + }; class Logger { @@ -105,7 +112,7 @@ namespace BCLog { std::atomic<bool> m_reopen_file{false}; /** Send a string to the log output */ - void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line); + void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level); /** Returns whether logs will be written to any output */ bool Enabled() const @@ -159,9 +166,14 @@ namespace BCLog { BCLog::Logger& LogInstance(); -/** Return true if log accepts specified category */ -static inline bool LogAcceptCategory(BCLog::LogFlags category) +/** Return true if log accepts specified category, at the specified level. */ +static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level) { + // Log messages at Warning and Error level unconditionally, so that + // important troubleshooting information doesn't get lost. + if (level >= BCLog::Level::Warning) { + return true; + } return LogInstance().WillLogCategory(category); } @@ -173,7 +185,7 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str); // peer can fill up a user's disk with debug.log entries. template <typename... Args> -static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const char* fmt, const Args&... args) +static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags flag, const BCLog::Level level, const char* fmt, const Args&... args) { if (LogInstance().Enabled()) { std::string log_msg; @@ -183,19 +195,29 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st /* Original format string will have newline so don't add one here */ log_msg = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + fmt; } - LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line); + LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line, flag, level); } } -#define LogPrintf(...) LogPrintf_(__func__, __FILE__, __LINE__, __VA_ARGS__) + +#define LogPrintLevel_(category, level, ...) LogPrintf_(__func__, __FILE__, __LINE__, category, level, __VA_ARGS__) + +#define LogPrintf(...) LogPrintLevel_(BCLog::LogFlags::NONE, BCLog::Level::None, __VA_ARGS__) // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging for the category is not enabled. -#define LogPrint(category, ...) \ - do { \ - if (LogAcceptCategory((category))) { \ - LogPrintf(__VA_ARGS__); \ - } \ +#define LogPrint(category, ...) \ + do { \ + if (LogAcceptCategory((category), BCLog::Level::Debug)) { \ + LogPrintLevel_(category, BCLog::Level::None, __VA_ARGS__); \ + } \ + } while (0) + +#define LogPrintLevel(category, level, ...) \ + do { \ + if (LogAcceptCategory((category), (level))) { \ + LogPrintLevel_(category, level, __VA_ARGS__); \ + } \ } while (0) #endif // BITCOIN_LOGGING_H diff --git a/src/net.cpp b/src/net.cpp index 3820ee1fd0..82b5a69eb5 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -25,6 +25,7 @@ #include <protocol.h> #include <random.h> #include <scheduler.h> +#include <util/designator.h> #include <util/sock.h> #include <util/strencodings.h> #include <util/syscall_sandbox.h> @@ -430,7 +431,7 @@ static CAddress GetBindAddress(SOCKET sock) if (!getsockname(sock, (struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind); } else { - LogPrint(BCLog::NET, "Warning: getsockname failed\n"); + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); } } return addr_bind; @@ -454,9 +455,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo } /// debug print - LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n", - pszDest ? pszDest : addrConnect.ToString(), - pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0); + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n", + pszDest ? pszDest : addrConnect.ToString(), + pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0); // Resolve const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) : @@ -1101,12 +1102,20 @@ bool CConnman::AttemptToEvictConnection() continue; if (node->fDisconnect) continue; - NodeEvictionCandidate candidate = {node->GetId(), node->m_connected, node->m_min_ping_time, - node->m_last_block_time, node->m_last_tx_time, - HasAllDesirableServiceFlags(node->nServices), - node->m_relays_txs.load(), node->m_bloom_filter_loaded.load(), - node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(), - node->ConnectedThroughNetwork()}; + NodeEvictionCandidate candidate{ + Desig(id) node->GetId(), + Desig(m_connected) node->m_connected, + Desig(m_min_ping_time) node->m_min_ping_time, + Desig(m_last_block_time) node->m_last_block_time, + Desig(m_last_tx_time) node->m_last_tx_time, + Desig(fRelevantServices) HasAllDesirableServiceFlags(node->nServices), + Desig(m_relay_txs) node->m_relays_txs.load(), + Desig(fBloomFilter) node->m_bloom_filter_loaded.load(), + Desig(nKeyedNetGroup) node->nKeyedNetGroup, + Desig(prefer_evict) node->m_prefer_evict, + Desig(m_is_local) node->addr.IsLocal(), + Desig(m_network) node->ConnectedThroughNetwork(), + }; vEvictionCandidates.push_back(candidate); } } @@ -1140,7 +1149,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { - LogPrintf("Warning: Unknown socket family\n"); + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Unknown socket family\n"); } else { addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE}; } @@ -1864,7 +1873,13 @@ bool CConnman::GetTryNewOutboundPeer() const void CConnman::SetTryNewOutboundPeer(bool flag) { m_try_another_outbound_peer = flag; - LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n", flag ? "true" : "false"); + LogPrint(BCLog::NET, "setting try another outbound peer=%s\n", flag ? "true" : "false"); +} + +void CConnman::StartExtraBlockRelayPeers() +{ + LogPrint(BCLog::NET, "enabling extra block-relay-only peers\n"); + m_start_extra_block_relay_peers = true; } // Return the number of peers we have over our outbound connection limit @@ -2160,7 +2175,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) if (fFeeler) { // Add small amount of random noise before connection to avoid synchronization. - int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000); + int randsleep = GetRand<int>(FEELER_SLEEP_WINDOW * 1000); if (!interruptNet.sleep_for(std::chrono::milliseconds(randsleep))) return; LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); @@ -2391,15 +2406,15 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, socklen_t len = sizeof(sockaddr); if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - strError = strprintf(Untranslated("Error: Bind address family for %s not supported"), addrBind.ToString()); - LogPrintf("%s\n", strError.original); + strError = strprintf(Untranslated("Bind address family for %s not supported"), addrBind.ToString()); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } std::unique_ptr<Sock> sock = CreateSock(addrBind); if (!sock) { - strError = strprintf(Untranslated("Error: Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError())); - LogPrintf("%s\n", strError.original); + strError = strprintf(Untranslated("Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } @@ -2435,7 +2450,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), PACKAGE_NAME); else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); - LogPrintf("%s\n", strError.original); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } LogPrintf("Bound to %s\n", addrBind.ToString()); @@ -2443,8 +2458,8 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, // Listen for incoming connections if (listen(sock->Get(), SOMAXCONN) == SOCKET_ERROR) { - strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); - LogPrintf("%s\n", strError.original); + strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } @@ -2685,7 +2700,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) class CNetCleanup { public: - CNetCleanup() {} + CNetCleanup() = default; ~CNetCleanup() { @@ -13,7 +13,6 @@ #include <crypto/siphash.h> #include <hash.h> #include <i2p.h> -#include <logging.h> #include <net_permissions.h> #include <netaddress.h> #include <netbase.h> @@ -613,7 +612,7 @@ public: * @return True if the peer should stay connected, * False if the peer should be disconnected from. */ - bool ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete); + bool ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete) EXCLUSIVE_LOCKS_REQUIRED(!cs_vRecv); void SetCommonVersion(int greatest_common_version) { @@ -625,9 +624,9 @@ public: return m_greatest_common_version; } - CService GetAddrLocal() const LOCKS_EXCLUDED(m_addr_local_mutex); + CService GetAddrLocal() const EXCLUSIVE_LOCKS_REQUIRED(!m_addr_local_mutex); //! May not be called more than once - void SetAddrLocal(const CService& addrLocalIn) LOCKS_EXCLUDED(m_addr_local_mutex); + void SetAddrLocal(const CService& addrLocalIn) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_local_mutex); CNode* AddRef() { @@ -640,9 +639,9 @@ public: nRefCount--; } - void CloseSocketDisconnect(); + void CloseSocketDisconnect() EXCLUSIVE_LOCKS_REQUIRED(!m_sock_mutex); - void CopyStats(CNodeStats& stats); + void CopyStats(CNodeStats& stats) EXCLUSIVE_LOCKS_REQUIRED(!m_subver_mutex, !m_addr_local_mutex, !cs_vSend, !cs_vRecv); ServiceFlags GetLocalServices() const { @@ -761,7 +760,7 @@ public: bool m_i2p_accept_incoming; }; - void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex) + void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_total_bytes_sent_mutex) { AssertLockNotHeld(m_total_bytes_sent_mutex); @@ -795,7 +794,8 @@ public: bool network_active = true); ~CConnman(); - bool Start(CScheduler& scheduler, const Options& options) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); + + bool Start(CScheduler& scheduler, const Options& options) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !m_added_nodes_mutex, !m_addr_fetches_mutex, !mutexMsgProc); void StopThreads(); void StopNodes(); @@ -805,7 +805,7 @@ public: StopNodes(); }; - void Interrupt(); + void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); @@ -857,10 +857,7 @@ public: void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer() const; - void StartExtraBlockRelayPeers() { - LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n"); - m_start_extra_block_relay_peers = true; - } + void StartExtraBlockRelayPeers(); // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set @@ -872,9 +869,9 @@ public: // Count the number of block-relay-only peers we have over our limit. int GetExtraBlockRelayCount() const; - bool AddNode(const std::string& node); - bool RemoveAddedNode(const std::string& node); - std::vector<AddedNodeInfo> GetAddedNodeInfo() const; + bool AddNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + std::vector<AddedNodeInfo> GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); /** * Attempts to open a connection. Currently only used from tests. @@ -927,7 +924,7 @@ public: unsigned int GetReceiveFloodSize() const; - void WakeMessageHandler(); + void WakeMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); /** Return true if we should disconnect the peer for failing an inactivity check. */ bool ShouldRunInactivityChecks(const CNode& node, std::chrono::seconds now) const; @@ -954,11 +951,11 @@ private: bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const Options& options); - void ThreadOpenAddedConnections(); - void AddAddrFetch(const std::string& strDest); - void ProcessAddrFetch(); - void ThreadOpenConnections(std::vector<std::string> connect); - void ThreadMessageHandler(); + void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); + void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); + void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex); + void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket& hListenSocket); @@ -1009,7 +1006,7 @@ private: /** * Check connected and listening sockets for IO readiness and process them accordingly. */ - void SocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); + void SocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); /** * Do the read/write for connected sockets that are ready for IO. @@ -1023,7 +1020,7 @@ private: const std::set<SOCKET>& recv_set, const std::set<SOCKET>& send_set, const std::set<SOCKET>& error_set) - EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); /** * Accept incoming connections, one from each read-ready listening socket. @@ -1031,8 +1028,8 @@ private: */ void SocketHandlerListening(const std::set<SOCKET>& recv_set); - void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); - void ThreadDNSAddressSeed(); + void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); + void ThreadDNSAddressSeed() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_nodes_mutex); uint64_t CalculateKeyedNetGroup(const CAddress& ad) const; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 478368b673..30d5548385 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -21,6 +21,7 @@ #include <node/blockstorage.h> #include <policy/fees.h> #include <policy/policy.h> +#include <policy/settings.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <random.h> @@ -175,6 +176,8 @@ static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1}; * based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR * is exempt from this limit). */ static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND}; +/** The compactblocks version we support. See BIP 152. */ +static constexpr uint64_t CMPCTBLOCKS_VERSION{2}; // Internal stuff namespace { @@ -237,36 +240,62 @@ struct Peer { /** Whether this peer relays txs via wtxid */ std::atomic<bool> m_wtxid_relay{false}; + /** The feerate in the most recent BIP133 `feefilter` message sent to the peer. + * It is *not* a p2p protocol violation for the peer to send us + * transactions with a lower fee rate than this. See BIP133. */ + CAmount m_fee_filter_sent{0}; + /** Timestamp after which we will send the next BIP133 `feefilter` message + * to the peer. */ + std::chrono::microseconds m_next_send_feefilter{0}; struct TxRelay { mutable RecursiveMutex m_bloom_filter_mutex; - // We use m_relay_txs for two purposes - - // a) it allows us to not relay tx invs before receiving the peer's version message - // b) the peer may tell us in its version message that we should not relay tx invs - // unless it loads a bloom filter. + /** 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. */ 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}; mutable RecursiveMutex m_tx_inventory_mutex; + /** A filter of all the txids and wtxids that the peer has announced to + * us or we have announced to the peer. We use this to avoid announcing + * the same txid/wtxid to a peer that already has the transaction. */ CRollingBloomFilter m_tx_inventory_known_filter GUARDED_BY(m_tx_inventory_mutex){50000, 0.000001}; - // Set of transaction ids we still have to announce. - // They are sorted by the mempool before relay, so the order is not important. + /** Set of transaction ids we still have to announce (txid for + * non-wtxid-relay peers, wtxid for wtxid-relay peers). We use the + * mempool to sort transactions in dependency order before relay, so + * this does not have to be sorted. */ std::set<uint256> m_tx_inventory_to_send; - // Used for BIP35 mempool sending + /** Whether the peer has requested us to send our complete mempool. Only + * permitted if the peer has NetPermissionFlags::Mempool. See BIP35. */ bool m_send_mempool GUARDED_BY(m_tx_inventory_mutex){false}; - // Last time a "MEMPOOL" request was serviced. + /** The last time a BIP35 `mempool` request was serviced. */ std::atomic<std::chrono::seconds> m_last_mempool_req{0s}; + /** The next time after which we will send an `inv` message containing + * transaction announcements to this peer. */ std::chrono::microseconds m_next_inv_send_time{0}; - /** Minimum fee rate with which to filter inv's to this node */ + /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ std::atomic<CAmount> m_fee_filter_received{0}; - CAmount m_fee_filter_sent{0}; - std::chrono::microseconds m_next_send_feefilter{0}; }; - /** 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) */ - std::unique_ptr<TxRelay> m_tx_relay; + /* Initializes a TxRelay struct for this peer. Can be called at most once for a peer. */ + TxRelay* SetTxRelay() EXCLUSIVE_LOCKS_REQUIRED(!m_tx_relay_mutex) + { + LOCK(m_tx_relay_mutex); + Assume(!m_tx_relay); + m_tx_relay = std::make_unique<Peer::TxRelay>(); + return m_tx_relay.get(); + }; + + TxRelay* GetTxRelay() + { + return WITH_LOCK(m_tx_relay_mutex, return m_tx_relay.get()); + }; /** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */ std::vector<CAddress> m_addrs_to_send; @@ -326,10 +355,17 @@ struct Peer { /** Work queue of items requested by this peer **/ std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); - explicit Peer(NodeId id, bool tx_relay) - : m_id(id) - , m_tx_relay(tx_relay ? std::make_unique<TxRelay>() : nullptr) + Peer(NodeId id) + : m_id{id} {} + +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). */ + std::unique_ptr<TxRelay> m_tx_relay GUARDED_BY(m_tx_relay_mutex); }; using PeerRef = std::shared_ptr<Peer>; @@ -365,23 +401,12 @@ struct CNodeState { bool fPreferredDownload{false}; //! Whether this peer wants invs or headers (when possible) for block announcements. bool fPreferHeaders{false}; - //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. - bool fPreferHeaderAndIDs{false}; - /** - * Whether this peer will send us cmpctblocks if we request them. - * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, - * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. - */ - bool fProvidesHeaderAndIDs{false}; + /** Whether this peer wants invs or cmpctblocks (when possible) for block announcements. */ + bool m_requested_hb_cmpctblocks{false}; + /** Whether this peer will send us cmpctblocks if we request them. */ + bool m_provides_cmpctblocks{false}; //! Whether this peer can give us witnesses bool fHaveWitness{false}; - //! Whether this peer wants witnesses in cmpctblocks/blocktxns - bool fWantsCmpctWitness{false}; - /** - * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, - * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. - */ - bool fSupportsDesiredCmpctVersion{false}; /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. * @@ -435,35 +460,43 @@ struct CNodeState { class PeerManagerImpl final : public PeerManager { public: - PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, + PeerManagerImpl(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs); /** Overridden from CValidationInterface. */ - void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; - void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override; - void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; - void BlockChecked(const CBlock& block, const BlockValidationState& state) override; - void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override; + void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override + EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex); + void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override + EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex); + void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void BlockChecked(const CBlock& block, const BlockValidationState& state) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex); /** Implement NetEventsInterface */ - void InitializeNode(CNode* pnode) override; - void FinalizeNode(const CNode& node) override; - bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; - bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); + void InitializeNode(CNode* pnode) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); + bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); /** Implement PeerManager */ void StartScheduledTasks(CScheduler& scheduler) override; void CheckForStaleTipAndEvictPeers() override; std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override; - bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override; + bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; } - void SendPings() override; - void RelayTransaction(const uint256& txid, const uint256& wtxid) override; + void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void SetBestHeight(int height) override { m_best_height = height; }; - void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override; + void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, - const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override; + const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; private: @@ -474,15 +507,15 @@ private: void EvictExtraOutboundPeers(std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Retrieve unbroadcast transactions from the mempool and reattempt sending to peers */ - void ReattemptInitialBroadcast(CScheduler& scheduler); + void ReattemptInitialBroadcast(CScheduler& scheduler) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Get a shared pointer to the Peer object. * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef GetPeerRef(NodeId id) const; + PeerRef GetPeerRef(NodeId id) const EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Get a shared pointer to the Peer object and remove it from m_peer_map. * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef RemovePeer(NodeId id); + PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** * Potentially mark a node discouraged based on the contents of a BlockValidationState object @@ -495,14 +528,16 @@ private: * @return Returns true if the peer was punished (probably disconnected) */ bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, - bool via_compact_block, const std::string& message = ""); + bool via_compact_block, const std::string& message = "") + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** * Potentially disconnect and discourage a node based on the contents of a TxValidationState object * * @return Returns true if the peer was punished (probably disconnected) */ - bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = ""); + bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = "") + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Maybe disconnect a peer and discourage future connections from its address. * @@ -512,13 +547,16 @@ private: */ bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer); - void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans); + void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Process a single headers message from a peer. */ void ProcessHeadersMessage(CNode& pfrom, const Peer& peer, const std::vector<CBlockHeader>& headers, - bool via_compact_block); + bool via_compact_block) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req); + void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Register with TxRequestTracker that an INV has been received from a * peer. The announcement parameters are decided in PeerManager and then @@ -545,7 +583,7 @@ private: * @param[in] fReachable Whether the address' network is reachable. We relay unreachable * addresses less. */ - void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable); + void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Send `feefilter` message. */ void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time); @@ -615,7 +653,8 @@ private: /** Number of preferable block download peers. */ int m_num_preferred_download_peers GUARDED_BY(cs_main){0}; - bool AlreadyHaveTx(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AlreadyHaveTx(const GenTxid& gtxid) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_recent_confirmed_transactions_mutex); /** * Filter for transactions that were recently rejected by the mempool. @@ -683,11 +722,10 @@ private: // All of the following cache a recent block, and are protected by m_most_recent_block_mutex - RecursiveMutex m_most_recent_block_mutex; + Mutex m_most_recent_block_mutex; std::shared_ptr<const CBlock> m_most_recent_block GUARDED_BY(m_most_recent_block_mutex); std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex); uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex); - bool m_most_recent_compact_block_has_witnesses GUARDED_BY(m_most_recent_block_mutex){false}; /** Height of the highest block announced using BIP 152 high-bandwidth mode. */ int m_highest_fast_announce{0}; @@ -722,7 +760,8 @@ private: /** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */ CTransactionRef FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main); - void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main); + void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main); /** Process a new block. Perform any post-processing housekeeping */ void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing); @@ -773,7 +812,8 @@ private: */ bool BlockRequestAllowed(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv); + void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv) + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex); /** * Validation logic for compact filters request handling. @@ -880,10 +920,11 @@ static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& ins static void AddKnownTx(Peer& peer, const uint256& hash) { - if (peer.m_tx_relay != nullptr) { - LOCK(peer.m_tx_relay->m_tx_inventory_mutex); - peer.m_tx_relay->m_tx_inventory_known_filter.insert(hash); - } + auto tx_relay = peer.GetTxRelay(); + if (!tx_relay) return; + + LOCK(tx_relay->m_tx_inventory_mutex); + tx_relay->m_tx_inventory_known_filter.insert(hash); } std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::microseconds now, @@ -974,54 +1015,52 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) if (m_ignore_incoming_txs) return; CNodeState* nodestate = State(nodeid); - if (!nodestate || !nodestate->fSupportsDesiredCmpctVersion) { - // Never ask from peers who can't provide witnesses. + if (!nodestate || !nodestate->m_provides_cmpctblocks) { + // Don't request compact blocks if the peer has not signalled support return; } - if (nodestate->fProvidesHeaderAndIDs) { - int num_outbound_hb_peers = 0; - for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) { - if (*it == nodeid) { - lNodesAnnouncingHeaderAndIDs.erase(it); - lNodesAnnouncingHeaderAndIDs.push_back(nodeid); - return; - } - CNodeState *state = State(*it); - if (state != nullptr && !state->m_is_inbound) ++num_outbound_hb_peers; - } - if (nodestate->m_is_inbound) { - // If we're adding an inbound HB peer, make sure we're not removing - // our last outbound HB peer in the process. - if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { - CNodeState *remove_node = State(lNodesAnnouncingHeaderAndIDs.front()); - if (remove_node != nullptr && !remove_node->m_is_inbound) { - // Put the HB outbound peer in the second slot, so that it - // doesn't get removed. - std::swap(lNodesAnnouncingHeaderAndIDs.front(), *std::next(lNodesAnnouncingHeaderAndIDs.begin())); - } - } + + int num_outbound_hb_peers = 0; + for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) { + if (*it == nodeid) { + lNodesAnnouncingHeaderAndIDs.erase(it); + lNodesAnnouncingHeaderAndIDs.push_back(nodeid); + return; } - m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); - uint64_t nCMPCTBLOCKVersion = 2; - if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { - // As per BIP152, we only get 3 of our peers to announce - // blocks using compact encodings. - m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this, nCMPCTBLOCKVersion](CNode* pnodeStop){ - m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); - // save BIP152 bandwidth state: we select peer to be low-bandwidth - pnodeStop->m_bip152_highbandwidth_to = false; - return true; - }); - lNodesAnnouncingHeaderAndIDs.pop_front(); + CNodeState *state = State(*it); + if (state != nullptr && !state->m_is_inbound) ++num_outbound_hb_peers; + } + if (nodestate->m_is_inbound) { + // If we're adding an inbound HB peer, make sure we're not removing + // our last outbound HB peer in the process. + if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { + CNodeState *remove_node = State(lNodesAnnouncingHeaderAndIDs.front()); + if (remove_node != nullptr && !remove_node->m_is_inbound) { + // Put the HB outbound peer in the second slot, so that it + // doesn't get removed. + std::swap(lNodesAnnouncingHeaderAndIDs.front(), *std::next(lNodesAnnouncingHeaderAndIDs.begin())); } - m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); - // save BIP152 bandwidth state: we select peer to be high-bandwidth - pfrom->m_bip152_highbandwidth_to = true; - lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); - return true; - }); + } } + m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + AssertLockHeld(::cs_main); + if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { + // As per BIP152, we only get 3 of our peers to announce + // blocks using compact encodings. + m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this](CNode* pnodeStop){ + m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION)); + // save BIP152 bandwidth state: we select peer to be low-bandwidth + pnodeStop->m_bip152_highbandwidth_to = false; + return true; + }); + lNodesAnnouncingHeaderAndIDs.pop_front(); + } + m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/true, /*version=*/CMPCTBLOCKS_VERSION)); + // save BIP152 bandwidth state: we select peer to be high-bandwidth + pfrom->m_bip152_highbandwidth_to = true; + lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); + return true; + }); } bool PeerManagerImpl::TipMayBeStale() @@ -1110,7 +1149,6 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count if (state->pindexLastCommonBlock == state->pindexBestKnownBlock) return; - const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); std::vector<const CBlockIndex*> vToFetch; const CBlockIndex *pindexWalk = state->pindexLastCommonBlock; // Never fetch further than the best block we know the peer has, or more than BLOCK_DOWNLOAD_WINDOW + 1 beyond the last @@ -1140,7 +1178,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // We consider the chain that this peer is on invalid. return; } - if (!State(nodeid)->fHaveWitness && DeploymentActiveAt(*pindex, consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { + if (!State(nodeid)->fHaveWitness && DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { // We wouldn't download this block or its descendants from this peer. return; } @@ -1186,7 +1224,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 && peer.m_tx_relay != nullptr && !pnode.IsFeelerConn(); + const bool tx_relay = !m_ignore_incoming_txs && !pnode.IsBlockOnlyConn() && !pnode.IsFeelerConn(); 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) @@ -1241,7 +1279,7 @@ void PeerManagerImpl::InitializeNode(CNode *pnode) m_node_states.emplace_hint(m_node_states.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); assert(m_txrequest.Count(nodeid) == 0); } - PeerRef peer = std::make_shared<Peer>(nodeid, /*tx_relay=*/ !pnode->IsBlockOnlyConn()); + PeerRef peer = std::make_shared<Peer>(nodeid); { LOCK(m_peer_mutex); m_peer_map.emplace_hint(m_peer_map.end(), nodeid, peer); @@ -1377,9 +1415,9 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load(); } - if (peer->m_tx_relay != nullptr) { - stats.m_relay_txs = WITH_LOCK(peer->m_tx_relay->m_bloom_filter_mutex, return peer->m_tx_relay->m_relay_txs); - stats.m_fee_filter_received = peer->m_tx_relay->m_fee_filter_received.load(); + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + stats.m_relay_txs = WITH_LOCK(tx_relay->m_bloom_filter_mutex, return tx_relay->m_relay_txs); + stats.m_fee_filter_received = tx_relay->m_fee_filter_received.load(); } else { stats.m_relay_txs = false; stats.m_fee_filter_received = 0; @@ -1550,17 +1588,17 @@ std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBl return std::nullopt; } -std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, +std::unique_ptr<PeerManager> PeerManager::make(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs) { - return std::make_unique<PeerManagerImpl>(chainparams, connman, addrman, banman, chainman, pool, ignore_incoming_txs); + return std::make_unique<PeerManagerImpl>(connman, addrman, banman, chainman, pool, ignore_incoming_txs); } -PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, +PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs) - : m_chainparams(chainparams), + : m_chainparams(chainman.GetParams()), m_connman(connman), m_addrman(addrman), m_banman(banman), @@ -1632,7 +1670,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo */ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { - std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true); + auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); LOCK(cs_main); @@ -1641,7 +1679,8 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha return; m_highest_fast_announce = pindex->nHeight; - bool fWitnessEnabled = DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); + if (!DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) return; + uint256 hashBlock(pblock->GetHash()); const std::shared_future<CSerializedNetMsg> lazy_ser{ std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })}; @@ -1651,10 +1690,9 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha m_most_recent_block_hash = hashBlock; m_most_recent_block = pblock; m_most_recent_compact_block = pcmpctblock; - m_most_recent_compact_block_has_witnesses = fWitnessEnabled; } - m_connman.ForEachNode([this, pindex, fWitnessEnabled, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + m_connman.ForEachNode([this, pindex, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) @@ -1663,8 +1701,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha CNodeState &state = *State(pnode->GetId()); // If the peer has, or we announced to them the previous block already, // but we don't think they have this one, go ahead and announce it - if (state.fPreferHeaderAndIDs && (!fWitnessEnabled || state.fWantsCmpctWitness) && - !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { + if (state.m_requested_hb_cmpctblocks && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock", hashBlock.ToString(), pnode->GetId()); @@ -1795,12 +1832,13 @@ void PeerManagerImpl::RelayTransaction(const uint256& txid, const uint256& wtxid LOCK(m_peer_mutex); for(auto& it : m_peer_map) { Peer& peer = *it.second; - if (!peer.m_tx_relay) continue; + auto tx_relay = peer.GetTxRelay(); + if (!tx_relay) continue; const uint256& hash{peer.m_wtxid_relay ? wtxid : txid}; - LOCK(peer.m_tx_relay->m_tx_inventory_mutex); - if (!peer.m_tx_relay->m_tx_inventory_known_filter.contains(hash)) { - peer.m_tx_relay->m_tx_inventory_to_send.insert(hash); + LOCK(tx_relay->m_tx_inventory_mutex); + if (!tx_relay->m_tx_inventory_known_filter.contains(hash)) { + tx_relay->m_tx_inventory_to_send.insert(hash); } }; } @@ -1859,12 +1897,10 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& { std::shared_ptr<const CBlock> a_recent_block; std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block; - bool fWitnessesPresentInARecentCompactBlock; { LOCK(m_most_recent_block_mutex); a_recent_block = m_most_recent_block; a_recent_compact_block = m_most_recent_compact_block; - fWitnessesPresentInARecentCompactBlock = m_most_recent_compact_block_has_witnesses; } bool need_activate_chain = false; @@ -1951,11 +1987,11 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& } else if (inv.IsMsgFilteredBlk()) { bool sendMerkleBlock = false; CMerkleBlock merkleBlock; - if (peer.m_tx_relay != nullptr) { - LOCK(peer.m_tx_relay->m_bloom_filter_mutex); - if (peer.m_tx_relay->m_bloom_filter) { + if (auto tx_relay = peer.GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_bloom_filter_mutex); + if (tx_relay->m_bloom_filter) { sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *peer.m_tx_relay->m_bloom_filter); + merkleBlock = CMerkleBlock(*pblock, *tx_relay->m_bloom_filter); } } if (sendMerkleBlock) { @@ -1977,17 +2013,15 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& // they won't have a useful mempool to match against a compact block, // and we don't feel like constructing the object for them, so // instead we respond with the full, non-compact block. - bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; - int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; if (CanDirectFetch() && pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_CMPCTBLOCK_DEPTH) { - if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); + if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { - CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + CBlockHeaderAndShortTxIDs cmpctblock{*pblock}; + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CMPCTBLOCK, cmpctblock)); } } else { - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); } } } @@ -2038,13 +2072,15 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic { AssertLockNotHeld(cs_main); + auto tx_relay = peer.GetTxRelay(); + std::deque<CInv>::iterator it = peer.m_getdata_requests.begin(); std::vector<CInv> vNotFound; const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); const auto now{GetTime<std::chrono::seconds>()}; // Get last mempool request time - const auto mempool_req = peer.m_tx_relay != nullptr ? peer.m_tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min(); + const auto mempool_req = tx_relay != nullptr ? tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min(); // Process as many TX items from the front of the getdata queue as // possible, since they're common and it's efficient to batch process @@ -2057,8 +2093,9 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic const CInv &inv = *it++; - if (peer.m_tx_relay == nullptr) { - // Ignore GETDATA requests for transactions from blocks-only peers. + if (tx_relay == nullptr) { + // Ignore GETDATA requests for transactions from block-relay-only + // peers and peers that asked us not to announce transactions. continue; } @@ -2085,7 +2122,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } for (const uint256& parent_txid : parent_ids_to_add) { // Relaying a transaction with a recent but unconfirmed parent. - if (WITH_LOCK(peer.m_tx_relay->m_tx_inventory_mutex, return !peer.m_tx_relay->m_tx_inventory_known_filter.contains(parent_txid))) { + if (WITH_LOCK(tx_relay->m_tx_inventory_mutex, return !tx_relay->m_tx_inventory_known_filter.contains(parent_txid))) { LOCK(cs_main); State(pfrom.GetId())->m_recently_announced_invs.insert(parent_txid); } @@ -2146,10 +2183,9 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c } resp.txn[i] = block.vtx[req.indexes[i]]; } - LOCK(cs_main); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - int nSendFlags = State(pfrom.GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp)); } void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, @@ -2214,7 +2250,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders(headers, state, m_chainparams, &pindexLast)) { + if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); return; @@ -2258,7 +2294,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && !IsBlockRequested(pindexWalk->GetBlockHash()) && - (!DeploymentActiveAt(*pindexWalk, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) { + (!DeploymentActiveAt(*pindexWalk, m_chainman, Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) { // We don't have this block, and it's not yet in flight. vToFetch.push_back(pindexWalk); } @@ -2292,7 +2328,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } if (vGetData.size() > 0) { if (!m_ignore_incoming_txs && - nodestate->fSupportsDesiredCmpctVersion && + nodestate->m_provides_cmpctblocks && vGetData.size() == 1 && mapBlocksInFlight.size() == 1 && pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) { @@ -2591,7 +2627,7 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv) void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing) { bool new_block{false}; - m_chainman.ProcessNewBlock(m_chainparams, block, force_processing, &new_block); + m_chainman.ProcessNewBlock(block, force_processing, &new_block); if (new_block) { node.m_last_block_time = GetTime<std::chrono::seconds>(); } else { @@ -2721,10 +2757,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // set nodes not capable of serving the complete blockchain history as "limited nodes" pfrom.m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); - if (peer->m_tx_relay != nullptr) { + // 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) + if (!pfrom.IsBlockOnlyConn() && + (fRelay || (pfrom.GetLocalServices() & NODE_BLOOM))) { + auto* const tx_relay = peer->SetTxRelay(); { - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); - peer->m_tx_relay->m_relay_txs = fRelay; // set to true after we get the first filter* message + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_relay_txs = fRelay; // set to true after we get the first filter* message } if (fRelay) pfrom.m_relays_txs = true; } @@ -2862,16 +2904,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); } if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { - // Tell our peer we are willing to provide version 1 or 2 cmpctblocks + // Tell our peer we are willing to provide version 2 cmpctblocks. // However, we do not request new block announcements using // cmpctblock messages. // We send this to non-NODE NETWORK peers as well, because // they may wish to request compact blocks from us - bool fAnnounceUsingCMPCTBLOCK = false; - uint64_t nCMPCTBLOCKVersion = 2; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); - nCMPCTBLOCKVersion = 1; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION)); } pfrom.fSuccessfullyConnected = true; return; @@ -2884,26 +2922,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::SENDCMPCT) { - bool fAnnounceUsingCMPCTBLOCK = false; - uint64_t nCMPCTBLOCKVersion = 0; - vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion; - if (nCMPCTBLOCKVersion == 1 || nCMPCTBLOCKVersion == 2) { - LOCK(cs_main); - // fProvidesHeaderAndIDs is used to "lock in" version of compact blocks we send (fWantsCmpctWitness) - if (!State(pfrom.GetId())->fProvidesHeaderAndIDs) { - State(pfrom.GetId())->fProvidesHeaderAndIDs = true; - State(pfrom.GetId())->fWantsCmpctWitness = nCMPCTBLOCKVersion == 2; - } - if (State(pfrom.GetId())->fWantsCmpctWitness == (nCMPCTBLOCKVersion == 2)) { // ignore later version announces - State(pfrom.GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK; - // save whether peer selects us as BIP152 high-bandwidth peer - // (receiving sendcmpct(1) signals high-bandwidth, sendcmpct(0) low-bandwidth) - pfrom.m_bip152_highbandwidth_from = fAnnounceUsingCMPCTBLOCK; - } - if (!State(pfrom.GetId())->fSupportsDesiredCmpctVersion) { - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); - } - } + bool sendcmpct_hb{false}; + uint64_t sendcmpct_version{0}; + vRecv >> sendcmpct_hb >> sendcmpct_version; + + // Only support compact block relay with witnesses + if (sendcmpct_version != CMPCTBLOCKS_VERSION) return; + + LOCK(cs_main); + CNodeState* nodestate = State(pfrom.GetId()); + nodestate->m_provides_cmpctblocks = true; + nodestate->m_requested_hb_cmpctblocks = sendcmpct_hb; + // save whether peer selects us as BIP152 high-bandwidth peer + // (receiving sendcmpct(1) signals high-bandwidth, sendcmpct(0) low-bandwidth) + pfrom.m_bip152_highbandwidth_from = sendcmpct_hb; return; } @@ -3054,7 +3086,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Reject tx INVs when the -blocksonly setting is enabled, or this is a // block-relay-only peer - bool reject_tx_invs{m_ignore_incoming_txs || (peer->m_tx_relay == nullptr)}; + bool reject_tx_invs{m_ignore_incoming_txs || pfrom.IsBlockOnlyConn()}; // Allow peers with relay permission to send data other than blocks in blocks only mode if (pfrom.HasPermission(NetPermissionFlags::Relay)) { @@ -3252,9 +3284,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // expensive disk reads, because it will require the peer to // actually receive all the data read from disk over the network. LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep\n", pfrom.GetId(), MAX_BLOCKTXN_DEPTH); - CInv inv; - WITH_LOCK(cs_main, inv.type = State(pfrom.GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK); - inv.hash = req.blockhash; + CInv inv{MSG_WITNESS_BLOCK, req.blockhash}; WITH_LOCK(peer->m_getdata_requests_mutex, peer->m_getdata_requests.push_back(inv)); // The message processing loop will go around again (without pausing) and we'll respond then return; @@ -3271,9 +3301,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + if (fImporting || fReindex) { + LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d while importing/reindexing\n", pfrom.GetId()); + return; + } + LOCK(cs_main); - if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !pfrom.HasPermission(NetPermissionFlags::Download)) { - LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId()); + + // 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 + // 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))) { + LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work\n", pfrom.GetId()); return; } @@ -3329,9 +3373,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (msg_type == NetMsgType::TX) { // Stop processing the transaction early if - // 1) We are in blocks only mode and peer has no relay permission + // 1) We are in blocks only mode and peer has no relay permission; OR // 2) This peer is a block-relay-only peer - if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || (peer->m_tx_relay == nullptr)) { + if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || pfrom.IsBlockOnlyConn()) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -3569,7 +3613,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const CBlockIndex *pindex = nullptr; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, m_chainparams, &pindex)) { + if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock"); return; @@ -3629,12 +3673,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - if (DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT) && !nodestate->fSupportsDesiredCmpctVersion) { - // Don't bother trying to process compact blocks from v1 peers - // after segwit activates. - return; - } - // We want to be a bit conservative just to be extra careful about DoS // possibilities in compact block processing... if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) { @@ -3943,9 +3981,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - if (peer->m_tx_relay != nullptr) { - LOCK(peer->m_tx_relay->m_tx_inventory_mutex); - peer->m_tx_relay->m_send_mempool = true; + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_tx_inventory_mutex); + tx_relay->m_send_mempool = true; } return; } @@ -4038,16 +4076,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { // There is no excuse for sending a too-large filter Misbehaving(pfrom.GetId(), 100, "too-large bloom filter"); - } - else if (peer->m_tx_relay != nullptr) - { + } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { { - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); - peer->m_tx_relay->m_bloom_filter.reset(new CBloomFilter(filter)); - peer->m_tx_relay->m_relay_txs = true; + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_bloom_filter.reset(new CBloomFilter(filter)); + tx_relay->m_relay_txs = true; } pfrom.m_bloom_filter_loaded = true; - pfrom.m_relays_txs = true; } return; } @@ -4066,10 +4101,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { bad = true; - } else if (peer->m_tx_relay != nullptr) { - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); - if (peer->m_tx_relay->m_bloom_filter) { - peer->m_tx_relay->m_bloom_filter->insert(vData); + } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_bloom_filter_mutex); + if (tx_relay->m_bloom_filter) { + tx_relay->m_bloom_filter->insert(vData); } else { bad = true; } @@ -4086,14 +4121,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, pfrom.fDisconnect = true; return; } - if (peer->m_tx_relay == nullptr) { - return; - } + auto tx_relay = peer->GetTxRelay(); + if (!tx_relay) return; { - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); - peer->m_tx_relay->m_bloom_filter = nullptr; - peer->m_tx_relay->m_relay_txs = true; + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_bloom_filter = nullptr; + tx_relay->m_relay_txs = true; } pfrom.m_bloom_filter_loaded = false; pfrom.m_relays_txs = true; @@ -4104,8 +4138,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, CAmount newFeeFilter = 0; vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { - if (peer->m_tx_relay != nullptr) { - peer->m_tx_relay->m_fee_filter_received = newFeeFilter; + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + tx_relay->m_fee_filter_received = newFeeFilter; } LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom.GetId()); } @@ -4479,10 +4513,10 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::mic } if (pingSend) { - uint64_t nonce = 0; - while (nonce == 0) { - GetRandBytes({(unsigned char*)&nonce, sizeof(nonce)}); - } + uint64_t nonce; + do { + nonce = GetRand<uint64_t>(); + } while (nonce == 0); peer.m_ping_queued = false; peer.m_ping_start = now; if (node_to.GetCommonVersion() > BIP0031_VERSION) { @@ -4566,10 +4600,12 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::microseconds current_time) { if (m_ignore_incoming_txs) return; - if (!peer.m_tx_relay) return; if (pto.GetCommonVersion() < FEEFILTER_VERSION) return; // peers with the forcerelay permission should not filter txs to us if (pto.HasPermission(NetPermissionFlags::ForceRelay)) return; + // Don't send feefilter messages to outbound block-relay-only peers since they should never announce + // transactions to us, regardless of feefilter state. + if (pto.IsBlockOnlyConn()) return; CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); static FeeFilterRounder g_filter_rounder{CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}}; @@ -4580,27 +4616,27 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi currentFilter = MAX_MONEY; } else { static const CAmount MAX_FILTER{g_filter_rounder.round(MAX_MONEY)}; - if (peer.m_tx_relay->m_fee_filter_sent == MAX_FILTER) { + if (peer.m_fee_filter_sent == MAX_FILTER) { // Send the current filter if we sent MAX_FILTER previously // and made it out of IBD. - peer.m_tx_relay->m_next_send_feefilter = 0us; + peer.m_next_send_feefilter = 0us; } } - if (current_time > peer.m_tx_relay->m_next_send_feefilter) { + if (current_time > peer.m_next_send_feefilter) { CAmount filterToSend = g_filter_rounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); - if (filterToSend != peer.m_tx_relay->m_fee_filter_sent) { + if (filterToSend != peer.m_fee_filter_sent) { m_connman.PushMessage(&pto, CNetMsgMaker(pto.GetCommonVersion()).Make(NetMsgType::FEEFILTER, filterToSend)); - peer.m_tx_relay->m_fee_filter_sent = filterToSend; + peer.m_fee_filter_sent = filterToSend; } - peer.m_tx_relay->m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); + peer.m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_tx_relay->m_next_send_feefilter && - (currentFilter < 3 * peer.m_tx_relay->m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_tx_relay->m_fee_filter_sent / 3)) { - peer.m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); + else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_next_send_feefilter && + (currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) { + peer.m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); } } @@ -4682,10 +4718,31 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (m_chainman.m_best_header == nullptr) { m_chainman.m_best_header = m_chainman.ActiveChain().Tip(); } - bool fFetch = state.fPreferredDownload || (m_num_preferred_download_peers == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. + + // Determine whether we might try initial headers sync or parallel + // block download from this peer -- this mostly affects behavior while + // in IBD (once out of IBD, we sync from all peers). + bool sync_blocks_and_headers_from_peer = false; + if (state.fPreferredDownload) { + sync_blocks_and_headers_from_peer = true; + } else if (!pto->fClient && !pto->IsAddrFetchConn()) { + // Typically this is an inbound peer. If we don't have any outbound + // peers, or if we aren't downloading any blocks from such peers, + // then allow block downloads from this peer, too. + // We prefer downloading blocks from outbound peers to avoid + // putting undue load on (say) some home user who is just making + // outbound connections to the network, but if our only source of + // the latest blocks is from an inbound peer, we have to be sure to + // eventually download it (and not just wait indefinitely for an + // outbound peer to have it). + if (m_num_preferred_download_peers == 0 || mapBlocksInFlight.empty()) { + sync_blocks_and_headers_from_peer = true; + } + } + if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. - if ((nSyncStarted == 0 && fFetch) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { + if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { state.fSyncStarted = true; state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE + ( @@ -4724,7 +4781,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) LOCK(peer->m_block_inv_mutex); std::vector<CBlock> vHeaders; bool fRevertToInv = ((!state.fPreferHeaders && - (!state.fPreferHeaderAndIDs || peer->m_blocks_for_headers_relay.size() > 1)) || + (!state.m_requested_hb_cmpctblocks || peer->m_blocks_for_headers_relay.size() > 1)) || peer->m_blocks_for_headers_relay.size() > MAX_BLOCKS_TO_ANNOUNCE); const CBlockIndex *pBestIndex = nullptr; // last header queued for delivery ProcessBlockAvailability(pto->GetId()); // ensure pindexBestKnownBlock is up-to-date @@ -4777,33 +4834,27 @@ bool PeerManagerImpl::SendMessages(CNode* pto) } } if (!fRevertToInv && !vHeaders.empty()) { - if (vHeaders.size() == 1 && state.fPreferHeaderAndIDs) { + if (vHeaders.size() == 1 && state.m_requested_hb_cmpctblocks) { // We only send up to 1 block as header-and-ids, as otherwise // probably means we're doing an initial-ish-sync or they're slow LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", __func__, vHeaders.front().GetHash().ToString(), pto->GetId()); - int nSendFlags = state.fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - - bool fGotBlockFromCache = false; + std::optional<CSerializedNetMsg> cached_cmpctblock_msg; { LOCK(m_most_recent_block_mutex); if (m_most_recent_block_hash == pBestIndex->GetBlockHash()) { - if (state.fWantsCmpctWitness || !m_most_recent_compact_block_has_witnesses) - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *m_most_recent_compact_block)); - else { - CBlockHeaderAndShortTxIDs cmpctblock(*m_most_recent_block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); - } - fGotBlockFromCache = true; + cached_cmpctblock_msg = msgMaker.Make(NetMsgType::CMPCTBLOCK, *m_most_recent_compact_block); } } - if (!fGotBlockFromCache) { + if (cached_cmpctblock_msg.has_value()) { + m_connman.PushMessage(pto, std::move(cached_cmpctblock_msg.value())); + } else { CBlock block; bool ret = ReadBlockFromDisk(block, pBestIndex, consensusParams); assert(ret); - CBlockHeaderAndShortTxIDs cmpctblock(block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + CBlockHeaderAndShortTxIDs cmpctblock{block}; + m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::CMPCTBLOCK, cmpctblock)); } state.pindexBestHeaderSent = pBestIndex; } else if (state.fPreferHeaders) { @@ -4868,45 +4919,45 @@ bool PeerManagerImpl::SendMessages(CNode* pto) peer->m_blocks_for_inv_relay.clear(); } - if (peer->m_tx_relay != nullptr) { - LOCK(peer->m_tx_relay->m_tx_inventory_mutex); + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_tx_inventory_mutex); // Check whether periodic sends should happen bool fSendTrickle = pto->HasPermission(NetPermissionFlags::NoBan); - if (peer->m_tx_relay->m_next_inv_send_time < current_time) { + if (tx_relay->m_next_inv_send_time < current_time) { fSendTrickle = true; if (pto->IsInboundConn()) { - peer->m_tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - peer->m_tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } // Time to send but the peer has requested we not relay transactions. if (fSendTrickle) { - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); - if (!peer->m_tx_relay->m_relay_txs) peer->m_tx_relay->m_tx_inventory_to_send.clear(); + LOCK(tx_relay->m_bloom_filter_mutex); + if (!tx_relay->m_relay_txs) tx_relay->m_tx_inventory_to_send.clear(); } // Respond to BIP35 mempool requests - if (fSendTrickle && peer->m_tx_relay->m_send_mempool) { + if (fSendTrickle && tx_relay->m_send_mempool) { auto vtxinfo = m_mempool.infoAll(); - peer->m_tx_relay->m_send_mempool = false; - const CFeeRate filterrate{peer->m_tx_relay->m_fee_filter_received.load()}; + tx_relay->m_send_mempool = false; + const CFeeRate filterrate{tx_relay->m_fee_filter_received.load()}; - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); + LOCK(tx_relay->m_bloom_filter_mutex); for (const auto& txinfo : vtxinfo) { const uint256& hash = peer->m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash(); CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash); - peer->m_tx_relay->m_tx_inventory_to_send.erase(hash); + tx_relay->m_tx_inventory_to_send.erase(hash); // Don't send transactions that peers will not put into their mempool if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { continue; } - if (peer->m_tx_relay->m_bloom_filter) { - if (!peer->m_tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; + if (tx_relay->m_bloom_filter) { + if (!tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; } - peer->m_tx_relay->m_tx_inventory_known_filter.insert(hash); + tx_relay->m_tx_inventory_known_filter.insert(hash); // Responses to MEMPOOL requests bypass the m_recently_announced_invs filter. vInv.push_back(inv); if (vInv.size() == MAX_INV_SZ) { @@ -4914,18 +4965,18 @@ bool PeerManagerImpl::SendMessages(CNode* pto) vInv.clear(); } } - peer->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time); + tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time); } // Determine transactions to relay if (fSendTrickle) { // Produce a vector with all candidates for sending std::vector<std::set<uint256>::iterator> vInvTx; - vInvTx.reserve(peer->m_tx_relay->m_tx_inventory_to_send.size()); - for (std::set<uint256>::iterator it = peer->m_tx_relay->m_tx_inventory_to_send.begin(); it != peer->m_tx_relay->m_tx_inventory_to_send.end(); it++) { + vInvTx.reserve(tx_relay->m_tx_inventory_to_send.size()); + for (std::set<uint256>::iterator it = tx_relay->m_tx_inventory_to_send.begin(); it != tx_relay->m_tx_inventory_to_send.end(); it++) { vInvTx.push_back(it); } - const CFeeRate filterrate{peer->m_tx_relay->m_fee_filter_received.load()}; + const CFeeRate filterrate{tx_relay->m_fee_filter_received.load()}; // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. // A heap is used so that not all items need sorting if only a few are being sent. CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, peer->m_wtxid_relay); @@ -4933,7 +4984,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // No reason to drain out at many times the network's capacity, // especially since we have many peers and some will draw much shorter delays. unsigned int nRelayedTransactions = 0; - LOCK(peer->m_tx_relay->m_bloom_filter_mutex); + LOCK(tx_relay->m_bloom_filter_mutex); while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { // Fetch the top element from the heap std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); @@ -4942,9 +4993,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) uint256 hash = *it; CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash); // Remove it from the to-be-sent set - peer->m_tx_relay->m_tx_inventory_to_send.erase(it); + tx_relay->m_tx_inventory_to_send.erase(it); // Check if not in the filter already - if (peer->m_tx_relay->m_tx_inventory_known_filter.contains(hash)) { + if (tx_relay->m_tx_inventory_known_filter.contains(hash)) { continue; } // Not in the mempool anymore? don't bother sending it. @@ -4958,7 +5009,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { continue; } - if (peer->m_tx_relay->m_bloom_filter && !peer->m_tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; + if (tx_relay->m_bloom_filter && !tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; // Send State(pto->GetId())->m_recently_announced_invs.insert(hash); vInv.push_back(inv); @@ -4985,14 +5036,14 @@ bool PeerManagerImpl::SendMessages(CNode* pto) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); vInv.clear(); } - peer->m_tx_relay->m_tx_inventory_known_filter.insert(hash); + tx_relay->m_tx_inventory_known_filter.insert(hash); if (hash != txid) { // Insert txid into m_tx_inventory_known_filter, even for // wtxidrelay peers. This prevents re-adding of // unconfirmed parents to the recently_announced // filter, when a child tx is requested. See // ProcessGetData(). - peer->m_tx_relay->m_tx_inventory_known_filter.insert(txid); + tx_relay->m_tx_inventory_known_filter.insert(txid); } } } @@ -5064,7 +5115,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Message: getdata (blocks) // std::vector<CInv> vGetData; - if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!pto->fClient && ((sync_blocks_and_headers_from_peer && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { std::vector<const CBlockIndex*> vToDownload; NodeId staller = -1; FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller); diff --git a/src/net_processing.h b/src/net_processing.h index c982b919a6..d5c73e6c79 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -39,7 +39,7 @@ struct CNodeStateStats { class PeerManager : public CValidationInterface, public NetEventsInterface { public: - static std::unique_ptr<PeerManager> make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, + static std::unique_ptr<PeerManager> make(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs); virtual ~PeerManager() { } diff --git a/src/net_types.cpp b/src/net_types.cpp index e4101a9876..90346715f0 100644 --- a/src/net_types.cpp +++ b/src/net_types.cpp @@ -12,9 +12,9 @@ static const char* BANMAN_JSON_VERSION_KEY{"version"}; CBanEntry::CBanEntry(const UniValue& json) - : nVersion(json[BANMAN_JSON_VERSION_KEY].get_int()), - nCreateTime(json["ban_created"].get_int64()), - nBanUntil(json["banned_until"].get_int64()) + : nVersion(json[BANMAN_JSON_VERSION_KEY].getInt<int>()), + nCreateTime(json["ban_created"].getInt<int64_t>()), + nBanUntil(json["banned_until"].getInt<int64_t>()) { } @@ -58,7 +58,7 @@ UniValue BanMapToJson(const banmap_t& bans) void BanMapFromJson(const UniValue& bans_json, banmap_t& bans) { for (const auto& ban_entry_json : bans_json.getValues()) { - const int version{ban_entry_json[BANMAN_JSON_VERSION_KEY].get_int()}; + const int version{ban_entry_json[BANMAN_JSON_VERSION_KEY].getInt<int>()}; if (version != CBanEntry::CURRENT_VERSION) { LogPrintf("Dropping entry with unknown version (%s) from ban list\n", version); continue; diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 9717f7abce..ca148bfa51 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -98,7 +98,7 @@ bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t addre * * @note This address is considered invalid by CNetAddr::IsValid() */ -CNetAddr::CNetAddr() {} +CNetAddr::CNetAddr() = default; void CNetAddr::SetIP(const CNetAddr& ipIn) { diff --git a/src/netaddress.h b/src/netaddress.h index b9a8dc589a..47ba045334 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -9,7 +9,6 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> #include <compat.h> #include <crypto/siphash.h> #include <prevector.h> @@ -556,8 +555,8 @@ class CServiceHash { public: CServiceHash() - : m_salt_k0{GetRand(std::numeric_limits<uint64_t>::max())}, - m_salt_k1{GetRand(std::numeric_limits<uint64_t>::max())} + : m_salt_k0{GetRand<uint64_t>()}, + m_salt_k1{GetRand<uint64_t>()} { } diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 17ab226a30..cadafcaa8d 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -226,7 +226,7 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr } } - LogPrint(BCLog::PRUNE, "Prune: target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n", + LogPrint(BCLog::PRUNE, "target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n", nPruneTarget/1024/1024, nCurrentUsage/1024/1024, ((int64_t)nPruneTarget - (int64_t)nCurrentUsage)/1024/1024, nLastBlockWeCanPrune, count); @@ -318,9 +318,9 @@ bool BlockManager::WriteBlockIndexDB() return true; } -bool BlockManager::LoadBlockIndexDB() +bool BlockManager::LoadBlockIndexDB(const Consensus::Params& consensus_params) { - if (!LoadBlockIndex(::Params().GetConsensus())) { + if (!LoadBlockIndex(consensus_params)) { return false; } diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 488713dbd8..2e52716649 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -153,7 +153,7 @@ public: std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main); bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool LoadBlockIndexDB(const Consensus::Params& consensus_params) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); CBlockIndex* AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Create a new block index entry for a given block hash */ diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 99615dea69..54ba5b7966 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -13,7 +13,6 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, ChainstateManager& chainman, CTxMemPool* mempool, bool fPruneMode, - const Consensus::Params& consensus_params, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -57,7 +56,7 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, } if (!chainman.BlockIndex().empty() && - !chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) { + !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) { return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; } @@ -126,10 +125,8 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman, bool fReset, bool fReindexChainState, - const Consensus::Params& consensus_params, int check_blocks, - int check_level, - std::function<int64_t()> get_unix_time_seconds) + int check_level) { auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); @@ -140,12 +137,12 @@ std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); - if (tip && tip->nTime > get_unix_time_seconds() + MAX_FUTURE_BLOCK_TIME) { + if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; } if (!CVerifyDB().VerifyDB( - *chainstate, consensus_params, chainstate->CoinsDB(), + *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(), check_level, check_blocks)) { return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB; diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 8ba04f1436..ff7935e8e0 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -59,7 +59,6 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, ChainstateManager& chainman, CTxMemPool* mempool, bool fPruneMode, - const Consensus::Params& consensus_params, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -78,10 +77,8 @@ enum class ChainstateLoadVerifyError { std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman, bool fReset, bool fReindexChainState, - const Consensus::Params& consensus_params, int check_blocks, - int check_level, - std::function<int64_t()> get_unix_time_seconds); + int check_level); } // namespace node #endif // BITCOIN_NODE_CHAINSTATE_H diff --git a/src/node/context.cpp b/src/node/context.cpp index 0b31c10f44..d80b8ca7a7 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -7,6 +7,7 @@ #include <addrman.h> #include <banman.h> #include <interfaces/chain.h> +#include <kernel/context.h> #include <net.h> #include <net_processing.h> #include <netgroup.h> @@ -16,6 +17,6 @@ #include <validation.h> namespace node { -NodeContext::NodeContext() {} -NodeContext::~NodeContext() {} +NodeContext::NodeContext() = default; +NodeContext::~NodeContext() = default; } // namespace node diff --git a/src/node/context.h b/src/node/context.h index 91ba456219..31be308787 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_NODE_CONTEXT_H #define BITCOIN_NODE_CONTEXT_H +#include <kernel/context.h> + #include <cassert> #include <functional> #include <memory> @@ -39,6 +41,8 @@ namespace node { //! any member functions. It should just be a collection of references that can //! be used without pulling in unwanted dependencies or functionality. struct NodeContext { + //! libbitcoin_kernel context + std::unique_ptr<kernel::Context> kernel; //! Init interface for initializing current process and connecting to other processes. interfaces::Init* init{nullptr}; std::unique_ptr<AddrMan> addrman; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 954bd1c31d..7752fb0f65 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -90,8 +90,16 @@ public: uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } bool baseInitialize() override { - return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false) && AppInitSanityChecks() && - AppInitLockDataDirectory() && AppInitInterfaces(*m_context); + if (!AppInitBasicSetup(gArgs)) return false; + if (!AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false)) return false; + + m_context->kernel = std::make_unique<kernel::Context>(); + if (!AppInitSanityChecks(*m_context->kernel)) return false; + + if (!AppInitLockDataDirectory()) return false; + if (!AppInitInterfaces(*m_context)) return false; + + return true; } bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override { @@ -112,6 +120,46 @@ public: } } bool shutdownRequested() override { return ShutdownRequested(); } + bool isSettingIgnored(const std::string& name) override + { + bool ignored = false; + gArgs.LockSettings([&](util::Settings& settings) { + if (auto* options = util::FindKey(settings.command_line_options, name)) { + ignored = !options->empty(); + } + }); + return ignored; + } + util::SettingsValue getPersistentSetting(const std::string& name) override { return gArgs.GetPersistentSetting(name); } + void updateRwSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + gArgs.WriteSettingsFile(); + } + void forceSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.forced_settings.erase(name); + } else { + settings.forced_settings[name] = value; + } + }); + } + void resetSettings() override + { + gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); + gArgs.LockSettings([&](util::Settings& settings) { + settings.rw_settings.clear(); + }); + gArgs.WriteSettingsFile(); + } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(ConnectionDirection flags) override @@ -228,7 +276,7 @@ public: uint256 getBestBlockHash() override { const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); - return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); + return tip ? tip->GetBlockHash() : chainman().GetParams().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { @@ -236,7 +284,7 @@ public: if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } - return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network + return chainman().GetParams().GenesisBlock().GetBlockTime(); // Genesis block's time of current network } double getVerificationProgress() override { @@ -245,7 +293,7 @@ public: LOCK(::cs_main); tip = chainman().ActiveChain().Tip(); } - return GuessVerificationProgress(Params().TxData(), tip); + return GuessVerificationProgress(chainman().GetParams().TxData(), tip); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); @@ -426,7 +474,7 @@ public: // try to handle the request. Otherwise, reraise the exception. if (!last_handler) { const UniValue& code = e["code"]; - if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { + if (code.isNum() && code.getInt<int>() == RPC_WALLET_NOT_FOUND) { return false; } } @@ -546,7 +594,7 @@ public: double guessVerificationProgress(const uint256& block_hash) override { LOCK(cs_main); - return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); + return GuessVerificationProgress(chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override { diff --git a/src/node/miner.cpp b/src/node/miner.cpp index be5d58527b..01db49d5cf 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -51,7 +51,7 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) block.vtx.at(0) = MakeTransactionRef(tx); const CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)); - GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus()); + chainman.GenerateCoinbaseCommitment(block, prev_block); block.hashMerkleRoot = BlockMerkleRoot(block); } @@ -62,8 +62,8 @@ BlockAssembler::Options::Options() nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options) - : chainparams(params), +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const Options& options) + : chainparams{chainstate.m_chainman.GetParams()}, m_mempool(mempool), m_chainstate(chainstate) { @@ -87,8 +87,8 @@ static BlockAssembler::Options DefaultOptions() return options; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params) - : BlockAssembler(chainstate, mempool, params, DefaultOptions()) {} +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool) + : BlockAssembler(chainstate, mempool, DefaultOptions()) {} void BlockAssembler::resetBlock() { @@ -126,7 +126,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; - pblock->nVersion = g_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); + pblock->nVersion = m_chainstate.m_chainman.m_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) { @@ -154,7 +154,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); - pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus()); + pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); pblocktemplate->vTxFees[0] = -nFees; LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); @@ -167,7 +167,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { + if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } int64_t nTime2 = GetTimeMicros(); @@ -310,10 +310,6 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda // Keep track of entries that failed inclusion, to avoid duplicate work CTxMemPool::setEntries failedTx; - // Start by adding all descendants of previously added txs to mapModifiedTx - // and modifying them for their already included ancestors - UpdatePackagesForAdded(inBlock, mapModifiedTx); - CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = m_mempool.mapTx.get<ancestor_score>().begin(); CTxMemPool::txiter iter; diff --git a/src/node/miner.h b/src/node/miner.h index 678df815c0..7cf8e3fb9e 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -157,8 +157,8 @@ public: CFeeRate blockMinFeeRate; }; - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params); - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); diff --git a/src/node/transaction.h b/src/node/transaction.h index b7cf225636..0604754a46 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_NODE_TRANSACTION_H #define BITCOIN_NODE_TRANSACTION_H -#include <attributes.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <util/error.h> diff --git a/src/outputtype.h b/src/outputtype.h index 66fe489bb0..6b4e695760 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_OUTPUTTYPE_H #define BITCOIN_OUTPUTTYPE_H -#include <attributes.h> #include <script/signingprovider.h> #include <script/standard.h> diff --git a/src/policy/feerate.cpp b/src/policy/feerate.cpp index 0ea56d8db7..82b767793d 100644 --- a/src/policy/feerate.cpp +++ b/src/policy/feerate.cpp @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <consensus/amount.h> #include <policy/feerate.h> - #include <tinyformat.h> #include <cmath> diff --git a/src/policy/feerate.h b/src/policy/feerate.h index 50fd6fd11b..a8d4d2fc63 100644 --- a/src/policy/feerate.h +++ b/src/policy/feerate.h @@ -9,7 +9,10 @@ #include <consensus/amount.h> #include <serialize.h> + +#include <cstdint> #include <string> +#include <type_traits> const std::string CURRENCY_UNIT = "BTC"; // One formatted unit const std::string CURRENCY_ATOM = "sat"; // One indivisible minimum value unit diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 6499dbd97f..b39632364f 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -6,12 +6,30 @@ #include <policy/fees.h> #include <clientversion.h> +#include <consensus/amount.h> #include <fs.h> #include <logging.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <random.h> +#include <serialize.h> #include <streams.h> +#include <sync.h> +#include <tinyformat.h> #include <txmempool.h> +#include <uint256.h> #include <util/serfloat.h> #include <util/system.h> +#include <util/time.h> + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <exception> +#include <stdexcept> +#include <utility> static const char* FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; @@ -537,9 +555,7 @@ CBlockPolicyEstimator::CBlockPolicyEstimator() } } -CBlockPolicyEstimator::~CBlockPolicyEstimator() -{ -} +CBlockPolicyEstimator::~CBlockPolicyEstimator() = default; void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) { diff --git a/src/policy/fees.h b/src/policy/fees.h index 6e25bb42b8..dea1e1d31b 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -7,20 +7,20 @@ #include <consensus/amount.h> #include <policy/feerate.h> -#include <uint256.h> #include <random.h> #include <sync.h> +#include <threadsafety.h> +#include <uint256.h> #include <array> #include <map> #include <memory> +#include <set> #include <string> #include <vector> class CAutoFile; -class CFeeRate; class CTxMemPoolEntry; -class CTxMemPool; class TxConfirmStats; /* Identifier for each of the 3 different TxConfirmStats which will track diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp index 21f5488816..67918c9dec 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -2,12 +2,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <consensus/validation.h> #include <policy/packages.h> +#include <policy/policy.h> #include <primitives/transaction.h> #include <uint256.h> #include <util/hasher.h> +#include <algorithm> +#include <cassert> +#include <iterator> +#include <memory> #include <numeric> #include <unordered_set> diff --git a/src/policy/packages.h b/src/policy/packages.h index 9f274f6b7d..ba6a3a9a06 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -5,10 +5,12 @@ #ifndef BITCOIN_POLICY_PACKAGES_H #define BITCOIN_POLICY_PACKAGES_H +#include <consensus/consensus.h> #include <consensus/validation.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <cstdint> #include <vector> /** Default maximum number of transactions in a package. */ diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 6aba6a4a5b..f6452266b7 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -7,10 +7,22 @@ #include <policy/policy.h> -#include <consensus/validation.h> #include <coins.h> +#include <consensus/amount.h> +#include <consensus/consensus.h> +#include <consensus/validation.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <script/interpreter.h> +#include <script/script.h> +#include <script/standard.h> +#include <serialize.h> #include <span.h> +#include <algorithm> +#include <cstddef> +#include <vector> + CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) { // "Dust" is defined in terms of dustRelayFee, diff --git a/src/policy/policy.h b/src/policy/policy.h index 89f6e72618..94f9623b8a 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -6,15 +6,18 @@ #ifndef BITCOIN_POLICY_POLICY_H #define BITCOIN_POLICY_POLICY_H +#include <consensus/amount.h> #include <consensus/consensus.h> -#include <policy/feerate.h> +#include <primitives/transaction.h> #include <script/interpreter.h> #include <script/standard.h> +#include <cstdint> #include <string> class CCoinsViewCache; -class CTxOut; +class CFeeRate; +class CScript; /** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/ static const unsigned int DEFAULT_BLOCK_MAX_WEIGHT = MAX_BLOCK_WEIGHT - 4000; @@ -52,6 +55,8 @@ static const unsigned int MAX_STANDARD_SCRIPTSIG_SIZE = 1650; * only increase the dust limit after prior releases were already not creating * outputs below the new threshold */ static const unsigned int DUST_RELAY_TX_FEE = 3000; +/** Default for -minrelaytxfee, minimum relay fee for transactions */ +static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; /** * Standard script verification flags that standard transactions will comply * with. However scripts violating these flags may still be present in valid diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 8fe4dc35b8..e25f5c7c5b 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -4,11 +4,19 @@ #include <policy/rbf.h> -#include <policy/settings.h> +#include <consensus/amount.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <sync.h> #include <tinyformat.h> +#include <txmempool.h> +#include <uint256.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <limits> +#include <vector> + RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) { AssertLockHeld(pool.cs); diff --git a/src/policy/rbf.h b/src/policy/rbf.h index fcec7052ed..07f68c8fd4 100644 --- a/src/policy/rbf.h +++ b/src/policy/rbf.h @@ -5,13 +5,20 @@ #ifndef BITCOIN_POLICY_RBF_H #define BITCOIN_POLICY_RBF_H +#include <consensus/amount.h> #include <primitives/transaction.h> +#include <threadsafety.h> #include <txmempool.h> -#include <uint256.h> +#include <cstddef> +#include <cstdint> #include <optional> +#include <set> #include <string> +class CFeeRate; +class uint256; + /** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all * mempool conflicts and their descendants. */ static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100}; diff --git a/src/policy/settings.cpp b/src/policy/settings.cpp index eb2ec56850..0b67d274ce 100644 --- a/src/policy/settings.cpp +++ b/src/policy/settings.cpp @@ -11,4 +11,5 @@ bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; CFeeRate incrementalRelayFee = CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE); CFeeRate dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); +CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP; diff --git a/src/policy/settings.h b/src/policy/settings.h index 0b4fc1e770..2311d01fe8 100644 --- a/src/policy/settings.h +++ b/src/policy/settings.h @@ -6,14 +6,19 @@ #ifndef BITCOIN_POLICY_SETTINGS_H #define BITCOIN_POLICY_SETTINGS_H +#include <policy/feerate.h> #include <policy/policy.h> -class CFeeRate; +#include <cstdint> +#include <string> + class CTransaction; // Policy settings which are configurable at runtime. extern CFeeRate incrementalRelayFee; extern CFeeRate dustRelayFee; +/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ +extern CFeeRate minRelayTxFee; extern unsigned int nBytesPerSigOp; extern bool fIsBareMultisigStd; diff --git a/src/prevector.h b/src/prevector.h index 830b31e315..a52510930a 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -35,6 +35,8 @@ */ template<unsigned int N, typename T, typename Size = uint32_t, typename Diff = int32_t> class prevector { + static_assert(std::is_trivially_copyable_v<T>); + public: typedef Size size_type; typedef Diff difference_type; @@ -411,15 +413,7 @@ public: // representation (with capacity N and size <= N). iterator p = first; char* endp = (char*)&(*end()); - if (!std::is_trivially_destructible<T>::value) { - while (p != last) { - (*p).~T(); - _size--; - ++p; - } - } else { - _size -= last - p; - } + _size -= last - p; memmove(&(*first), &(*last), endp - ((char*)(&(*last)))); return first; } @@ -465,9 +459,6 @@ public: } ~prevector() { - if (!std::is_trivially_destructible<T>::value) { - clear(); - } if (!is_direct()) { free(_union.indirect_contents.indirect); _union.indirect_contents.indirect = nullptr; diff --git a/src/protocol.h b/src/protocol.h index fdeaa9a9c5..da2d24aff3 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -3,10 +3,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef __cplusplus -#error This header can only be compiled as C++. -#endif - #ifndef BITCOIN_PROTOCOL_H #define BITCOIN_PROTOCOL_H @@ -15,10 +11,9 @@ #include <serialize.h> #include <streams.h> #include <uint256.h> -#include <version.h> +#include <cstdint> #include <limits> -#include <stdint.h> #include <string> /** Message header. @@ -420,7 +415,6 @@ public: use_v2 = s.GetVersion() & ADDRV2_FORMAT; } - SER_READ(obj, obj.nTime = TIME_INIT); READWRITE(obj.nTime); // nServices is serialized as CompactSize in V2; as uint64_t in V1. if (use_v2) { diff --git a/src/psbt.h b/src/psbt.h index 8a9cbd33d2..8fda889bb4 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_PSBT_H #define BITCOIN_PSBT_H -#include <attributes.h> #include <node/transaction.h> #include <policy/feerate.h> #include <primitives/transaction.h> diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d59a4345f3..a82bd5f73e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -19,6 +19,11 @@ #include <QMenu> #include <QMessageBox> #include <QSortFilterProxyModel> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QRegularExpression> +#else +#include <QRegExp> +#endif class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel { @@ -46,12 +51,13 @@ protected: auto address = model->index(row, AddressTableModel::Address, parent); - if (filterRegExp().indexIn(model->data(address).toString()) < 0 && - filterRegExp().indexIn(model->data(label).toString()) < 0) { - return false; - } - - return true; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + const auto pattern = filterRegularExpression(); +#else + const auto pattern = filterRegExp(); +#endif + return (model->data(address).toString().contains(pattern) || + model->data(label).toString().contains(pattern)); } }; diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index dcab631d98..27ee9509e6 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -30,7 +30,7 @@ struct AddressTableEntry QString label; QString address; - AddressTableEntry() {} + AddressTableEntry() = default; AddressTableEntry(Type _type, const QString &_label, const QString &_address): type(_type), label(_label), address(_address) {} }; diff --git a/src/qt/android/res/values/libs.xml b/src/qt/android/res/values/libs.xml index 0f20df4eb0..b4b77b1c7b 100644 --- a/src/qt/android/res/values/libs.xml +++ b/src/qt/android/res/values/libs.xml @@ -10,8 +10,5 @@ <item> x86_64;libbitcoin-qt_x86_64.so </item> - <item> - x86;libbitcoin-qt_x86.so - </item> </array> </resources> diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index e004fba308..3d0be69302 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -89,10 +89,7 @@ BanTableModel::BanTableModel(interfaces::Node& node, QObject* parent) : refresh(); } -BanTableModel::~BanTableModel() -{ - // Intentionally left empty -} +BanTableModel::~BanTableModel() = default; int BanTableModel::rowCount(const QModelIndex &parent) const { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 8cac28400f..53fbac0106 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -261,7 +261,7 @@ void BitcoinApplication::createPaymentServer() void BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(this, resetSettings); + optionsModel = new OptionsModel(node(), this, resetSettings); } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -292,7 +292,6 @@ void BitcoinApplication::createNode(interfaces::Init& init) { assert(!m_node); m_node = init.makeNode(); - if (optionsModel) optionsModel->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node); } @@ -308,7 +307,9 @@ void BitcoinApplication::startThread() /* communication to and from thread */ connect(&m_executor.value(), &InitExecutor::initializeResult, this, &BitcoinApplication::initializeResult); - connect(&m_executor.value(), &InitExecutor::shutdownResult, this, &QCoreApplication::quit); + connect(&m_executor.value(), &InitExecutor::shutdownResult, this, [] { + QCoreApplication::exit(0); + }); connect(&m_executor.value(), &InitExecutor::runawayException, this, &BitcoinApplication::handleRunawayException); connect(this, &BitcoinApplication::requestedInitialize, &m_executor.value(), &InitExecutor::initialize); connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown); @@ -633,6 +634,12 @@ int GuiMain(int argc, char* argv[]) // Allow parameter interaction before we create the options model app.parameterSetup(); GUIUtil::LogQtInfo(); + + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) + app.createSplashScreen(networkStyle.data()); + + app.createNode(*init); + // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); @@ -641,11 +648,6 @@ int GuiMain(int argc, char* argv[]) app.InitPruneSetting(prune_MiB); } - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) - app.createSplashScreen(networkStyle.data()); - - app.createNode(*init); - int rv = EXIT_SUCCESS; try { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 81b0e711b2..bfcdf6f316 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -26,7 +26,7 @@ #include <qt/walletview.h> #endif // ENABLE_WALLET -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macdockiconhandler.h> #endif @@ -69,7 +69,7 @@ const std::string BitcoinGUI::DEFAULT_UIPLATFORM = -#if defined(Q_OS_MAC) +#if defined(Q_OS_MACOS) "macosx" #elif defined(Q_OS_WIN) "windows" @@ -219,7 +219,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS m_app_nap_inhibitor = new CAppNapInhibitor; #endif @@ -235,7 +235,7 @@ BitcoinGUI::~BitcoinGUI() settings.setValue("MainWindowGeometry", saveGeometry()); if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); @@ -433,7 +433,7 @@ void BitcoinGUI::createActions() void BitcoinGUI::createMenuBar() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Create a decoupled menu bar on Mac which stays even if the window is closed appMenuBar = new QMenuBar(); #else @@ -482,7 +482,7 @@ void BitcoinGUI::createMenuBar() minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); }); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QAction* zoom_action = window_menu->addAction(tr("Zoom")); connect(zoom_action, &QAction::triggered, [] { QWindow* window = qApp->focusWindow(); @@ -499,7 +499,7 @@ void BitcoinGUI::createMenuBar() #endif if (walletFrame) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS window_menu->addSeparator(); QAction* main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { @@ -755,7 +755,7 @@ void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (QSystemTrayIcon::isSystemTrayAvailable()) { trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); @@ -766,17 +766,17 @@ void BitcoinGUI::createTrayIcon() void BitcoinGUI::createTrayIconMenu() { -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (!trayIcon) return; -#endif // Q_OS_MAC +#endif // Q_OS_MACOS // Configuration of the tray icon (or Dock icon) menu. QAction* show_hide_action{nullptr}; -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS // Note: On macOS, the Dock icon's menu already has Show / Hide action. show_hide_action = trayIconMenu->addAction(QString(), this, &BitcoinGUI::toggleHidden); trayIconMenu->addSeparator(); -#endif // Q_OS_MAC +#endif // Q_OS_MACOS QAction* send_action{nullptr}; QAction* receive_action{nullptr}; @@ -794,7 +794,7 @@ void BitcoinGUI::createTrayIconMenu() options_action->setMenuRole(QAction::PreferencesRole); QAction* node_window_action = trayIconMenu->addAction(openRPCConsoleAction->text(), openRPCConsoleAction, &QAction::trigger); QAction* quit_action{nullptr}; -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS // Note: On macOS, the Dock icon's menu already has Quit action. trayIconMenu->addSeparator(); quit_action = trayIconMenu->addAction(quitAction->text(), quitAction, &QAction::trigger); @@ -814,7 +814,7 @@ void BitcoinGUI::createTrayIconMenu() activateWindow(); }); trayIconMenu->setAsDockMenu(); -#endif // Q_OS_MAC +#endif // Q_OS_MACOS connect( // Using QSystemTrayIcon::Context is not reliable. @@ -1006,7 +1006,7 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (sync_state == SynchronizationState::POST_INIT) { m_app_nap_inhibitor->enableAppNap(); } else { @@ -1192,7 +1192,7 @@ void BitcoinGUI::changeEvent(QEvent *e) QMainWindow::changeEvent(e); -#ifndef Q_OS_MAC // Ignored on Mac +#ifndef Q_OS_MACOS // Ignored on Mac if(e->type() == QEvent::WindowStateChange) { if(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) @@ -1215,7 +1215,7 @@ void BitcoinGUI::changeEvent(QEvent *e) void BitcoinGUI::closeEvent(QCloseEvent *event) { -#ifndef Q_OS_MAC // Ignored on Mac +#ifndef Q_OS_MACOS // Ignored on Mac if(clientModel && clientModel->getOptionsModel()) { if(!clientModel->getOptionsModel()->getMinimizeOnClose()) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 5d9a978695..6272d5d947 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -22,7 +22,7 @@ #include <QPoint> #include <QSystemTrayIcon> -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macos_appnap.h> #endif @@ -175,7 +175,7 @@ private: QMenu* m_network_context_menu = new QMenu(this); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS CAppNapInhibitor* m_app_nap_inhibitor = nullptr; #endif diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 4327d31787..f41da519df 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -21,9 +21,9 @@ #include <validation.h> #include <stdint.h> -#include <functional> #include <QDebug> +#include <QMetaObject> #include <QThread> #include <QTimer> @@ -148,21 +148,6 @@ uint256 ClientModel::getBestBlockHash() return m_cached_tip_blocks; } -void ClientModel::updateNumConnections(int numConnections) -{ - Q_EMIT numConnectionsChanged(numConnections); -} - -void ClientModel::updateNetworkActive(bool networkActive) -{ - Q_EMIT networkActiveChanged(networkActive); -} - -void ClientModel::updateAlert() -{ - Q_EMIT alertsChanged(getStatusBarWarnings()); -} - enum BlockSource ClientModel::getBlockSource() const { if (m_node.getReindex()) @@ -230,94 +215,65 @@ QString ClientModel::blocksDir() const return GUIUtil::PathToQString(gArgs.GetBlocksDirPath()); } -void ClientModel::updateBanlist() -{ - banTableModel->refresh(); -} - -// Handlers for core signals -static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) -{ - // emits signal "showProgress" - bool invoked = QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(title)), - Q_ARG(int, nProgress)); - assert(invoked); -} - -static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) -{ - // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections); - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, - Q_ARG(int, newNumConnections)); - assert(invoked); -} - -static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive) -{ - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, - Q_ARG(bool, networkActive)); - assert(invoked); -} - -static void NotifyAlertChanged(ClientModel *clientmodel) -{ - qDebug() << "NotifyAlertChanged"; - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); - assert(invoked); -} - -static void BannedListChanged(ClientModel *clientmodel) -{ - qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__); - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); - assert(invoked); -} - -static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_state, interfaces::BlockTip tip, double verificationProgress, bool fHeader) +void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) { - if (fHeader) { + if (header) { // cache best headers time and height to reduce future cs_main locks - clientmodel->cachedBestHeaderHeight = tip.block_height; - clientmodel->cachedBestHeaderTime = tip.block_time; + cachedBestHeaderHeight = tip.block_height; + cachedBestHeaderTime = tip.block_time; } else { - clientmodel->m_cached_num_blocks = tip.block_height; - WITH_LOCK(clientmodel->m_cached_tip_mutex, clientmodel->m_cached_tip_blocks = tip.block_hash;); + m_cached_num_blocks = tip.block_height; + WITH_LOCK(m_cached_tip_mutex, m_cached_tip_blocks = tip.block_hash;); } // Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex. - const bool throttle = (sync_state != SynchronizationState::POST_INIT && !fHeader) || sync_state == SynchronizationState::INIT_REINDEX; + const bool throttle = (sync_state != SynchronizationState::POST_INIT && !header) || sync_state == SynchronizationState::INIT_REINDEX; const int64_t now = throttle ? GetTimeMillis() : 0; - int64_t& nLastUpdateNotification = fHeader ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; + int64_t& nLastUpdateNotification = header ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; if (throttle && now < nLastUpdateNotification + count_milliseconds(MODEL_UPDATE_DELAY)) { return; } - bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, - Q_ARG(int, tip.block_height), - Q_ARG(QDateTime, QDateTime::fromSecsSinceEpoch(tip.block_time)), - Q_ARG(double, verificationProgress), - Q_ARG(bool, fHeader), - Q_ARG(SynchronizationState, sync_state)); - assert(invoked); + Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, header, sync_state); nLastUpdateNotification = now; } void ClientModel::subscribeToCoreSignals() { - // Connect signals to client - m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); - m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1)); - m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1)); - m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this)); - m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this)); - m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, false)); - m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, true)); + m_handler_show_progress = m_node.handleShowProgress( + [this](const std::string& title, int progress, [[maybe_unused]] bool resume_possible) { + Q_EMIT showProgress(QString::fromStdString(title), progress); + }); + m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged( + [this](int new_num_connections) { + Q_EMIT numConnectionsChanged(new_num_connections); + }); + m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged( + [this](bool network_active) { + Q_EMIT networkActiveChanged(network_active); + }); + m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged( + [this]() { + qDebug() << "ClientModel: NotifyAlertChanged"; + Q_EMIT alertsChanged(getStatusBarWarnings()); + }); + m_handler_banned_list_changed = m_node.handleBannedListChanged( + [this]() { + qDebug() << "ClienModel: Requesting update for peer banlist"; + QMetaObject::invokeMethod(banTableModel, [this] { banTableModel->refresh(); }); + }); + m_handler_notify_block_tip = m_node.handleNotifyBlockTip( + [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) { + TipChanged(sync_state, tip, verification_progress, /*header=*/false); + }); + m_handler_notify_header_tip = m_node.handleNotifyHeaderTip( + [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) { + TipChanged(sync_state, tip, verification_progress, /*header=*/true); + }); } void ClientModel::unsubscribeFromCoreSignals() { - // Disconnect signals from client m_handler_show_progress->disconnect(); m_handler_notify_num_connections_changed->disconnect(); m_handler_notify_network_active_changed->disconnect(); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 846691c0c0..b10ffb4523 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -23,6 +23,7 @@ enum class SynchronizationState; namespace interfaces { class Handler; class Node; +struct BlockTip; } QT_BEGIN_NAMESPACE @@ -61,7 +62,7 @@ public: //! Return number of connections, default is in- and outbound (total) int getNumConnections(unsigned int flags = CONNECTIONS_ALL) const; int getNumBlocks() const; - uint256 getBestBlockHash(); + uint256 getBestBlockHash() EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex); int getHeaderTipHeight() const; int64_t getHeaderTipTime() const; @@ -104,6 +105,7 @@ private: //! A thread to interact with m_node asynchronously QThread* const m_thread; + void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header); void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); @@ -120,12 +122,6 @@ Q_SIGNALS: // Show progress dialog e.g. for verifychain void showProgress(const QString &title, int nProgress); - -public Q_SLOTS: - void updateNumConnections(int numConnections); - void updateNetworkActive(bool networkActive); - void updateAlert(); - void updateBanlist(); }; #endif // BITCOIN_QT_CLIENTMODEL_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 706cf2f7db..e3c6d8a624 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -74,7 +74,7 @@ #include <string> #include <vector> -#if defined(Q_OS_MAC) +#if defined(Q_OS_MACOS) #include <QProcess> @@ -399,7 +399,7 @@ bool isObscured(QWidget *w) void bringToFront(QWidget* w) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS ForceActivation(); #endif @@ -443,7 +443,7 @@ bool openBitcoinConf() /* Open bitcoin.conf with the associated application */ bool res = QDesktopServices::openUrl(QUrl::fromLocalFile(PathToQString(pathConfig))); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Workaround for macOS-specific behavior; see #15409. if (!res) { res = QProcess::startDetached("/usr/bin/open", QStringList{"-t", PathToQString(pathConfig)}); @@ -882,7 +882,7 @@ bool ItemDelegate::eventFilter(QObject *object, QEvent *event) void PolishProgressDialog(QProgressDialog* dialog) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index 51151b0be8..b311a9f32e 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -18,7 +18,7 @@ #include <QtDBus> #include <stdint.h> #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macnotificationhandler.h> #endif @@ -50,7 +50,7 @@ Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon mode = Freedesktop; } #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // check if users OS has support for NSUserNotification if( MacNotificationHandler::instance()->hasUserNotificationCenterSupport()) { mode = UserNotificationCenter; @@ -71,7 +71,7 @@ Notificator::~Notificator() class FreedesktopImage { public: - FreedesktopImage() {} + FreedesktopImage() = default; explicit FreedesktopImage(const QImage &img); // Image to variant that can be marshalled over DBus @@ -210,7 +210,7 @@ void Notificator::notifySystray(Class cls, const QString &title, const QString & trayIcon->showMessage(title, text, sicon, millisTimeout); } -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void Notificator::notifyMacUserNotificationCenter(const QString &title, const QString &text) { // icon is not supported by the user notification center yet. OSX will use the app icon. @@ -230,7 +230,7 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c case QSystemTray: notifySystray(cls, title, text, millisTimeout); break; -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS case UserNotificationCenter: notifyMacUserNotificationCenter(title, text); break; diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 7d80b43e43..642d2b29ae 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -69,7 +69,7 @@ private: void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif void notifySystray(Class cls, const QString &title, const QString &text, int millisTimeout); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void notifyMacUserNotificationCenter(const QString &title, const QString &text); #endif }; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f90765fe5b..e6ff43a379 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -78,7 +78,7 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->connectSocksTor, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState); /* Window elements init */ -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); /* hide launch at startup option on macOS */ @@ -261,7 +261,7 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor); /* Window */ -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (QSystemTrayIcon::isSystemTrayAvailable()) { mapper->addMapping(ui->showTrayIcon, OptionsModel::ShowTrayIcon); mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 40b9ed5483..612d3009c1 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -30,8 +30,8 @@ const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static const QString GetDefaultProxyAddress(); -OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : - QAbstractListModel(parent) +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) : + QAbstractListModel(parent), m_node{node} { Init(resetSettings); } @@ -335,260 +335,272 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const { if(role == Qt::EditRole) { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - return GUIUtil::GetStartOnSystemStartup(); - case ShowTrayIcon: - return m_show_tray_icon; - case MinimizeToTray: - return fMinimizeToTray; - case MapPortUPnP: + return getOption(OptionID(index.row())); + } + return QVariant(); +} + +// write QSettings values +bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + bool successful = true; /* set to false on parse error */ + if(role == Qt::EditRole) + { + successful = setOption(OptionID(index.row()), value); + } + + Q_EMIT dataChanged(index, index); + + return successful; +} + +QVariant OptionsModel::getOption(OptionID option) const +{ + QSettings settings; + switch (option) { + case StartAtStartup: + return GUIUtil::GetStartOnSystemStartup(); + case ShowTrayIcon: + return m_show_tray_icon; + case MinimizeToTray: + return fMinimizeToTray; + case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return settings.value("fUseUPnP"); #else - return false; + return false; #endif // USE_UPNP - case MapPortNatpmp: + case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return settings.value("fUseNatpmp"); #else - return false; + return false; #endif // USE_NATPMP - case MinimizeOnClose: - return fMinimizeOnClose; - - // default proxy - case ProxyUse: - return settings.value("fUseProxy", false); - case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; - case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; - - // separate Tor proxy - case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); - case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; - case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + case MinimizeOnClose: + return fMinimizeOnClose; + + // default proxy + case ProxyUse: + return settings.value("fUseProxy", false); + case ProxyIP: + return GetProxySetting(settings, "addrProxy").ip; + case ProxyPort: + return GetProxySetting(settings, "addrProxy").port; + + // separate Tor proxy + case ProxyUseTor: + return settings.value("fUseSeparateProxyTor", false); + case ProxyIPTor: + return GetProxySetting(settings, "addrSeparateProxyTor").ip; + case ProxyPortTor: + return GetProxySetting(settings, "addrSeparateProxyTor").port; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); - case ExternalSignerPath: - return settings.value("external_signer_path"); - case SubFeeFromAmount: - return m_sub_fee_from_amount; + case SpendZeroConfChange: + return settings.value("bSpendZeroConfChange"); + case ExternalSignerPath: + return settings.value("external_signer_path"); + case SubFeeFromAmount: + return m_sub_fee_from_amount; #endif - case DisplayUnit: - return QVariant::fromValue(m_display_bitcoin_unit); - case ThirdPartyTxUrls: - return strThirdPartyTxUrls; - case Language: - return settings.value("language"); - case UseEmbeddedMonospacedFont: - return m_use_embedded_monospaced_font; - case CoinControlFeatures: - return fCoinControlFeatures; - case EnablePSBTControls: - return settings.value("enable_psbt_controls"); - case Prune: - return settings.value("bPrune"); - case PruneSize: - return settings.value("nPruneSize"); - case DatabaseCache: - return settings.value("nDatabaseCache"); - case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); - case Listen: - return settings.value("fListen"); - case Server: - return settings.value("server"); - default: - return QVariant(); - } + case DisplayUnit: + return QVariant::fromValue(m_display_bitcoin_unit); + case ThirdPartyTxUrls: + return strThirdPartyTxUrls; + case Language: + return settings.value("language"); + case UseEmbeddedMonospacedFont: + return m_use_embedded_monospaced_font; + case CoinControlFeatures: + return fCoinControlFeatures; + case EnablePSBTControls: + return settings.value("enable_psbt_controls"); + case Prune: + return settings.value("bPrune"); + case PruneSize: + return settings.value("nPruneSize"); + case DatabaseCache: + return settings.value("nDatabaseCache"); + case ThreadsScriptVerif: + return settings.value("nThreadsScriptVerif"); + case Listen: + return settings.value("fListen"); + case Server: + return settings.value("server"); + default: + return QVariant(); } - return QVariant(); } -// write QSettings values -bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +bool OptionsModel::setOption(OptionID option, const QVariant& value) { bool successful = true; /* set to false on parse error */ - if(role == Qt::EditRole) - { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); - break; - case ShowTrayIcon: - m_show_tray_icon = value.toBool(); - settings.setValue("fHideTrayIcon", !m_show_tray_icon); - Q_EMIT showTrayIconChanged(m_show_tray_icon); - break; - case MinimizeToTray: - fMinimizeToTray = value.toBool(); - settings.setValue("fMinimizeToTray", fMinimizeToTray); - break; - case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); - break; - case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); - break; - case MinimizeOnClose: - fMinimizeOnClose = value.toBool(); - settings.setValue("fMinimizeOnClose", fMinimizeOnClose); - break; - - // default proxy - case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } - } + QSettings settings; + + switch (option) { + case StartAtStartup: + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } - } + case ShowTrayIcon: + m_show_tray_icon = value.toBool(); + settings.setValue("fHideTrayIcon", !m_show_tray_icon); + Q_EMIT showTrayIconChanged(m_show_tray_icon); + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + settings.setValue("fMinimizeToTray", fMinimizeToTray); + break; + case MapPortUPnP: // core option - can be changed on-the-fly + settings.setValue("fUseUPnP", value.toBool()); + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + settings.setValue("fUseNatpmp", value.toBool()); + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + settings.setValue("fMinimizeOnClose", fMinimizeOnClose); break; - // separate Tor proxy - case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + // default proxy + case ProxyUse: + if (settings.value("fUseProxy") != value) { + settings.setValue("fUseProxy", value.toBool()); + setRestartRequired(true); } break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + case ProxyIP: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); + } + } + break; + case ProxyPort: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); + } + } + break; + + // separate Tor proxy + case ProxyUseTor: + if (settings.value("fUseSeparateProxyTor") != value) { + settings.setValue("fUseSeparateProxyTor", value.toBool()); + setRestartRequired(true); } break; + case ProxyIPTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); + } + } + break; + case ProxyPortTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); + } + } + break; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); - setRestartRequired(true); - } - break; - case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); - setRestartRequired(true); - } - break; - case SubFeeFromAmount: - m_sub_fee_from_amount = value.toBool(); - settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); - break; + case SpendZeroConfChange: + if (settings.value("bSpendZeroConfChange") != value) { + settings.setValue("bSpendZeroConfChange", value); + setRestartRequired(true); + } + break; + case ExternalSignerPath: + if (settings.value("external_signer_path") != value.toString()) { + settings.setValue("external_signer_path", value.toString()); + setRestartRequired(true); + } + break; + case SubFeeFromAmount: + m_sub_fee_from_amount = value.toBool(); + settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); + break; #endif - case DisplayUnit: - setDisplayUnit(value); - break; - case ThirdPartyTxUrls: - if (strThirdPartyTxUrls != value.toString()) { - strThirdPartyTxUrls = value.toString(); - settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); - setRestartRequired(true); - } - break; - case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); - setRestartRequired(true); - } - break; - case UseEmbeddedMonospacedFont: - m_use_embedded_monospaced_font = value.toBool(); - settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); - Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); - break; - case CoinControlFeatures: - fCoinControlFeatures = value.toBool(); - settings.setValue("fCoinControlFeatures", fCoinControlFeatures); - Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); - break; - case EnablePSBTControls: - m_enable_psbt_controls = value.toBool(); - settings.setValue("enable_psbt_controls", m_enable_psbt_controls); - break; - case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); - setRestartRequired(true); - } - break; - case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); - } - break; - case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); - setRestartRequired(true); - } - break; - case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); - setRestartRequired(true); - } - break; - case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; - case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); - setRestartRequired(true); - } - break; - default: - break; + case DisplayUnit: + setDisplayUnit(value); + break; + case ThirdPartyTxUrls: + if (strThirdPartyTxUrls != value.toString()) { + strThirdPartyTxUrls = value.toString(); + settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); + setRestartRequired(true); } + break; + case Language: + if (settings.value("language") != value) { + settings.setValue("language", value); + setRestartRequired(true); + } + break; + case UseEmbeddedMonospacedFont: + m_use_embedded_monospaced_font = value.toBool(); + settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); + Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); + break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); + break; + case EnablePSBTControls: + m_enable_psbt_controls = value.toBool(); + settings.setValue("enable_psbt_controls", m_enable_psbt_controls); + break; + case Prune: + if (settings.value("bPrune") != value) { + settings.setValue("bPrune", value); + setRestartRequired(true); + } + break; + case PruneSize: + if (settings.value("nPruneSize") != value) { + settings.setValue("nPruneSize", value); + setRestartRequired(true); + } + break; + case DatabaseCache: + if (settings.value("nDatabaseCache") != value) { + settings.setValue("nDatabaseCache", value); + setRestartRequired(true); + } + break; + case ThreadsScriptVerif: + if (settings.value("nThreadsScriptVerif") != value) { + settings.setValue("nThreadsScriptVerif", value); + setRestartRequired(true); + } + break; + case Listen: + if (settings.value("fListen") != value) { + settings.setValue("fListen", value); + setRestartRequired(true); + } + break; + case Server: + if (settings.value("server") != value) { + settings.setValue("server", value); + setRestartRequired(true); + } + break; + default: + break; } - Q_EMIT dataChanged(index, index); - return successful; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 510ebb5cfd..92f80ecf21 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -41,7 +41,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); enum OptionID { StartAtStartup, // bool @@ -80,6 +80,8 @@ public: int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + QVariant getOption(OptionID option) const; + bool setOption(OptionID option, const QVariant& value); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -103,11 +105,10 @@ public: void setRestartRequired(bool fRequired); bool isRestartRequired() const; - interfaces::Node& node() const { assert(m_node); return *m_node; } - void setNode(interfaces::Node& node) { assert(!m_node); m_node = &node; } + interfaces::Node& node() const { return m_node; } private: - interfaces::Node* m_node = nullptr; + interfaces::Node& m_node; /* Qt-only settings */ bool m_show_tray_icon; bool fMinimizeToTray; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 820bcbf3cd..a8133f481e 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -35,8 +35,7 @@ class TxViewDelegate : public QAbstractItemDelegate Q_OBJECT public: explicit TxViewDelegate(const PlatformStyle* _platformStyle, QObject* parent = nullptr) - : QAbstractItemDelegate(parent), unit(BitcoinUnit::BTC), - platformStyle(_platformStyle) + : QAbstractItemDelegate(parent), platformStyle(_platformStyle) { connect(this, &TxViewDelegate::width_changed, this, &TxViewDelegate::sizeHintChanged); } @@ -125,7 +124,7 @@ public: return {DECORATION_SIZE + 8 + minimum_text_width, DECORATION_SIZE}; } - BitcoinUnit unit; + BitcoinUnit unit{BitcoinUnit::BTC}; Q_SIGNALS: //! An intermediate signal for emitting from the `paint() const` member function. diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index c82f0683fc..be6f604932 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -158,9 +158,7 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : } } -PaymentServer::~PaymentServer() -{ -} +PaymentServer::~PaymentServer() = default; // // OSX-specific way of handling bitcoin: URIs diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 41c389d9cc..b7de88225e 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -28,10 +28,7 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) : refresh(); } -PeerTableModel::~PeerTableModel() -{ - // Intentionally left empty -} +PeerTableModel::~PeerTableModel() = default; void PeerTableModel::startAutoRefresh() { diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 03ca9ad7dc..061513b58f 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -34,10 +34,7 @@ RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &RecentRequestsTableModel::updateDisplayUnit); } -RecentRequestsTableModel::~RecentRequestsTableModel() -{ - /* Intentionally left empty */ -} +RecentRequestsTableModel::~RecentRequestsTableModel() = default; int RecentRequestsTableModel::rowCount(const QModelIndex &parent) const { diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 4a51990f88..b791fd30c4 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -27,14 +27,6 @@ #include <univalue.h> -#ifdef ENABLE_WALLET -#ifdef USE_BDB -#include <wallet/bdb.h> -#endif -#include <wallet/db.h> -#include <wallet/wallet.h> -#endif - #include <QAbstractButton> #include <QAbstractItemModel> #include <QDateTime> @@ -120,7 +112,7 @@ public: connect(&timer, &QTimer::timeout, [this]{ func(); }); timer.start(millis); } - ~QtRPCTimerBase() {} + ~QtRPCTimerBase() = default; private: QTimer timer; std::function<void()> func; @@ -129,7 +121,7 @@ private: class QtRPCTimerInterface: public RPCTimerInterface { public: - ~QtRPCTimerInterface() {} + ~QtRPCTimerInterface() = default; const char *Name() override { return "Qt"; } RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override { @@ -457,7 +449,7 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode { try // Nice formatting for standard-format error { - int code = find_value(objError, "code").get_int(); + int code = find_value(objError, "code").getInt<int>(); std::string message = find_value(objError, "message").get_str(); Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); } @@ -871,7 +863,7 @@ void RPCConsole::clear(bool keep_prompt) } // Set default style sheet -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont(/*use_embedded_font=*/true)); #else QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index fd8eccb86d..d8daccecbd 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -757,10 +757,6 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn case WalletModel::AbsurdFee: msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee())); break; - case WalletModel::PaymentRequestExpired: - msgParams.first = tr("Payment request expired."); - msgParams.second = CClientUIInterface::MSG_ERROR; - break; // included to prevent a compiler warning. case WalletModel::OK: default: diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 66637a5dcf..3b7a40438b 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -8,6 +8,7 @@ #include <interfaces/chain.h> #include <interfaces/node.h> +#include <qt/addressbookpage.h> #include <qt/clientmodel.h> #include <qt/editaddressdialog.h> #include <qt/optionsmodel.h> @@ -23,8 +24,10 @@ #include <chrono> #include <QApplication> -#include <QTimer> +#include <QLineEdit> #include <QMessageBox> +#include <QTableView> +#include <QTimer> using wallet::AddWallet; using wallet::CWallet; @@ -100,11 +103,13 @@ void TestAddAddressesToSendBook(interfaces::Node& node) QString s_label("already here (s)"); // Define a new address (which should add to the address book successfully). - QString new_address; + QString new_address_a; + QString new_address_b; std::tie(r_key_dest, preexisting_r_address) = build_address(); std::tie(s_key_dest, preexisting_s_address) = build_address(); - std::tie(std::ignore, new_address) = build_address(); + std::tie(std::ignore, new_address_a) = build_address(); + std::tie(std::ignore, new_address_b) = build_address(); { LOCK(wallet->cs_wallet); @@ -122,7 +127,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -131,14 +136,19 @@ void TestAddAddressesToSendBook(interfaces::Node& node) EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); + AddressBookPage address_book{platformStyle.get(), AddressBookPage::ForEditing, AddressBookPage::SendingTab}; + address_book.setModel(walletModel.getAddressTableModel()); + auto table_view = address_book.findChild<QTableView*>("tableView"); + QCOMPARE(table_view->model()->rowCount(), 1); + EditAddressAndSubmit( &editAddressDialog, QString("uhoh"), preexisting_r_address, QString( "Address \"%1\" already exists as a receiving address with label " "\"%2\" and so cannot be added as a sending address." ).arg(preexisting_r_address).arg(r_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); EditAddressAndSubmit( &editAddressDialog, QString("uhoh, different"), preexisting_s_address, @@ -146,22 +156,65 @@ void TestAddAddressesToSendBook(interfaces::Node& node) "The entered address \"%1\" is already in the address book with " "label \"%2\"." ).arg(preexisting_s_address).arg(s_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); // Submit a new address which should add successfully - we expect the // warning message to be blank. EditAddressAndSubmit( - &editAddressDialog, QString("new"), new_address, QString("")); - + &editAddressDialog, QString("io - new A"), new_address_a, QString("")); check_addbook_size(3); + QCOMPARE(table_view->model()->rowCount(), 2); + + EditAddressAndSubmit( + &editAddressDialog, QString("io - new B"), new_address_b, QString("")); + check_addbook_size(4); + QCOMPARE(table_view->model()->rowCount(), 3); + + auto search_line = address_book.findChild<QLineEdit*>("searchLineEdit"); + + search_line->setText(r_label); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(s_label); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText("io"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wildcard "?". + search_line->setText("io?new"); + QCOMPARE(table_view->model()->rowCount(), 0); + search_line->setText("io???new"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wildcard "*". + search_line->setText("io*new"); + QCOMPARE(table_view->model()->rowCount(), 2); + search_line->setText("*"); + QCOMPARE(table_view->model()->rowCount(), 3); + + search_line->setText(preexisting_r_address); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(preexisting_s_address); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_a); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_b); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(""); + QCOMPARE(table_view->model()->rowCount(), 3); } } // namespace void AddressBookTests::addressBookTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 099ac5fcae..9648ef6188 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -58,7 +58,7 @@ void TestRpcCommand(RPCConsole* console) //! Entry point for BitcoinApplication tests. void AppTests::appTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, @@ -119,6 +119,6 @@ AppTests::HandleCallback::~HandleCallback() assert(it != callbacks.end()); callbacks.erase(it); if (callbacks.empty()) { - m_app_tests.m_app.quit(); + m_app_tests.m_app.exit(0); } } diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 4a943a6343..2ef9230c59 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -13,8 +13,7 @@ #include <univalue.h> -//! Entry point for BitcoinApplication tests. -void OptionTests::optionTests() +void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure // that setting integer prune value doesn't cause an exception to be thrown @@ -24,7 +23,7 @@ void OptionTests::optionTests() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{}; + OptionsModel{m_node}; gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -49,7 +48,7 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{}; + OptionsModel{m_node}; const bool expected{false}; diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 39c1612c8f..257a0b65be 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -16,7 +16,7 @@ public: explicit OptionTests(interfaces::Node& node) : m_node(node) {} private Q_SLOTS: - void optionTests(); + void integerGetArgBug(); void parametersInteraction(); private: diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index c4cd0f4cd1..bc06f0f23b 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -184,7 +184,7 @@ void TestGUI(interfaces::Node& node) std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -315,7 +315,7 @@ void TestGUI(interfaces::Node& node) void WalletTests::walletTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 9e92f89543..a61d5407b3 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -36,13 +36,41 @@ QString TransactionDesc::FormatTxStatus(const interfaces::WalletTxStatus& status { int depth = status.depth_in_main_chain; if (depth < 0) { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This status + represents an unconfirmed transaction that conflicts with a confirmed + transaction. */ return tr("conflicted with a transaction with %1 confirmations").arg(-depth); } else if (depth == 0) { - const QString abandoned{status.is_abandoned ? QLatin1String(", ") + tr("abandoned") : QString()}; - return tr("0/unconfirmed, %1").arg(inMempool ? tr("in memory pool") : tr("not in memory pool")) + abandoned; + QString s; + if (inMempool) { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This status + represents an unconfirmed transaction that is in the memory pool. */ + s = tr("0/unconfirmed, in memory pool"); + } else { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This status + represents an unconfirmed transaction that is not in the memory pool. */ + s = tr("0/unconfirmed, not in memory pool"); + } + if (status.is_abandoned) { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This + status represents an abandoned transaction. */ + s += QLatin1String(", ") + tr("abandoned"); + } + return s; } else if (depth < 6) { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This + status represents a transaction confirmed in at least one block, + but less than 6 blocks. */ return tr("%1/unconfirmed").arg(depth); } else { + /*: Text explaining the current status of a transaction, shown in the + status field of the details window for this transaction. This status + represents a transaction confirmed in 6 or more blocks. */ return tr("%1 confirmations").arg(depth); } } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 7b932890cf..4312b3cd24 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -62,7 +62,7 @@ struct TxLessThan struct TransactionNotification { public: - TransactionNotification() {} + TransactionNotification() = default; TransactionNotification(uint256 _hash, ChangeType _status, bool _showTransaction): hash(_hash), status(_status), showTransaction(_showTransaction) {} diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index dc4e25a02b..11bea85b21 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -55,9 +55,7 @@ WalletFrame::WalletFrame(const PlatformStyle* _platformStyle, QWidget* parent) walletStack->addWidget(no_wallet_group); } -WalletFrame::~WalletFrame() -{ -} +WalletFrame::~WalletFrame() = default; void WalletFrame::setClientModel(ClientModel *_clientModel) { diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 5ee32e79d5..1f6c90af4a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -145,7 +145,7 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } -bool WalletModel::validateAddress(const QString &address) +bool WalletModel::validateAddress(const QString& address) const { return IsValidDestinationString(address.toStdString()); } @@ -284,22 +284,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran return SendCoinsReturn(OK); } -OptionsModel *WalletModel::getOptionsModel() +OptionsModel* WalletModel::getOptionsModel() const { return optionsModel; } -AddressTableModel *WalletModel::getAddressTableModel() +AddressTableModel* WalletModel::getAddressTableModel() const { return addressTableModel; } -TransactionTableModel *WalletModel::getTransactionTableModel() +TransactionTableModel* WalletModel::getTransactionTableModel() const { return transactionTableModel; } -RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() +RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const { return recentRequestsTableModel; } @@ -369,7 +369,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), @@ -557,7 +557,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) return true; } -bool WalletModel::displayAddress(std::string sAddress) +bool WalletModel::displayAddress(std::string sAddress) const { CTxDestination dest = DecodeDestination(sAddress); bool res = false; @@ -585,7 +585,7 @@ QString WalletModel::getDisplayName() const return name.isEmpty() ? "["+tr("default wallet")+"]" : name; } -bool WalletModel::isMultiwallet() +bool WalletModel::isMultiwallet() const { return m_node.walletLoader().getWallets().size() > 1; } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ad1239ccdc..d524d48111 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -66,8 +66,7 @@ public: AmountWithFeeExceedsBalance, DuplicateAddress, TransactionCreationFailed, // Error returned when wallet is still locked - AbsurdFee, - PaymentRequestExpired + AbsurdFee }; enum EncryptionStatus @@ -77,15 +76,15 @@ public: Unlocked // wallet->IsCrypted() && !wallet->IsLocked() }; - OptionsModel *getOptionsModel(); - AddressTableModel *getAddressTableModel(); - TransactionTableModel *getTransactionTableModel(); - RecentRequestsTableModel *getRecentRequestsTableModel(); + OptionsModel* getOptionsModel() const; + AddressTableModel* getAddressTableModel() const; + TransactionTableModel* getTransactionTableModel() const; + RecentRequestsTableModel* getRecentRequestsTableModel() const; EncryptionStatus getEncryptionStatus() const; // Check address for validity - bool validateAddress(const QString &address); + bool validateAddress(const QString& address) const; // Return status record for SendCoins, contains error id + information struct SendCoinsReturn @@ -137,7 +136,7 @@ public: UnlockContext requestUnlock(); bool bumpFee(uint256 hash, uint256& new_hash); - bool displayAddress(std::string sAddress); + bool displayAddress(std::string sAddress) const; static bool isWalletEnabled(); @@ -149,9 +148,7 @@ public: QString getWalletName() const; QString getDisplayName() const; - bool isMultiwallet(); - - AddressTableModel* getAddressTableModel() const { return addressTableModel; } + bool isMultiwallet() const; void refresh(bool pk_hash_only = false); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 344bf628bb..2f92c57607 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -111,9 +111,7 @@ WalletView::WalletView(WalletModel* wallet_model, const PlatformStyle* _platform connect(walletModel, &WalletModel::showProgress, this, &WalletView::showProgress); } -WalletView::~WalletView() -{ -} +WalletView::~WalletView() = default; void WalletView::setClientModel(ClientModel *_clientModel) { diff --git a/src/random.cpp b/src/random.cpp index 6ae08103b1..74ceb3d2a3 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -370,11 +370,9 @@ public: InitHardwareRand(); } - ~RNGState() - { - } + ~RNGState() = default; - void AddEvent(uint32_t event_info) noexcept + void AddEvent(uint32_t event_info) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_events_mutex) { LOCK(m_events_mutex); @@ -388,7 +386,7 @@ public: /** * Feed (the hash of) all events added through AddEvent() to hasher. */ - void SeedEvents(CSHA512& hasher) noexcept + void SeedEvents(CSHA512& hasher) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_events_mutex) { // We use only SHA256 for the events hashing to get the ASM speedups we have for SHA256, // since we want it to be fast as network peers may be able to trigger it repeatedly. @@ -407,7 +405,7 @@ public: * * If this function has never been called with strong_seed = true, false is returned. */ - bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { assert(num <= 32); unsigned char buf[64]; @@ -586,16 +584,11 @@ void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(e bool g_mock_deterministic_tests{false}; -uint64_t GetRand(uint64_t nMax) noexcept +uint64_t GetRandInternal(uint64_t nMax) noexcept { return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); } -int GetRandInt(int nMax) noexcept -{ - return GetRand(nMax); -} - uint256 GetRandHash() noexcept { uint256 hash; diff --git a/src/random.h b/src/random.h index 285158b1c3..b92c29f0be 100644 --- a/src/random.h +++ b/src/random.h @@ -69,7 +69,17 @@ */ void GetRandBytes(Span<unsigned char> bytes) noexcept; /** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */ -uint64_t GetRand(uint64_t nMax) noexcept; +uint64_t GetRandInternal(uint64_t nMax) noexcept; +/** Generate a uniform random integer of type T in the range [0..nMax) + * nMax defaults to std::numeric_limits<T>::max() + * Precondition: nMax > 0, T is an integral type, no larger than uint64_t + */ +template<typename T> +T GetRand(T nMax=std::numeric_limits<T>::max()) noexcept { + static_assert(std::is_integral<T>(), "T must be integral"); + static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<uint64_t>::max(), "GetRand only supports up to uint64_t"); + return T(GetRandInternal(nMax)); +} /** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ template <typename D> D GetRandomDuration(typename std::common_type<D>::type max) noexcept @@ -95,7 +105,6 @@ constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>; * */ std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); -int GetRandInt(int nMax) noexcept; uint256 GetRandHash() noexcept; /** @@ -223,6 +232,17 @@ public: /** Generate a random boolean. */ bool randbool() noexcept { return randbits(1); } + /** Return the time point advanced by a uniform random duration. */ + template <typename Tp> + Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) + { + using Dur = typename Tp::duration; + Dur dur{range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} : + range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} : + /* interval [0..0] */ Dur{0}}; + return time + dur; + } + // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } diff --git a/src/rest.cpp b/src/rest.cpp index 22b5d2e074..1b90baaf95 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -305,7 +305,7 @@ static bool rest_block(const std::any& context, if (chainman.m_blockman.IsBlockPruned(pblockindex)) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) + if (!ReadBlockFromDisk(block, pblockindex, chainman.GetParams().GetConsensus())) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 57b5178d78..d682a7d3e8 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -19,11 +19,11 @@ #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> +#include <kernel/coinstats.h> #include <logging/timer.h> #include <net.h> #include <net_processing.h> #include <node/blockstorage.h> -#include <node/coinstats.h> #include <node/context.h> #include <node/utxo_snapshot.h> #include <primitives/transaction.h> @@ -51,10 +51,10 @@ #include <memory> #include <mutex> +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; + using node::BlockManager; -using node::CCoinsStats; -using node::CoinStatsHashType; -using node::GetUTXOStats; using node::NodeContext; using node::ReadBlockFromDisk; using node::SnapshotMetadata; @@ -110,7 +110,7 @@ static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateMan CChain& active_chain = chainman.ActiveChain(); if (param.isNum()) { - const int height{param.get_int()}; + const int height{param.getInt<int>()}; if (height < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); } @@ -271,7 +271,7 @@ static RPCHelpMan waitfornewblock() { int timeout = 0; if (!request.params[0].isNull()) - timeout = request.params[0].get_int(); + timeout = request.params[0].getInt<int>(); CUpdatedBlock block; { @@ -317,7 +317,7 @@ static RPCHelpMan waitforblock() uint256 hash(ParseHashV(request.params[0], "blockhash")); if (!request.params[1].isNull()) - timeout = request.params[1].get_int(); + timeout = request.params[1].getInt<int>(); CUpdatedBlock block; { @@ -361,10 +361,10 @@ static RPCHelpMan waitforblockheight() { int timeout = 0; - int height = request.params[0].get_int(); + int height = request.params[0].getInt<int>(); if (!request.params[1].isNull()) - timeout = request.params[1].get_int(); + timeout = request.params[1].getInt<int>(); CUpdatedBlock block; { @@ -445,7 +445,7 @@ static RPCHelpMan getblockfrompeer() PeerManager& peerman = EnsurePeerman(node); const uint256& block_hash{ParseHashV(request.params[0], "blockhash")}; - const NodeId peer_id{request.params[1].get_int64()}; + const NodeId peer_id{request.params[1].getInt<int64_t>()}; const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash);); @@ -485,7 +485,7 @@ static RPCHelpMan getblockhash() LOCK(cs_main); const CChain& active_chain = chainman.ActiveChain(); - int nHeight = request.params[0].get_int(); + int nHeight = request.params[0].getInt<int>(); if (nHeight < 0 || nHeight > active_chain.Height()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); @@ -694,7 +694,7 @@ static RPCHelpMan getblock() if (request.params[1].isBool()) { verbosity = request.params[1].get_bool() ? 1 : 0; } else { - verbosity = request.params[1].get_int(); + verbosity = request.params[1].getInt<int>(); } } @@ -759,7 +759,7 @@ static RPCHelpMan pruneblockchain() CChainState& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; - int heightParam = request.params[0].get_int(); + int heightParam = request.params[0].getInt<int>(); if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); } @@ -777,11 +777,11 @@ static RPCHelpMan pruneblockchain() unsigned int height = (unsigned int) heightParam; unsigned int chainHeight = (unsigned int) active_chain.Height(); - if (chainHeight < Params().PruneAfterHeight()) + if (chainHeight < chainman.GetParams().PruneAfterHeight()) { throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); - else if (height > chainHeight) + } else if (height > chainHeight) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); - else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { + } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } @@ -790,7 +790,7 @@ static RPCHelpMan pruneblockchain() const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())}; const CBlockIndex* last_block{active_chainstate.m_blockman.GetFirstStoredBlock(block)}; - return static_cast<uint64_t>(last_block->nHeight); + return static_cast<int64_t>(last_block->nHeight - 1); }, }; } @@ -808,6 +808,36 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input) } } +/** + * Calculate statistics about the unspent transaction output set + * + * @param[in] index_requested Signals if the coinstatsindex should be used (when available). + */ +static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, + kernel::CoinStatsHashType hash_type, + const std::function<void()>& interruption_point = {}, + const CBlockIndex* pindex = nullptr, + bool index_requested = true) +{ + // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested + if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) { + if (pindex) { + return g_coin_stats_index->LookUpStats(pindex); + } else { + CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + return g_coin_stats_index->LookUpStats(block_index); + } + } + + // If the coinstats index isn't requested or is otherwise not usable, the + // pindex should either be null or equal to the view's best block. This is + // because without the coinstats index we can only get coinstats about the + // best block. + CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock()); + + return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point); +} + static RPCHelpMan gettxoutsetinfo() { return RPCHelpMan{"gettxoutsetinfo", @@ -862,8 +892,7 @@ static RPCHelpMan gettxoutsetinfo() const CBlockIndex* pindex{nullptr}; const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; - CCoinsStats stats{hash_type}; - stats.index_requested = request.params[2].isNull() || request.params[2].get_bool(); + bool index_requested = request.params[2].isNull() || request.params[2].get_bool(); NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); @@ -884,14 +913,14 @@ static RPCHelpMan gettxoutsetinfo() throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex"); } - if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) { + if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block"); } pindex = ParseHashOrHeight(request.params[1], chainman); } - if (stats.index_requested && g_coin_stats_index) { + if (index_requested && g_coin_stats_index) { if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) { const IndexSummary summary{g_coin_stats_index->GetSummary()}; @@ -903,7 +932,9 @@ static RPCHelpMan gettxoutsetinfo() } } - if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { + const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested); + if (maybe_stats.has_value()) { + const CCoinsStats& stats = maybe_stats.value(); ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs); @@ -922,10 +953,13 @@ static RPCHelpMan gettxoutsetinfo() } else { ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount)); - CCoinsStats prev_stats{hash_type}; - + CCoinsStats prev_stats{}; if (pindex->nHeight > 0) { - GetUTXOStats(coins_view, *blockman, prev_stats, node.rpc_interruption_point, pindex->pprev); + const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested); + if (!maybe_prev_stats) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); + } + prev_stats = maybe_prev_stats.value(); } UniValue block_info(UniValue::VOBJ); @@ -993,8 +1027,7 @@ static RPCHelpMan gettxout() UniValue ret(UniValue::VOBJ); uint256 hash(ParseHashV(request.params[0], "txid")); - int n = request.params[1].get_int(); - COutPoint out(hash, n); + COutPoint out{hash, request.params[1].getInt<uint32_t>()}; bool fMempool = true; if (!request.params[2].isNull()) fMempool = request.params[2].get_bool(); @@ -1051,39 +1084,39 @@ static RPCHelpMan verifychain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()}; - const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; + const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()}; + const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, Params().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, }; } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep) { // For buried deployments. - if (!DeploymentEnabled(params, dep)) return; + if (!DeploymentEnabled(chainman, dep)) return; UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); // getdeploymentinfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", DeploymentActiveAfter(blockindex, params, dep)); - rv.pushKV("height", params.DeploymentHeight(dep)); + rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep)); + rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep)); softforks.pushKV(DeploymentName(dep), rv); } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id) { // For BIP9 deployments. - if (!DeploymentEnabled(consensusParams, id)) return; + if (!DeploymentEnabled(chainman, id)) return; if (blockindex == nullptr) return; auto get_state_name = [](const ThresholdState state) -> std::string { @@ -1099,29 +1132,29 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo UniValue bip9(UniValue::VOBJ); - const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id); - const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id); + const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id); + const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id); const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state); // BIP9 parameters if (has_signal) { - bip9.pushKV("bit", consensusParams.vDeployments[id].bit); + bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit); } - bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); - bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); + bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime); + bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout); + bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height); // BIP9 status bip9.pushKV("status", get_state_name(current_state)); - bip9.pushKV("since", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id)); + bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id)); bip9.pushKV("status_next", get_state_name(next_state)); // BIP9 signalling status, if applicable if (has_signal) { UniValue statsUV(UniValue::VOBJ); std::vector<bool> signals; - BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals); + BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); @@ -1142,7 +1175,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); if (ThresholdState::ACTIVE == next_state) { - rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id)); + rv.pushKV("height", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id)); } rv.pushKV("active", ThresholdState::ACTIVE == next_state); rv.pushKV("bip9", bip9); @@ -1150,16 +1183,9 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo softforks.pushKV(DeploymentName(id), rv); } -namespace { -/* TODO: when -deprecatedrpc=softforks is removed, drop these */ -UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams); -extern const std::vector<RPCResult> RPCHelpForDeployment; -} - // used by rest.cpp:rest_chaininfo, so cannot be static RPCHelpMan getblockchaininfo() { - /* TODO: from v24, remove -deprecatedrpc=softforks */ return RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, @@ -1178,15 +1204,9 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, - {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"}, + {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"}, {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"}, {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"}, - {RPCResult::Type::OBJ_DYN, "softforks", /*optional=*/true, "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks", - { - {RPCResult::Type::OBJ, "xxxx", "name of the softfork", - RPCHelpForDeployment - }, - }}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, RPCExamples{ @@ -1203,14 +1223,14 @@ RPCHelpMan getblockchaininfo() const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; const int height{tip.nHeight}; UniValue obj(UniValue::VOBJ); - obj.pushKV("chain", Params().NetworkIDString()); + obj.pushKV("chain", chainman.GetParams().NetworkIDString()); obj.pushKV("blocks", height); obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1); obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex()); obj.pushKV("difficulty", GetDifficulty(&tip)); obj.pushKV("time", tip.GetBlockTime()); obj.pushKV("mediantime", tip.GetMedianTimePast()); - obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), &tip)); + obj.pushKV("verificationprogress", GuessVerificationProgress(chainman.GetParams().TxData(), &tip)); obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); obj.pushKV("chainwork", tip.nChainWork.GetHex()); obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage()); @@ -1226,11 +1246,6 @@ RPCHelpMan getblockchaininfo() } } - if (IsDeprecatedRPCEnabled("softforks")) { - const Consensus::Params& consensusParams = Params().GetConsensus(); - obj.pushKV("softforks", DeploymentInfo(&tip, consensusParams)); - } - obj.pushKV("warnings", GetWarnings(false).original); return obj; }, @@ -1263,16 +1278,16 @@ const std::vector<RPCResult> RPCHelpForDeployment{ }}, }; -UniValue DeploymentInfo(const CBlockIndex* blockindex, const Consensus::Params& consensusParams) +UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman) { UniValue softforks(UniValue::VOBJ); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CSV); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT); return softforks; } } // anon namespace @@ -1311,12 +1326,10 @@ static RPCHelpMan getdeploymentinfo() } } - const Consensus::Params& consensusParams = Params().GetConsensus(); - UniValue deploymentinfo(UniValue::VOBJ); deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString()); deploymentinfo.pushKV("height", blockindex->nHeight); - deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, consensusParams)); + deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman)); return deploymentinfo; }, }; @@ -1584,7 +1597,7 @@ static RPCHelpMan getchaintxstats() { ChainstateManager& chainman = EnsureAnyChainman(request.context); const CBlockIndex* pindex; - int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month + int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month if (request.params[1].isNull()) { LOCK(cs_main); @@ -1606,7 +1619,7 @@ static RPCHelpMan getchaintxstats() if (request.params[0].isNull()) { blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1)); } else { - blockcount = request.params[0].get_int(); + blockcount = request.params[0].getInt<int>(); if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1"); @@ -1900,7 +1913,7 @@ static RPCHelpMan getblockstats() ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate); ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize); ret_all.pushKV("outs", outputs); - ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, Params().GetConsensus())); + ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus())); ret_all.pushKV("swtotal_size", swtotal_size); ret_all.pushKV("swtotal_weight", swtotal_weight); ret_all.pushKV("swtxs", swtxs); @@ -1969,9 +1982,9 @@ static std::atomic<bool> g_should_abort_scan; class CoinsViewScanReserver { private: - bool m_could_reserve; + bool m_could_reserve{false}; public: - explicit CoinsViewScanReserver() : m_could_reserve(false) {} + explicit CoinsViewScanReserver() = default; bool reserve() { CHECK_NONFATAL(!m_could_reserve); @@ -2072,7 +2085,7 @@ static RPCHelpMan scantxoutset() // no scan in progress return NullUniValue; } - result.pushKV("progress", g_scan_progress); + result.pushKV("progress", g_scan_progress.load()); return result; } else if (request.params[0].get_str() == "abort") { CoinsViewScanReserver reserver; @@ -2281,6 +2294,12 @@ static RPCHelpMan dumptxoutset() FILE* file{fsbridge::fopen(temppath, "wb")}; CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; + if (afile.IsNull()) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Couldn't open file " + temppath.u8string() + " for writing."); + } + NodeContext& node = EnsureAnyNodeContext(request.context); UniValue result = CreateUTXOSnapshot( node, node.chainman->ActiveChainstate(), afile, path, temppath); @@ -2300,7 +2319,7 @@ UniValue CreateUTXOSnapshot( const fs::path& temppath) { std::unique_ptr<CCoinsViewCursor> pcursor; - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; + std::optional<CCoinsStats> maybe_stats; const CBlockIndex* tip; { @@ -2320,19 +2339,20 @@ UniValue CreateUTXOSnapshot( chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) { + maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point); + if (!maybe_stats) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = chainstate.CoinsDB().Cursor(); - tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(stats.hashBlock)); + tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock)); } LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", tip->nHeight, tip->GetBlockHash().ToString(), fs::PathToString(path), fs::PathToString(temppath))); - SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx}; + SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx}; afile << metadata; @@ -2354,11 +2374,11 @@ UniValue CreateUTXOSnapshot( afile.fclose(); UniValue result(UniValue::VOBJ); - result.pushKV("coins_written", stats.coins_count); + result.pushKV("coins_written", maybe_stats->coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.u8string()); - result.pushKV("txoutset_hash", stats.hashSerialized.ToString()); + result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString()); // Cast required because univalue doesn't have serialization specified for // `unsigned int`, nChainTx's type. result.pushKV("nchaintx", uint64_t{tip->nChainTx}); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 23e9d4074c..ae0d0112ba 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -173,6 +173,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "setwalletflag", 1, "value" }, { "getmempoolancestors", 1, "verbose" }, { "getmempooldescendants", 1, "verbose" }, + { "gettxspendingprevout", 0, "outputs" }, { "bumpfee", 1, "options" }, { "psbtbumpfee", 1, "options" }, { "logging", 0, "include" }, diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index bfec0780aa..1873bc1587 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -7,6 +7,7 @@ #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> +#include <policy/settings.h> #include <rpc/protocol.h> #include <rpc/request.h> #include <rpc/server.h> @@ -16,7 +17,6 @@ #include <univalue.h> #include <util/fees.h> #include <util/system.h> -#include <validation.h> #include <algorithm> #include <array> diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 27080d3881..97ec95a166 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -8,6 +8,7 @@ #include <core_io.h> #include <fs.h> #include <policy/rbf.h> +#include <policy/settings.h> #include <primitives/transaction.h> #include <rpc/server.h> #include <rpc/server_util.h> @@ -15,7 +16,6 @@ #include <txmempool.h> #include <univalue.h> #include <util/moneystr.h> -#include <validation.h> using node::DEFAULT_MAX_RAW_TX_FEE_RATE; using node::NodeContext; @@ -229,23 +229,12 @@ static std::vector<RPCResult> MempoolEntryDescription() return { RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."}, RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."}, - RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, - "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true, - "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT + - " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"}, RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"}, RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true, - "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + - CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, - "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + - CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"}, RPCResult{RPCResult::Type::OBJ, "fees", "", { @@ -269,24 +258,12 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool info.pushKV("vsize", (int)e.GetTxSize()); info.pushKV("weight", (int)e.GetTxWeight()); - // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24 - const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")}; - if (deprecated_fee_fields_enabled) { - info.pushKV("fee", ValueFromAmount(e.GetFee())); - info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); - } info.pushKV("time", count_seconds(e.GetTime())); info.pushKV("height", (int)e.GetHeight()); info.pushKV("descendantcount", e.GetCountWithDescendants()); info.pushKV("descendantsize", e.GetSizeWithDescendants()); - if (deprecated_fee_fields_enabled) { - info.pushKV("descendantfees", e.GetModFeesWithDescendants()); - } info.pushKV("ancestorcount", e.GetCountWithAncestors()); info.pushKV("ancestorsize", e.GetSizeWithAncestors()); - if (deprecated_fee_fields_enabled) { - info.pushKV("ancestorfees", e.GetModFeesWithAncestors()); - } info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString()); UniValue fees(UniValue::VOBJ); @@ -587,6 +564,89 @@ static RPCHelpMan getmempoolentry() }; } +static RPCHelpMan gettxspendingprevout() +{ + return RPCHelpMan{"gettxspendingprevout", + "Scans the mempool to find transactions spending any of the given outputs", + { + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).", + { + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, + }, + }, + }, + }, + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "the transaction id of the checked output"}, + {RPCResult::Type::NUM, "vout", "the vout value of the checked output"}, + {RPCResult::Type::STR_HEX, "spendingtxid", /*optional=*/true, "the transaction id of the mempool transaction spending this output (omitted if unspent)"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"") + + HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheckArgument(request.params[0], UniValue::VARR); + const UniValue& output_params = request.params[0]; + if (output_params.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing"); + } + + std::vector<COutPoint> prevouts; + prevouts.reserve(output_params.size()); + + for (unsigned int idx = 0; idx < output_params.size(); idx++) { + const UniValue& o = output_params[idx].get_obj(); + + RPCTypeCheckObj(o, + { + {"txid", UniValueType(UniValue::VSTR)}, + {"vout", UniValueType(UniValue::VNUM)}, + }, /*fAllowNull=*/false, /*fStrict=*/true); + + const uint256 txid(ParseHashO(o, "txid")); + const int nOutput{find_value(o, "vout").getInt<int>()}; + if (nOutput < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); + } + + prevouts.emplace_back(txid, nOutput); + } + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + UniValue result{UniValue::VARR}; + + for (const COutPoint& prevout : prevouts) { + UniValue o(UniValue::VOBJ); + o.pushKV("txid", prevout.hash.ToString()); + o.pushKV("vout", (uint64_t)prevout.n); + + const CTransaction* spendingTx = mempool.GetConflictTx(prevout); + if (spendingTx != nullptr) { + o.pushKV("spendingtxid", spendingTx->GetHash().ToString()); + } + + result.push_back(o); + } + + return result; + }, + }; +} + UniValue MempoolInfoToJSON(const CTxMemPool& pool) { // Make sure this call is atomic in the pool. @@ -677,6 +737,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &getmempoolancestors}, {"blockchain", &getmempooldescendants}, {"blockchain", &getmempoolentry}, + {"blockchain", &gettxspendingprevout}, {"blockchain", &getmempoolinfo}, {"blockchain", &getrawmempool}, {"blockchain", &savemempool}, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index b552528951..8fb6daf0cb 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -27,6 +27,7 @@ #include <script/script.h> #include <script/signingprovider.h> #include <shutdown.h> +#include <timedata.h> #include <txmempool.h> #include <univalue.h> #include <util/strencodings.h> @@ -109,7 +110,7 @@ static RPCHelpMan getnetworkhashps() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1, chainman.ActiveChain()); + return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].getInt<int>() : 120, !request.params[1].isNull() ? request.params[1].getInt<int>() : -1, chainman.ActiveChain()); }, }; } @@ -119,9 +120,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& block_hash.SetNull(); block.hashMerkleRoot = BlockMerkleRoot(block); - CChainParams chainparams(Params()); - - while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) { + while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainman.GetConsensus()) && !ShutdownRequested()) { ++block.nNonce; --max_tries; } @@ -133,7 +132,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); - if (!chainman.ProcessNewBlock(chainparams, shared_pblock, true, nullptr)) { + if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } @@ -145,7 +144,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me { UniValue blockHashes(UniValue::VARR); while (nGenerate > 0 && !ShutdownRequested()) { - std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler{chainman.ActiveChainstate(), mempool}.CreateNewBlock(coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; @@ -217,8 +216,8 @@ static RPCHelpMan generatetodescriptor() "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int num_blocks{request.params[0].get_int()}; - const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()}; + const int num_blocks{request.params[0].getInt<int>()}; + const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()}; CScript coinbase_script; std::string error; @@ -264,8 +263,8 @@ static RPCHelpMan generatetoaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int num_blocks{request.params[0].get_int()}; - const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()}; + const int num_blocks{request.params[0].getInt<int>()}; + const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()}; CTxDestination destination = DecodeDestination(request.params[1].get_str()); if (!IsValidDestination(destination)) { @@ -349,7 +348,6 @@ static RPCHelpMan generateblock() } } - CChainParams chainparams(Params()); CBlock block; ChainstateManager& chainman = EnsureChainman(node); @@ -357,7 +355,7 @@ static RPCHelpMan generateblock() LOCK(cs_main); CTxMemPool empty_mempool; - std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(chainman.ActiveChainstate(), empty_mempool, chainparams).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler{chainman.ActiveChainstate(), empty_mempool}.CreateNewBlock(coinbase_script)); if (!blocktemplate) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } @@ -374,7 +372,7 @@ static RPCHelpMan generateblock() LOCK(cs_main); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), false, false)) { + if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), GetAdjustedTime, false, false)) { throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString())); } } @@ -429,7 +427,7 @@ static RPCHelpMan getmininginfo() obj.pushKV("difficulty", (double)GetDifficulty(active_chain.Tip())); obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); - obj.pushKV("chain", Params().NetworkIDString()); + obj.pushKV("chain", chainman.GetParams().NetworkIDString()); obj.pushKV("warnings", GetWarnings(false).original); return obj; }, @@ -462,7 +460,7 @@ static RPCHelpMan prioritisetransaction() LOCK(cs_main); uint256 hash(ParseHashV(request.params[0], "txid")); - CAmount nAmount = request.params[2].get_int64(); + CAmount nAmount = request.params[2].getInt<int64_t>(); if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0."); @@ -643,7 +641,7 @@ static RPCHelpMan getblocktemplate() if (block.hashPrevBlock != pindexPrev->GetBlockHash()) return "inconclusive-not-best-prevblk"; BlockValidationState state; - TestBlockValidity(state, Params(), active_chainstate, block, pindexPrev, false, true); + TestBlockValidity(state, chainman.GetParams(), active_chainstate, block, pindexPrev, GetAdjustedTime, false, true); return BIP22ValidationResult(state); } @@ -657,7 +655,7 @@ static RPCHelpMan getblocktemplate() // NOTE: It is important that this NOT be read if versionbits is supported const UniValue& uvMaxVersion = find_value(oparam, "maxversion"); if (uvMaxVersion.isNum()) { - nMaxVersionPreVB = uvMaxVersion.get_int64(); + nMaxVersionPreVB = uvMaxVersion.getInt<int64_t>(); } } } @@ -665,7 +663,7 @@ static RPCHelpMan getblocktemplate() if (strMode != "template") throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); - if (!Params().IsTestChain()) { + if (!chainman.GetParams().IsTestChain()) { const CConnman& connman = EnsureConnman(node); if (connman.GetNodeCount(ConnectionDirection::Both) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); @@ -726,7 +724,7 @@ static RPCHelpMan getblocktemplate() // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners? } - const Consensus::Params& consensusParams = Params().GetConsensus(); + const Consensus::Params& consensusParams = chainman.GetParams().GetConsensus(); // GBT must be called with 'signet' set in the rules for signet chains if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) { @@ -755,7 +753,7 @@ static RPCHelpMan getblocktemplate() // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(active_chainstate, mempool, Params()).CreateNewBlock(scriptDummy); + pblocktemplate = BlockAssembler{active_chainstate, mempool}.CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -770,7 +768,7 @@ static RPCHelpMan getblocktemplate() pblock->nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration - const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT); + const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT); UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); @@ -836,7 +834,7 @@ static RPCHelpMan getblocktemplate() UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); - ThresholdState state = g_versionbitscache.State(pindexPrev, consensusParams, pos); + ThresholdState state = chainman.m_versionbitscache.State(pindexPrev, consensusParams, pos); switch (state) { case ThresholdState::DEFINED: case ThresholdState::FAILED: @@ -844,7 +842,7 @@ static RPCHelpMan getblocktemplate() break; case ThresholdState::LOCKED_IN: // Ensure bit is set in block version - pblock->nVersion |= g_versionbitscache.Mask(consensusParams, pos); + pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); [[fallthrough]]; case ThresholdState::STARTED: { @@ -853,7 +851,7 @@ static RPCHelpMan getblocktemplate() if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (!vbinfo.gbt_force) { // If the client doesn't support this, don't indicate it in the [default] version - pblock->nVersion &= ~g_versionbitscache.Mask(consensusParams, pos); + pblock->nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); } } break; @@ -930,10 +928,10 @@ class submitblock_StateCatcher final : public CValidationInterface { public: uint256 hash; - bool found; + bool found{false}; BlockValidationState state; - explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} + explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), state() {} protected: void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override { @@ -993,14 +991,14 @@ static RPCHelpMan submitblock() LOCK(cs_main); const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); if (pindex) { - UpdateUncommittedBlockStructures(block, pindex, Params().GetConsensus()); + chainman.UpdateUncommittedBlockStructures(block, pindex); } } bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; @@ -1042,7 +1040,7 @@ static RPCHelpMan submitheader() } BlockValidationState state; - chainman.ProcessNewBlockHeaders({h}, state, Params()); + chainman.ProcessNewBlockHeaders({h}, state); if (state.IsValid()) return NullUniValue; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 09dc8eb3eb..0a061f2451 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -412,7 +412,7 @@ static RPCHelpMan disconnectnode() success = connman.DisconnectNode(address_arg.get_str()); } else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) { /* handle disconnect-by-id */ - NodeId nodeid = (NodeId) id_arg.get_int64(); + NodeId nodeid = (NodeId) id_arg.getInt<int64_t>(); success = connman.DisconnectNode(nodeid); } else { throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided."); @@ -720,7 +720,7 @@ static RPCHelpMan setban() int64_t banTime = 0; //use standard bantime if not specified if (!request.params[2].isNull()) - banTime = request.params[2].get_int64(); + banTime = request.params[2].getInt<int64_t>(); bool absolute = false; if (request.params[3].isTrue()) @@ -879,7 +879,7 @@ static RPCHelpMan getnodeaddresses() NodeContext& node = EnsureAnyNodeContext(request.context); const CConnman& connman = EnsureConnman(node); - const int count{request.params[0].isNull() ? 1 : request.params[0].get_int()}; + const int count{request.params[0].isNull() ? 1 : request.params[0].getInt<int>()}; if (count < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range"); const std::optional<Network> network{request.params[1].isNull() ? std::nullopt : std::optional<Network>{ParseNetwork(request.params[1].get_str())}}; @@ -932,7 +932,7 @@ static RPCHelpMan addpeeraddress() } const std::string& addr_string{request.params[0].get_str()}; - const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())}; + const auto port{request.params[1].getInt<uint16_t>()}; const bool tried{request.params[2].isTrue()}; UniValue obj(UniValue::VOBJ); diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index 24697a606e..5475662b82 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -53,7 +53,7 @@ static RPCHelpMan setmocktime() LOCK(cs_main); RPCTypeCheck(request.params, {UniValue::VNUM}); - const int64_t time{request.params[0].get_int64()}; + const int64_t time{request.params[0].getInt<int64_t>()}; if (time < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); } @@ -108,7 +108,7 @@ static RPCHelpMan mockscheduler() // check params are valid values RPCTypeCheck(request.params, {UniValue::VNUM}); - int64_t delta_seconds = request.params[0].get_int64(); + int64_t delta_seconds = request.params[0].getInt<int64_t>(); if (delta_seconds <= 0 || delta_seconds > 3600) { throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); } diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 1cf952d0c8..f4bb76f50f 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -124,7 +124,7 @@ static RPCHelpMan createmultisig() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - int required = request.params[0].get_int(); + int required = request.params[0].getInt<int>(); // Get the public keys const UniValue& keys = request.params[1].get_array(); @@ -163,11 +163,11 @@ static RPCHelpMan createmultisig() result.pushKV("descriptor", descriptor->ToString()); UniValue warnings(UniValue::VARR); - if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { + if (descriptor->GetOutputType() != output_type) { // Only warns if the user has explicitly chosen an address type we cannot generate warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); } - if (warnings.size()) result.pushKV("warnings", warnings); + if (!warnings.empty()) result.pushKV("warnings", warnings); return result; }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index e8713fbd2e..b9b8c36bb3 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -217,7 +217,7 @@ static RPCHelpMan getrawtransaction() uint256 hash = ParseHashV(request.params[0], "parameter 1"); const CBlockIndex* blockindex = nullptr; - if (hash == Params().GenesisBlock().hashMerkleRoot) { + if (hash == chainman.GetParams().GenesisBlock().hashMerkleRoot) { // Special exception for the genesis block coinbase transaction throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved"); } @@ -225,7 +225,7 @@ static RPCHelpMan getrawtransaction() // Accept either a bool (true) or a num (>=1) to indicate verbose output. bool fVerbose = false; if (!request.params[1].isNull()) { - fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool(); + fVerbose = request.params[1].isNum() ? (request.params[1].getInt<int>() != 0) : request.params[1].get_bool(); } if (!request.params[2].isNull()) { @@ -245,7 +245,7 @@ static RPCHelpMan getrawtransaction() } uint256 hash_block; - const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, Params().GetConsensus(), hash_block); + const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, chainman.GetConsensus(), hash_block); if (!tx) { std::string errmsg; if (blockindex) { diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index e23fe34480..86b5b7e960 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -40,7 +40,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal CMutableTransaction rawTx; if (!locktime.isNull()) { - int64_t nLockTime = locktime.get_int64(); + int64_t nLockTime = locktime.getInt<int64_t>(); if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; @@ -55,7 +55,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal const UniValue& vout_v = find_value(o, "vout"); if (!vout_v.isNum()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); - int nOutput = vout_v.get_int(); + int nOutput = vout_v.getInt<int>(); if (nOutput < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); @@ -71,7 +71,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal // set the sequence number if passed in the parameters object const UniValue& sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { - int64_t seqNr64 = sequenceObj.get_int64(); + int64_t seqNr64 = sequenceObj.getInt<int64_t>(); if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } else { @@ -177,7 +177,7 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst uint256 txid = ParseHashO(prevOut, "txid"); - int nOut = find_value(prevOut, "vout").get_int(); + int nOut = find_value(prevOut, "vout").getInt<int>(); if (nOut < 0) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout cannot be negative"); } diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index d0e068de19..304c265b31 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -146,7 +146,7 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in) if (!rec.isObject()) { throw std::runtime_error("Batch member must be an object"); } - size_t id = rec["id"].get_int(); + size_t id = rec["id"].getInt<int>(); if (id >= num) { throw std::runtime_error("Batch member id is larger than batch size"); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 3817a4a45c..af00acdc9f 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -176,7 +176,7 @@ static RPCHelpMan stop() // this reply will get back to the client. StartShutdown(); if (jsonRequest.params[0].isNum()) { - UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()}); + UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()}); } return RESULT; }, diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index d16820baeb..dcf6c6bee1 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -87,7 +87,7 @@ static RPCHelpMan gettxoutproof() LOCK(cs_main); if (pblockindex == nullptr) { - const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock); + const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), chainman.GetConsensus(), hashBlock); if (!tx || hashBlock.IsNull()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); } @@ -98,7 +98,7 @@ static RPCHelpMan gettxoutproof() } CBlock block; - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { + if (!ReadBlockFromDisk(block, pblockindex, chainman.GetConsensus())) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index ef3e58494e..7517f64ea1 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -12,6 +12,7 @@ #include <util/check.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/system.h> #include <util/translation.h> #include <tuple> @@ -267,7 +268,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto class DescribeAddressVisitor { public: - explicit DescribeAddressVisitor() {} + explicit DescribeAddressVisitor() = default; UniValue operator()(const CNoDestination& dest) const { @@ -337,7 +338,7 @@ UniValue DescribeAddress(const CTxDestination& dest) unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target) { - const int target{value.get_int()}; + const int target{value.getInt<int>()}; const unsigned int unsigned_target{static_cast<unsigned int>(target)}; if (target < 1 || unsigned_target > max_target) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u and %u", 1, max_target)); @@ -581,7 +582,9 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const throw std::runtime_error(ToString()); } const UniValue ret = m_fun(*this, request); - CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [ret](const RPCResult& res) { return res.MatchesType(ret); })); + if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { + CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); })); + } return ret; } @@ -1023,11 +1026,11 @@ std::string RPCArg::ToString(const bool oneline) const static std::pair<int64_t, int64_t> ParseRange(const UniValue& value) { if (value.isNum()) { - return {0, value.get_int64()}; + return {0, value.getInt<int64_t>()}; } if (value.isArray() && value.size() == 2 && value[0].isNum() && value[1].isNum()) { - int64_t low = value[0].get_int64(); - int64_t high = value[1].get_int64(); + int64_t low = value[0].getInt<int64_t>(); + int64_t high = value[1].getInt<int64_t>(); if (low > high) throw JSONRPCError(RPC_INVALID_PARAMETER, "Range specified as [begin,end] must not have begin after end"); return {low, high}; } diff --git a/src/rpc/util.h b/src/rpc/util.h index e16fed75bc..e883dc008e 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_RPC_UTIL_H #define BITCOIN_RPC_UTIL_H -#include <node/coinstats.h> #include <node/transaction.h> #include <outputtype.h> #include <protocol.h> @@ -22,6 +21,14 @@ #include <variant> #include <vector> +static constexpr bool DEFAULT_RPC_DOC_CHECK{ +#ifdef RPC_DOC_CHECK + true +#else + false +#endif +}; + /** * String used to describe UNIX epoch time in documentation, factored out to a * constant for consistency. diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 3e7ee7d370..3df1d48b3c 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -4,17 +4,15 @@ #include <scheduler.h> -#include <random.h> +#include <sync.h> #include <util/syscall_sandbox.h> #include <util/time.h> -#include <assert.h> +#include <cassert> #include <functional> #include <utility> -CScheduler::CScheduler() -{ -} +CScheduler::CScheduler() = default; CScheduler::~CScheduler() { @@ -43,7 +41,7 @@ void CScheduler::serviceQueue() // the time of the first item on the queue: while (!shouldStop() && !taskQueue.empty()) { - std::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first; + std::chrono::steady_clock::time_point timeToWaitFor = taskQueue.begin()->first; if (newTaskScheduled.wait_until(lock, timeToWaitFor) == std::cv_status::timeout) { break; // Exit loop after timeout, it means we reached the time of the event } @@ -72,7 +70,7 @@ void CScheduler::serviceQueue() newTaskScheduled.notify_one(); } -void CScheduler::schedule(CScheduler::Function f, std::chrono::system_clock::time_point t) +void CScheduler::schedule(CScheduler::Function f, std::chrono::steady_clock::time_point t) { { LOCK(newTaskMutex); @@ -89,7 +87,7 @@ void CScheduler::MockForward(std::chrono::seconds delta_seconds) LOCK(newTaskMutex); // use temp_queue to maintain updated schedule - std::multimap<std::chrono::system_clock::time_point, Function> temp_queue; + std::multimap<std::chrono::steady_clock::time_point, Function> temp_queue; for (const auto& element : taskQueue) { temp_queue.emplace_hint(temp_queue.cend(), element.first - delta_seconds, element.second); @@ -114,8 +112,8 @@ void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds scheduleFromNow([this, f, delta] { Repeat(*this, f, delta); }, delta); } -size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const +size_t CScheduler::getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const { LOCK(newTaskMutex); size_t result = taskQueue.size(); @@ -143,7 +141,7 @@ void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() if (m_are_callbacks_running) return; if (m_callbacks_pending.empty()) return; } - m_scheduler.schedule([this] { this->ProcessQueue(); }, std::chrono::system_clock::now()); + m_scheduler.schedule([this] { this->ProcessQueue(); }, std::chrono::steady_clock::now()); } void SingleThreadedSchedulerClient::ProcessQueue() diff --git a/src/scheduler.h b/src/scheduler.h index eb350e4bc3..749e5442b0 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -46,12 +46,12 @@ public: typedef std::function<void()> Function; /** Call func at/after time t */ - void schedule(Function f, std::chrono::system_clock::time_point t); + void schedule(Function f, std::chrono::steady_clock::time_point t) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Call f once after the delta has passed */ - void scheduleFromNow(Function f, std::chrono::milliseconds delta) + void scheduleFromNow(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { - schedule(std::move(f), std::chrono::system_clock::now() + delta); + schedule(std::move(f), std::chrono::steady_clock::now() + delta); } /** @@ -60,29 +60,29 @@ public: * The timing is not exact: Every time f is finished, it is rescheduled to run again after delta. If you need more * accurate scheduling, don't use this method. */ - void scheduleEvery(Function f, std::chrono::milliseconds delta); + void scheduleEvery(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** * Mock the scheduler to fast forward in time. * Iterates through items on taskQueue and reschedules them * to be delta_seconds sooner. */ - void MockForward(std::chrono::seconds delta_seconds); + void MockForward(std::chrono::seconds delta_seconds) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** * Services the queue 'forever'. Should be run in a thread. */ - void serviceQueue(); + void serviceQueue() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Tell any threads running serviceQueue to stop as soon as the current task is done */ - void stop() + void stop() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { WITH_LOCK(newTaskMutex, stopRequested = true); newTaskScheduled.notify_all(); if (m_service_thread.joinable()) m_service_thread.join(); } /** Tell any threads running serviceQueue to stop when there is no work left to be done */ - void StopWhenDrained() + void StopWhenDrained() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { WITH_LOCK(newTaskMutex, stopWhenEmpty = true); newTaskScheduled.notify_all(); @@ -93,16 +93,17 @@ public: * Returns number of tasks waiting to be serviced, * and first and last task times */ - size_t getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const; + size_t getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const + EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Returns true if there are threads actively running in serviceQueue() */ - bool AreThreadsServicingQueue() const; + bool AreThreadsServicingQueue() const EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); private: mutable Mutex newTaskMutex; std::condition_variable newTaskScheduled; - std::multimap<std::chrono::system_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); + std::multimap<std::chrono::steady_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); int nThreadsServicingQueue GUARDED_BY(newTaskMutex){0}; bool stopRequested GUARDED_BY(newTaskMutex){false}; bool stopWhenEmpty GUARDED_BY(newTaskMutex){false}; @@ -128,8 +129,8 @@ private: std::list<std::function<void()>> m_callbacks_pending GUARDED_BY(m_callbacks_mutex); bool m_are_callbacks_running GUARDED_BY(m_callbacks_mutex) = false; - void MaybeScheduleProcessQueue(); - void ProcessQueue(); + void MaybeScheduleProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); + void ProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); public: explicit SingleThreadedSchedulerClient(CScheduler& scheduler LIFETIMEBOUND) : m_scheduler{scheduler} {} @@ -140,15 +141,15 @@ public: * Practically, this means that callbacks can behave as if they are executed * in order by a single thread. */ - void AddToProcessQueue(std::function<void()> func); + void AddToProcessQueue(std::function<void()> func) EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); /** * Processes all remaining queue members on the calling thread, blocking until queue is empty * Must be called after the CScheduler has no remaining processing threads! */ - void EmptyQueue(); + void EmptyQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); - size_t CallbacksPending(); + size_t CallbacksPending() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); }; #endif // BITCOIN_SCHEDULER_H diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index c4d13d7283..9f56301377 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1832,6 +1832,10 @@ uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script) uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash) { + assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE); + assert(control.size() <= TAPROOT_CONTROL_MAX_SIZE); + assert((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE == 0); + const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index 019f02f159..cb4d4cb783 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -17,69 +17,67 @@ Type SanitizeType(Type e) { int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst); if (num_types == 0) return ""_mst; // No valid type, don't care about the rest assert(num_types == 1); // K, V, B, W all conflict with each other - bool ok = // Work around a GCC 4.8 bug that breaks user-defined literals in macro calls. - (!(e << "z"_mst) || !(e << "o"_mst)) && // z conflicts with o - (!(e << "n"_mst) || !(e << "z"_mst)) && // n conflicts with z - (!(e << "n"_mst) || !(e << "W"_mst)) && // n conflicts with W - (!(e << "V"_mst) || !(e << "d"_mst)) && // V conflicts with d - (!(e << "K"_mst) || (e << "u"_mst)) && // K implies u - (!(e << "V"_mst) || !(e << "u"_mst)) && // V conflicts with u - (!(e << "e"_mst) || !(e << "f"_mst)) && // e conflicts with f - (!(e << "e"_mst) || (e << "d"_mst)) && // e implies d - (!(e << "V"_mst) || !(e << "e"_mst)) && // V conflicts with e - (!(e << "d"_mst) || !(e << "f"_mst)) && // d conflicts with f - (!(e << "V"_mst) || (e << "f"_mst)) && // V implies f - (!(e << "K"_mst) || (e << "s"_mst)) && // K implies s - (!(e << "z"_mst) || (e << "m"_mst)); // z implies m - assert(ok); + assert(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o + assert(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z + assert(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W + assert(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d + assert(!(e << "K"_mst) || (e << "u"_mst)); // K implies u + assert(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u + assert(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f + assert(!(e << "e"_mst) || (e << "d"_mst)); // e implies d + assert(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e + assert(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f + assert(!(e << "V"_mst) || (e << "f"_mst)); // V implies f + assert(!(e << "K"_mst) || (e << "s"_mst)); // K implies s + assert(!(e << "z"_mst) || (e << "m"_mst)); // z implies m return e; } -Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) { +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) { // Sanity check on data - if (nodetype == Fragment::SHA256 || nodetype == Fragment::HASH256) { + if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) { assert(data_size == 32); - } else if (nodetype == Fragment::RIPEMD160 || nodetype == Fragment::HASH160) { + } else if (fragment == Fragment::RIPEMD160 || fragment == Fragment::HASH160) { assert(data_size == 20); } else { assert(data_size == 0); } // Sanity check on k - if (nodetype == Fragment::OLDER || nodetype == Fragment::AFTER) { + if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) { assert(k >= 1 && k < 0x80000000UL); - } else if (nodetype == Fragment::MULTI) { + } else if (fragment == Fragment::MULTI) { assert(k >= 1 && k <= n_keys); - } else if (nodetype == Fragment::THRESH) { + } else if (fragment == Fragment::THRESH) { assert(k >= 1 && k <= n_subs); } else { assert(k == 0); } // Sanity check on subs - if (nodetype == Fragment::AND_V || nodetype == Fragment::AND_B || nodetype == Fragment::OR_B || - nodetype == Fragment::OR_C || nodetype == Fragment::OR_I || nodetype == Fragment::OR_D) { + if (fragment == Fragment::AND_V || fragment == Fragment::AND_B || fragment == Fragment::OR_B || + fragment == Fragment::OR_C || fragment == Fragment::OR_I || fragment == Fragment::OR_D) { assert(n_subs == 2); - } else if (nodetype == Fragment::ANDOR) { + } else if (fragment == Fragment::ANDOR) { assert(n_subs == 3); - } else if (nodetype == Fragment::WRAP_A || nodetype == Fragment::WRAP_S || nodetype == Fragment::WRAP_C || - nodetype == Fragment::WRAP_D || nodetype == Fragment::WRAP_V || nodetype == Fragment::WRAP_J || - nodetype == Fragment::WRAP_N) { + } else if (fragment == Fragment::WRAP_A || fragment == Fragment::WRAP_S || fragment == Fragment::WRAP_C || + fragment == Fragment::WRAP_D || fragment == Fragment::WRAP_V || fragment == Fragment::WRAP_J || + fragment == Fragment::WRAP_N) { assert(n_subs == 1); - } else if (nodetype != Fragment::THRESH) { + } else if (fragment != Fragment::THRESH) { assert(n_subs == 0); } // Sanity check on keys - if (nodetype == Fragment::PK_K || nodetype == Fragment::PK_H) { + if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) { assert(n_keys == 1); - } else if (nodetype == Fragment::MULTI) { + } else if (fragment == Fragment::MULTI) { assert(n_keys >= 1 && n_keys <= 20); } else { assert(n_keys == 0); } - // Below is the per-nodetype logic for computing the expression types. + // Below is the per-fragment logic for computing the expression types. // It heavily relies on Type's << operator (where "X << a_mst" means // "X has all properties listed in a"). - switch (nodetype) { + switch (fragment) { case Fragment::PK_K: return "Konudemsxk"_mst; case Fragment::PK_H: return "Knudemsxk"_mst; case Fragment::OLDER: return @@ -247,11 +245,10 @@ Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Ty } } assert(false); - return ""_mst; } -size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) { - switch (nodetype) { +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) { + switch (fragment) { case Fragment::JUST_1: case Fragment::JUST_0: return 1; case Fragment::PK_K: return 34; @@ -262,7 +259,7 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_ case Fragment::SHA256: return 4 + 2 + 33; case Fragment::HASH160: case Fragment::RIPEMD160: return 4 + 2 + 21; - case Fragment::MULTI: return 3 + (n_keys > 16) + (k > 16) + 34 * n_keys; + case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys; case Fragment::AND_V: return subsize; case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst); case Fragment::WRAP_S: @@ -280,19 +277,17 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_ case Fragment::THRESH: return subsize + n_subs + BuildScript(k).size(); } assert(false); - return 0; } -bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out) +std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script) { - out.clear(); + std::vector<Opcode> out; CScript::const_iterator it = script.begin(), itend = script.end(); while (it != itend) { std::vector<unsigned char> push_data; opcodetype opcode; if (!script.GetOp(it, opcode, push_data)) { - out.clear(); - return false; + return {}; } else if (opcode >= OP_1 && opcode <= OP_16) { // Deal with OP_n (GetOp does not turn them into pushes). push_data.assign(1, CScript::DecodeOP_N(opcode)); @@ -309,30 +304,28 @@ bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, st out.emplace_back(OP_EQUAL, std::vector<unsigned char>()); opcode = OP_VERIFY; } else if (IsPushdataOp(opcode)) { - if (!CheckMinimalPush(push_data, opcode)) return false; + if (!CheckMinimalPush(push_data, opcode)) return {}; } else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) { // Rule out non minimal VERIFY sequences - return false; + return {}; } out.emplace_back(opcode, std::move(push_data)); } std::reverse(out.begin(), out.end()); - return true; + return out; } -bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k) { +std::optional<int64_t> ParseScriptNumber(const Opcode& in) { if (in.first == OP_0) { - k = 0; - return true; + return 0; } if (!in.second.empty()) { - if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return false; + if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return {}; try { - k = CScriptNum(in.second, true).GetInt64(); - return true; + return CScriptNum(in.second, true).GetInt64(); } catch(const scriptnum_error&) {} } - return false; + return {}; } int FindNextChar(Span<const char> sp, const char m) diff --git a/src/script/miniscript.h b/src/script/miniscript.h index 5c1cc316dc..2c239c2678 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -6,6 +6,7 @@ #define BITCOIN_SCRIPT_MINISCRIPT_H #include <algorithm> +#include <functional> #include <numeric> #include <memory> #include <optional> @@ -40,7 +41,7 @@ namespace miniscript { * - For example: older(n) = <n> OP_CHECKSEQUENCEVERIFY. * - "V" Verify: * - Takes its inputs from the top of the stack. - * - When satisfactied, pushes nothing. + * - When satisfied, pushes nothing. * - Cannot be dissatisfied. * - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY @@ -179,6 +180,8 @@ inline constexpr Type operator"" _mst(const char* c, size_t l) { return typ; } +using Opcode = std::pair<opcodetype, std::vector<unsigned char>>; + template<typename Key> struct Node; template<typename Key> using NodeRef = std::shared_ptr<const Node<Key>>; @@ -224,10 +227,10 @@ enum class Fragment { namespace internal { //! Helper function for Node::CalcType. -Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys); +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys); //! Helper function for Node::CalcScriptLen. -size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys); +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys); //! A helper sanitizer/checker for the output of CalcType. Type SanitizeType(Type x); @@ -279,7 +282,7 @@ struct StackSize { template<typename Key> struct Node { //! What node type this node is. - const Fragment nodetype; + const Fragment fragment; //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M)) const uint32_t k = 0; //! The keys used by this expression (only for PK_K/PK_H/MULTI) @@ -298,6 +301,8 @@ private: const Type typ; //! Cached script length (computed by CalcScriptLen). const size_t scriptlen; + //! Whether a public key appears more than once in this node. + const bool duplicate_key; //! Compute the length of the script for this miniscript (including children). size_t CalcScriptLen() const { @@ -306,7 +311,7 @@ private: subsize += sub->ScriptSize(); } Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst; - return internal::ComputeScriptLen(nodetype, sub0type, subsize, k, subs.size(), keys.size()); + return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size()); } /* Apply a recursive algorithm to a Miniscript tree, without actual recursive calls. @@ -329,6 +334,8 @@ private: * computes the result of the node. If std::nullopt is returned by upfn, * TreeEvalMaybe() immediately returns std::nullopt. * The return value of TreeEvalMaybe is the result of the root node. + * + * Result type cannot be bool due to the std::vector<bool> specialization. */ template<typename Result, typename State, typename DownFn, typename UpFn> std::optional<Result> TreeEvalMaybe(State root_state, DownFn downfn, UpFn upfn) const @@ -393,6 +400,20 @@ private: return std::move(results[0]); } + /** Like TreeEvalMaybe, but without downfn or State type. + * upfn takes (const Node&, Span<Result>) and returns std::optional<Result>. */ + template<typename Result, typename UpFn> + std::optional<Result> TreeEvalMaybe(UpFn upfn) const + { + struct DummyState {}; + return TreeEvalMaybe<Result>(DummyState{}, + [](DummyState, const Node&, size_t) { return DummyState{}; }, + [&upfn](DummyState, const Node& node, Span<Result> subs) { + return upfn(node, subs); + } + ); + } + /** Like TreeEvalMaybe, but always produces a result. upfn must return Result. */ template<typename Result, typename State, typename DownFn, typename UpFn> Result TreeEval(State root_state, DownFn&& downfn, UpFn upfn) const @@ -408,13 +429,33 @@ private: )); } + /** Compare two miniscript subtrees, using a non-recursive algorithm. */ + friend int Compare(const Node<Key>& node1, const Node<Key>& node2) + { + std::vector<std::pair<const Node<Key>&, const Node<Key>&>> queue; + queue.emplace_back(node1, node2); + while (!queue.empty()) { + const auto& [a, b] = queue.back(); + queue.pop_back(); + if (std::tie(a.fragment, a.k, a.keys, a.data) < std::tie(b.fragment, b.k, b.keys, b.data)) return -1; + if (std::tie(b.fragment, b.k, b.keys, b.data) < std::tie(a.fragment, a.k, a.keys, a.data)) return 1; + if (a.subs.size() < b.subs.size()) return -1; + if (b.subs.size() < a.subs.size()) return 1; + size_t n = a.subs.size(); + for (size_t i = 0; i < n; ++i) { + queue.emplace_back(*a.subs[n - 1 - i], *b.subs[n - 1 - i]); + } + } + return 0; + } + //! Compute the type for this miniscript. Type CalcType() const { using namespace internal; // THRESH has a variable number of subexpressions std::vector<Type> sub_types; - if (nodetype == Fragment::THRESH) { + if (fragment == Fragment::THRESH) { for (const auto& sub : subs) sub_types.push_back(sub->GetType()); } // All other nodes than THRESH can be computed just from the types of the 0-3 subexpressions. @@ -422,7 +463,7 @@ private: Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst; Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst; - return SanitizeType(ComputeType(nodetype, x, y, z, sub_types, k, data.size(), subs.size(), keys.size())); + return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size())); } public: @@ -434,17 +475,17 @@ public: // by an OP_VERIFY (which may need to be combined with the last script opcode). auto downfn = [](bool verify, const Node& node, size_t index) { // For WRAP_V, the subexpression is certainly followed by OP_VERIFY. - if (node.nodetype == Fragment::WRAP_V) return true; + if (node.fragment == Fragment::WRAP_V) return true; // The subexpression of WRAP_S, and the last subexpression of AND_V // inherit the followed-by-OP_VERIFY property from the parent. - if (node.nodetype == Fragment::WRAP_S || - (node.nodetype == Fragment::AND_V && index == 1)) return verify; + if (node.fragment == Fragment::WRAP_S || + (node.fragment == Fragment::AND_V && index == 1)) return verify; return false; }; // The upward function computes for a node, given its followed-by-OP_VERIFY status // and the CScripts of its child nodes, the CScript of the node. auto upfn = [&ctx](bool verify, const Node& node, Span<CScript> subs) -> CScript { - switch (node.nodetype) { + switch (node.fragment) { case Fragment::PK_K: return BuildScript(ctx.ToPKBytes(node.keys[0])); case Fragment::PK_H: return BuildScript(OP_DUP, OP_HASH160, ctx.ToPKHBytes(node.keys[0]), OP_EQUALVERIFY); case Fragment::OLDER: return BuildScript(node.k, OP_CHECKSEQUENCEVERIFY); @@ -491,45 +532,44 @@ public: } } assert(false); - return {}; }; return TreeEval<CScript>(false, downfn, upfn); } template<typename CTx> - bool ToString(const CTx& ctx, std::string& ret) const { + std::optional<std::string> ToString(const CTx& ctx) const { // To construct the std::string representation for a Miniscript object, we use // the TreeEvalMaybe algorithm. The State is a boolean: whether the parent node is a // wrapper. If so, non-wrapper expressions must be prefixed with a ":". auto downfn = [](bool, const Node& node, size_t) { - return (node.nodetype == Fragment::WRAP_A || node.nodetype == Fragment::WRAP_S || - node.nodetype == Fragment::WRAP_D || node.nodetype == Fragment::WRAP_V || - node.nodetype == Fragment::WRAP_J || node.nodetype == Fragment::WRAP_N || - node.nodetype == Fragment::WRAP_C || - (node.nodetype == Fragment::AND_V && node.subs[1]->nodetype == Fragment::JUST_1) || - (node.nodetype == Fragment::OR_I && node.subs[0]->nodetype == Fragment::JUST_0) || - (node.nodetype == Fragment::OR_I && node.subs[1]->nodetype == Fragment::JUST_0)); + return (node.fragment == Fragment::WRAP_A || node.fragment == Fragment::WRAP_S || + node.fragment == Fragment::WRAP_D || node.fragment == Fragment::WRAP_V || + node.fragment == Fragment::WRAP_J || node.fragment == Fragment::WRAP_N || + node.fragment == Fragment::WRAP_C || + (node.fragment == Fragment::AND_V && node.subs[1]->fragment == Fragment::JUST_1) || + (node.fragment == Fragment::OR_I && node.subs[0]->fragment == Fragment::JUST_0) || + (node.fragment == Fragment::OR_I && node.subs[1]->fragment == Fragment::JUST_0)); }; // The upward function computes for a node, given whether its parent is a wrapper, // and the string representations of its child nodes, the string representation of the node. auto upfn = [&ctx](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> { std::string ret = wrapped ? ":" : ""; - switch (node.nodetype) { + switch (node.fragment) { case Fragment::WRAP_A: return "a" + std::move(subs[0]); case Fragment::WRAP_S: return "s" + std::move(subs[0]); case Fragment::WRAP_C: - if (node.subs[0]->nodetype == Fragment::PK_K) { + if (node.subs[0]->fragment == Fragment::PK_K) { // pk(K) is syntactic sugar for c:pk_k(K) - std::string key_str; - if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {}; - return std::move(ret) + "pk(" + std::move(key_str) + ")"; + auto key_str = ctx.ToString(node.subs[0]->keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk(" + std::move(*key_str) + ")"; } - if (node.subs[0]->nodetype == Fragment::PK_H) { + if (node.subs[0]->fragment == Fragment::PK_H) { // pkh(K) is syntactic sugar for c:pk_h(K) - std::string key_str; - if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {}; - return std::move(ret) + "pkh(" + std::move(key_str) + ")"; + auto key_str = ctx.ToString(node.subs[0]->keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pkh(" + std::move(*key_str) + ")"; } return "c" + std::move(subs[0]); case Fragment::WRAP_D: return "d" + std::move(subs[0]); @@ -538,24 +578,24 @@ public: case Fragment::WRAP_N: return "n" + std::move(subs[0]); case Fragment::AND_V: // t:X is syntactic sugar for and_v(X,1). - if (node.subs[1]->nodetype == Fragment::JUST_1) return "t" + std::move(subs[0]); + if (node.subs[1]->fragment == Fragment::JUST_1) return "t" + std::move(subs[0]); break; case Fragment::OR_I: - if (node.subs[0]->nodetype == Fragment::JUST_0) return "l" + std::move(subs[1]); - if (node.subs[1]->nodetype == Fragment::JUST_0) return "u" + std::move(subs[0]); + if (node.subs[0]->fragment == Fragment::JUST_0) return "l" + std::move(subs[1]); + if (node.subs[1]->fragment == Fragment::JUST_0) return "u" + std::move(subs[0]); break; default: break; } - switch (node.nodetype) { + switch (node.fragment) { case Fragment::PK_K: { - std::string key_str; - if (!ctx.ToString(node.keys[0], key_str)) return {}; - return std::move(ret) + "pk_k(" + std::move(key_str) + ")"; + auto key_str = ctx.ToString(node.keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk_k(" + std::move(*key_str) + ")"; } case Fragment::PK_H: { - std::string key_str; - if (!ctx.ToString(node.keys[0], key_str)) return {}; - return std::move(ret) + "pk_h(" + std::move(key_str) + ")"; + auto key_str = ctx.ToString(node.keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk_h(" + std::move(*key_str) + ")"; } case Fragment::AFTER: return std::move(ret) + "after(" + ::ToString(node.k) + ")"; case Fragment::OLDER: return std::move(ret) + "older(" + ::ToString(node.k) + ")"; @@ -573,14 +613,14 @@ public: case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; case Fragment::ANDOR: // and_n(X,Y) is syntactic sugar for andor(X,Y,0). - if (node.subs[2]->nodetype == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + if (node.subs[2]->fragment == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")"; case Fragment::MULTI: { auto str = std::move(ret) + "multi(" + ::ToString(node.k); for (const auto& key : node.keys) { - std::string key_str; - if (!ctx.ToString(key, key_str)) return {}; - str += "," + std::move(key_str); + auto key_str = ctx.ToString(key); + if (!key_str) return {}; + str += "," + std::move(*key_str); } return std::move(str) + ")"; } @@ -591,18 +631,16 @@ public: } return std::move(str) + ")"; } - default: assert(false); + default: break; } - return ""; // Should never be reached. + assert(false); }; - auto res = TreeEvalMaybe<std::string>(false, downfn, upfn); - if (res.has_value()) ret = std::move(*res); - return res.has_value(); + return TreeEvalMaybe<std::string>(false, downfn, upfn); } internal::Ops CalcOps() const { - switch (nodetype) { + switch (fragment) { case Fragment::JUST_1: return {0, 0, {}}; case Fragment::JUST_0: return {0, {}, 0}; case Fragment::PK_K: return {0, 0, 0}; @@ -672,11 +710,10 @@ public: } } assert(false); - return {0, {}, {}}; } internal::StackSize CalcStackSize() const { - switch (nodetype) { + switch (fragment) { case Fragment::JUST_0: return {{}, 0}; case Fragment::JUST_1: case Fragment::OLDER: @@ -723,7 +760,42 @@ public: } } assert(false); - return {{}, {}}; + } + + /** Check whether any key is repeated. + * This uses a custom key comparator provided by the context in order to still detect duplicates + * for more complicated types. + */ + template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const { + // We cannot use a lambda here, as lambdas are non assignable, and the set operations + // below require moving the comparators around. + struct Comp { + const Ctx* ctx_ptr; + Comp(const Ctx& ctx) : ctx_ptr(&ctx) {} + bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); } + }; + using set = std::set<Key, Comp>; + + auto upfn = [this, &ctx](const Node& node, Span<set> subs) -> std::optional<set> { + if (&node != this && node.duplicate_key) return {}; + + size_t keys_count = node.keys.size(); + set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; + if (key_set.size() != keys_count) return {}; + + for (auto& sub: subs) { + keys_count += sub.size(); + // Small optimization: std::set::merge is linear in the size of the second arg but + // logarithmic in the size of the first. + if (key_set.size() < sub.size()) std::swap(key_set, sub); + key_set.merge(sub); + if (key_set.size() != keys_count) return {}; + } + + return key_set; + }; + + return !TreeEvalMaybe<set>(upfn); } public: @@ -758,35 +830,31 @@ public: //! Check whether this script always needs a signature. bool NeedsSignature() const { return GetType() << "s"_mst; } - //! Do all sanity checks. - bool IsSane() const { return IsValid() && GetType() << "mk"_mst && CheckOpsLimit() && CheckStackSize(); } + //! Check whether there is no satisfaction path that contains both timelocks and heightlocks + bool CheckTimeLocksMix() const { return GetType() << "k"_mst; } + + //! Check whether there is no duplicate key across this fragment and all its sub-fragments. + bool CheckDuplicateKey() const { return !duplicate_key; } + + //! Whether successful non-malleable satisfactions are guaranteed to be valid. + bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); } + + //! Whether the apparent policy of this node matches its script semantics. Doesn't guarantee it is a safe script on its own. + bool IsSaneSubexpression() const { return ValidSatisfactions() && IsNonMalleable() && CheckTimeLocksMix() && CheckDuplicateKey(); } //! Check whether this node is safe as a script on its own. - bool IsSaneTopLevel() const { return IsValidTopLevel() && IsSane() && NeedsSignature(); } + bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); } //! Equality testing. - bool operator==(const Node<Key>& arg) const - { - if (nodetype != arg.nodetype) return false; - if (k != arg.k) return false; - if (data != arg.data) return false; - if (keys != arg.keys) return false; - if (subs.size() != arg.subs.size()) return false; - for (size_t i = 0; i < subs.size(); ++i) { - if (!(*subs[i] == *arg.subs[i])) return false; - } - assert(scriptlen == arg.scriptlen); - assert(typ == arg.typ); - return true; - } + bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; } // Constructors with various argument combinations. - Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(Fragment nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(Fragment nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} }; namespace internal { @@ -847,15 +915,15 @@ enum class ParseContext { int FindNextChar(Span<const char> in, const char m); -/** Parse a key string ending with a ')' or ','. */ +/** Parse a key string ending at the end of the fragment's text representation. */ template<typename Key, typename Ctx> std::optional<std::pair<Key, int>> ParseKeyEnd(Span<const char> in, const Ctx& ctx) { - Key key; int key_size = FindNextChar(in, ')'); if (key_size < 1) return {}; - if (!ctx.FromString(in.begin(), in.begin() + key_size, key)) return {}; - return {{std::move(key), key_size}}; + auto key = ctx.FromString(in.begin(), in.begin() + key_size); + if (!key) return {}; + return {{std::move(*key), key_size}}; } /** Parse a hex string ending at the end of the fragment's text representation. */ @@ -873,15 +941,15 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co } /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */ -template<typename Key> -void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) +template<typename Key, typename Ctx> +void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) { NodeRef<Key> child = std::move(constructed.back()); constructed.pop_back(); if (reverse) { - constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(child), std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back()))); } else { - constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(constructed.back()), std::move(child))); + constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child))); } } @@ -934,7 +1002,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAP_T, -1, -1); } else if (in[j] == 'l') { // The l: wrapper is equivalent to or_i(0,X) - constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); to_parse.emplace_back(ParseContext::OR_I, -1, -1); } else { return {}; @@ -946,56 +1014,56 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) } case ParseContext::EXPR: { if (Const("0", in)) { - constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); } else if (Const("1", in)) { - constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); } else if (Const("pk(", in)) { auto res = ParseKeyEnd<Key, Ctx>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))))); in = in.subspan(key_size + 1); } else if (Const("pkh(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))))); in = in.subspan(key_size + 1); } else if (Const("pk_k(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))); in = in.subspan(key_size + 1); } else if (Const("pk_h(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))); in = in.subspan(key_size + 1); } else if (Const("sha256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, std::move(hash))); in = in.subspan(hash_size + 1); } else if (Const("ripemd160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, std::move(hash))); in = in.subspan(hash_size + 1); } else if (Const("hash256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, std::move(hash))); in = in.subspan(hash_size + 1); } else if (Const("hash160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, std::move(hash))); in = in.subspan(hash_size + 1); } else if (Const("after(", in)) { int arg_size = FindNextChar(in, ')'); @@ -1003,7 +1071,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, num)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num)); in = in.subspan(arg_size + 1); } else if (Const("older(", in)) { int arg_size = FindNextChar(in, ')'); @@ -1011,7 +1079,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, num)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num)); in = in.subspan(arg_size + 1); } else if (Const("multi(", in)) { // Get threshold @@ -1022,17 +1090,17 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // Get keys std::vector<Key> keys; while (next_comma != -1) { - Key key; next_comma = FindNextChar(in, ','); int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma; if (key_length < 1) return {}; - if (!ctx.FromString(in.begin(), in.begin() + key_length, key)) return {}; - keys.push_back(std::move(key)); + auto key = ctx.FromString(in.begin(), in.begin() + key_length); + if (!key) return {}; + keys.push_back(std::move(*key)); in = in.subspan(key_length + 1); } if (keys.size() < 1 || keys.size() > 20) return {}; if (k < 1 || k > (int64_t)keys.size()) return {}; - constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k)); } else if (Const("thresh(", in)) { int next_comma = FindNextChar(in, ','); if (next_comma < 1) return {}; @@ -1076,69 +1144,69 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) break; } case ParseContext::ALT: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case ParseContext::SWAP: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case ParseContext::CHECK: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case ParseContext::DUP_IF: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case ParseContext::NON_ZERO: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case ParseContext::ZERO_NOTEQUAL: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case ParseContext::VERIFY: { - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case ParseContext::WRAP_U: { - constructed.back() = MakeNodeRef<Key>(Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); break; } case ParseContext::WRAP_T: { - constructed.back() = MakeNodeRef<Key>(Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_1))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1))); break; } case ParseContext::AND_B: { - BuildBack(Fragment::AND_B, constructed); + BuildBack(ctx, Fragment::AND_B, constructed); break; } case ParseContext::AND_N: { auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); break; } case ParseContext::AND_V: { - BuildBack(Fragment::AND_V, constructed); + BuildBack(ctx, Fragment::AND_V, constructed); break; } case ParseContext::OR_B: { - BuildBack(Fragment::OR_B, constructed); + BuildBack(ctx, Fragment::OR_B, constructed); break; } case ParseContext::OR_C: { - BuildBack(Fragment::OR_C, constructed); + BuildBack(ctx, Fragment::OR_C, constructed); break; } case ParseContext::OR_D: { - BuildBack(Fragment::OR_D, constructed); + BuildBack(ctx, Fragment::OR_D, constructed); break; } case ParseContext::OR_I: { - BuildBack(Fragment::OR_I, constructed); + BuildBack(ctx, Fragment::OR_I, constructed); break; } case ParseContext::ANDOR: { @@ -1146,7 +1214,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); break; } case ParseContext::THRESH: { @@ -1165,7 +1233,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); } std::reverse(subs.begin(), subs.end()); - constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); } else { return {}; } @@ -1200,10 +1268,10 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) * and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL * respectively, plus OP_VERIFY. */ -bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out); +std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script); /** Determine whether the passed pair (created by DecomposeScript) is pushing a number. */ -bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k); +std::optional<int64_t> ParseScriptNumber(const Opcode& in); enum class DecodeContext { /** A single expression of type B, K, or V. Specifically, this can't be an @@ -1300,58 +1368,59 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) // Constants if (in[0].first == OP_1) { ++in; - constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); break; } if (in[0].first == OP_0) { ++in; - constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); break; } // Public keys if (in[0].second.size() == 33) { - Key key; - if (!ctx.FromPKBytes(in[0].second.begin(), in[0].second.end(), key)) return {}; + auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end()); + if (!key) return {}; ++in; - constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(*key)))); break; } if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) { - Key key; - if (!ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end(), key)) return {}; + auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end()); + if (!key) return {}; in += 5; - constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(*key)))); break; } // Time locks - if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && ParseScriptNumber(in[1], k)) { + std::optional<int64_t> num; + if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; - if (k < 1 || k > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, k)); + if (*num < 1 || *num > 0x7FFFFFFFL) return {}; + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, *num)); break; } - if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && ParseScriptNumber(in[1], k)) { + if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; - if (k < 1 || k > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, k)); + if (num < 1 || num > 0x7FFFFFFFL) return {}; + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, *num)); break; } // Hashes - if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && ParseScriptNumber(in[5], k) && k == 32 && in[6].first == OP_SIZE) { + if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) { if (in[2].first == OP_SHA256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, in[1].second)); in += 7; break; } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, in[1].second)); in += 7; break; } @@ -1359,20 +1428,20 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) // Multi if (last - in >= 3 && in[0].first == OP_CHECKMULTISIG) { std::vector<Key> keys; - if (!ParseScriptNumber(in[1], n)) return {}; - if (last - in < 3 + n) return {}; - if (n < 1 || n > 20) return {}; - for (int i = 0; i < n; ++i) { - Key key; + const auto n = ParseScriptNumber(in[1]); + if (!n || last - in < 3 + *n) return {}; + if (*n < 1 || *n > 20) return {}; + for (int i = 0; i < *n; ++i) { if (in[2 + i].second.size() != 33) return {}; - if (!ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end(), key)) return {}; - keys.push_back(std::move(key)); + auto key = ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end()); + if (!key) return {}; + keys.push_back(std::move(*key)); } - if (!ParseScriptNumber(in[2 + n], k)) return {}; - if (k < 1 || k > n) return {}; - in += 3 + n; + const auto k = ParseScriptNumber(in[2 + *n]); + if (!k || *k < 1 || *k > *n) return {}; + in += 3 + *n; std::reverse(keys.begin(), keys.end()); - constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), *k)); break; } /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather @@ -1400,10 +1469,10 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) break; } // Thresh - if (last - in >= 3 && in[0].first == OP_EQUAL && ParseScriptNumber(in[1], k)) { - if (k < 1) return {}; + if (last - in >= 3 && in[0].first == OP_EQUAL && (num = ParseScriptNumber(in[1]))) { + if (*num < 1) return {}; in += 2; - to_parse.emplace_back(DecodeContext::THRESH_W, 0, k); + to_parse.emplace_back(DecodeContext::THRESH_W, 0, *num); break; } // OP_ENDIF can be WRAP_J, WRAP_D, ANDOR, OR_C, OR_D, or OR_I @@ -1467,63 +1536,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) case DecodeContext::SWAP: { if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case DecodeContext::ALT: { if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case DecodeContext::CHECK: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case DecodeContext::DUP_IF: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case DecodeContext::VERIFY: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case DecodeContext::NON_ZERO: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case DecodeContext::ZERO_NOTEQUAL: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case DecodeContext::AND_V: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::AND_V, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true); break; } case DecodeContext::AND_B: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::AND_B, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_B: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_B, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_C: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_C, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true); break; } case DecodeContext::OR_D: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_D, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true); break; } case DecodeContext::ANDOR: { @@ -1533,7 +1602,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) NodeRef<Key> right = std::move(constructed.back()); constructed.pop_back(); NodeRef<Key> mid = std::move(constructed.back()); - constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); break; } case DecodeContext::THRESH_W: { @@ -1557,7 +1626,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) constructed.pop_back(); subs.push_back(std::move(sub)); } - constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); break; } case DecodeContext::ENDIF: { @@ -1607,7 +1676,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (in >= last) return {}; if (in[0].first == OP_IF) { ++in; - BuildBack(Fragment::OR_I, constructed, /*reverse=*/true); + BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true); } else if (in[0].first == OP_NOTIF) { ++in; to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); @@ -1638,12 +1707,12 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& template<typename Ctx> inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { using namespace internal; - std::vector<std::pair<opcodetype, std::vector<unsigned char>>> decomposed; - if (!DecomposeScript(script, decomposed)) return {}; - auto it = decomposed.begin(); - auto ret = DecodeScript<typename Ctx::Key>(it, decomposed.end(), ctx); + auto decomposed = DecomposeScript(script); + if (!decomposed) return {}; + auto it = decomposed->begin(); + auto ret = DecodeScript<typename Ctx::Key>(it, decomposed->end(), ctx); if (!ret) return {}; - if (it != decomposed.end()) return {}; + if (it != decomposed->end()) return {}; return ret; } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 7328e8d1ad..2d569d674a 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -563,7 +563,7 @@ namespace { class DummySignatureChecker final : public BaseSignatureChecker { public: - DummySignatureChecker() {} + DummySignatureChecker() = default; bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return true; } }; diff --git a/src/script/standard.h b/src/script/standard.h index f0b143c52b..6a15ba4e3d 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_STANDARD_H #define BITCOIN_SCRIPT_STANDARD_H +#include <attributes.h> #include <pubkey.h> #include <script/interpreter.h> #include <uint256.h> diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index ea1a27c6f6..6907749c6d 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -282,9 +282,8 @@ LockedPool::LockedPool(std::unique_ptr<LockedPageAllocator> allocator_in, Lockin { } -LockedPool::~LockedPool() -{ -} +LockedPool::~LockedPool() = default; + void* LockedPool::alloc(size_t size) { std::lock_guard<std::mutex> lock(mutex); diff --git a/src/sync.h b/src/sync.h index c69b58741b..a175926113 100644 --- a/src/sync.h +++ b/src/sync.h @@ -83,8 +83,6 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi inline void DeleteLock(void* cs) {} inline bool LockStackEmpty() { return true; } #endif -#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) -#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs) /** * Template mixin that adds -Wthread-safety locking annotations and lock order @@ -129,7 +127,13 @@ public: using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; /** Wrapped mutex: supports waiting but not recursive locking */ -typedef AnnotatedMixin<std::mutex> Mutex; +using Mutex = AnnotatedMixin<std::mutex>; + +#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) + +inline void AssertLockNotHeldInline(const char* name, const char* file, int line, Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) { AssertLockNotHeldInternal(name, file, line, cs); } +inline void AssertLockNotHeldInline(const char* name, const char* file, int line, RecursiveMutex* 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> diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 0fa6b7784f..64cc924239 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -120,8 +120,9 @@ const std::vector<std::string> TEST5 = { "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL" }; -void RunTest(const TestVector &test) { - std::vector<unsigned char> seed = ParseHex(test.strHexMaster); +void RunTest(const TestVector& test) +{ + std::vector<std::byte> seed{ParseHex<std::byte>(test.strHexMaster)}; CExtKey key; CExtPubKey pubkey; key.SetSeed(seed); diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 9dfbd7ba7c..875241094d 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) // Do a simple ShortTxIDs RT { - CBlockHeaderAndShortTxIDs shortIDs(block, true); + CBlockHeaderAndShortTxIDs shortIDs{block}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << shortIDs; @@ -122,7 +122,7 @@ public: stream >> *this; } explicit TestHeaderAndShortIDs(const CBlock& block) : - TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs(block, true)) {} + TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block}) {} uint64_t GetShortID(const uint256& txhash) const { CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); @@ -279,7 +279,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest) // Test simple header round-trip with only coinbase { - CBlockHeaderAndShortTxIDs shortIDs(block, false); + CBlockHeaderAndShortTxIDs shortIDs{block}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << shortIDs; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 82b9617384..ba1eacfc78 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -65,8 +65,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey) { - const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; @@ -83,7 +82,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, block.hashMerkleRoot = BlockMerkleRoot(block); } - while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; + while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce; return block; } @@ -101,7 +100,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, CBlockHeader header = block->GetBlockHeader(); BlockValidationState state; - if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, Params(), &pindex)) { + if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) { return false; } } @@ -178,7 +177,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainA_last_header = last_header; for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; @@ -196,7 +195,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainB_last_header = last_header; for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; @@ -227,7 +226,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // Reorg back to chain A. for (size_t i = 2; i < 4; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } // Check that chain A and B blocks can be retrieved. diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index 8eb4dbc592..178757e7b4 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test) } unsigned int pos = 0; - /*int block_height =*/ test[pos++].get_int(); + /*int block_height =*/ test[pos++].getInt<int>(); uint256 block_hash; BOOST_CHECK(ParseHashStr(test[pos++].get_str(), block_hash)); diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 79d6b94dff..875522d744 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -95,7 +95,7 @@ struct MemoryCheck { { return true; } - MemoryCheck(){}; + MemoryCheck() = default; MemoryCheck(const MemoryCheck& x) { // We have to do this to make sure that destructor calls are paired @@ -129,7 +129,7 @@ struct FrozenCleanupCheck { { return true; } - FrozenCleanupCheck() {} + FrozenCleanupCheck() = default; ~FrozenCleanupCheck() { if (should_freeze) { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 82e4e1c90f..b333a9f72d 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <attributes.h> #include <clientversion.h> #include <coins.h> #include <script/standard.h> diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 5b73481bc1..50eb479035 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -13,8 +13,8 @@ #include <chrono> -using node::CCoinsStats; -using node::CoinStatsHashType; +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; BOOST_AUTO_TEST_SUITE(coinstatsindex_tests) @@ -33,7 +33,6 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) { CoinStatsIndex coin_stats_index{1 << 20, true}; - CCoinsStats coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* block_index; { LOCK(cs_main); @@ -41,7 +40,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) } // CoinStatsIndex should not be found before it is started. - BOOST_CHECK(!coin_stats_index.LookUpStats(block_index, coin_stats)); + BOOST_CHECK(!coin_stats_index.LookUpStats(block_index)); // BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex // is started. @@ -57,10 +56,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } - BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index, coin_stats)); + BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index)); // Check that CoinStatsIndex updates with new blocks. - coin_stats_index.LookUpStats(block_index, coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(block_index)); const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; std::vector<CMutableTransaction> noTxns; @@ -69,13 +68,12 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // Let the CoinStatsIndex to catch up again. BOOST_CHECK(coin_stats_index.BlockUntilSyncedToCurrentChain()); - CCoinsStats new_coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* new_block_index; { LOCK(cs_main); new_block_index = m_node.chainman->ActiveChain().Tip(); } - coin_stats_index.LookUpStats(new_block_index, new_coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index)); BOOST_CHECK(block_index != new_block_index); diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index fc89fe1450..ab4c587c46 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -321,7 +321,7 @@ struct StringContentsSerializer { // Used to make two serialized objects the same while letting them have different lengths // This is a terrible idea std::string str; - StringContentsSerializer() {} + StringContentsSerializer() = default; explicit StringContentsSerializer(const std::string& inp) : str(inp) {} StringContentsSerializer& operator+=(const std::string& s) { diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 7b492fd1da..3b4a6f2637 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -44,11 +44,10 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { - const CChainParams& chainparams = Params(); auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); // Disable inactivity checks for this test to avoid interference static_cast<ConnmanTestMsg*>(connman.get())->SetPeerConnectTimeout(99999s); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, false); // Mock an outbound peer @@ -134,9 +133,8 @@ static void AddRandomOutboundPeer(NodeId& id, std::vector<CNode*>& vNodes, PeerM BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { NodeId id{0}; - const CChainParams& chainparams = Params(); auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; @@ -147,7 +145,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) const auto time_init{GetTime<std::chrono::seconds>()}; SetMockTime(time_init); - const auto time_later{time_init + 3 * std::chrono::seconds{chainparams.GetConsensus().nPowTargetSpacing} + 1s}; + const auto time_later{time_init + 3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s}; connman->Init(options); std::vector<CNode *> vNodes; @@ -212,9 +210,8 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) BOOST_AUTO_TEST_CASE(block_relay_only_eviction) { NodeId id{0}; - const CChainParams& chainparams = Params(); auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS}; @@ -274,10 +271,9 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) BOOST_AUTO_TEST_CASE(peer_discouragement) { - const CChainParams& chainparams = Params(); auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, false); CNetAddr tor_netaddr; @@ -390,10 +386,9 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_AUTO_TEST_CASE(DoS_bantime) { - const CChainParams& chainparams = Params(); auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 360dc00307..6c96702f1e 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -10,7 +10,6 @@ #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <key.h> -#include <node/coinstats.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <pubkey.h> @@ -26,10 +25,6 @@ #include <string> #include <vector> -using node::CCoinsStats; -using node::CoinStatsHashType; -using node::GetUTXOStats; - namespace { const TestingSetup* g_setup; const Coin EMPTY_COIN{}; @@ -270,16 +265,6 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); }, [&] { - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; - bool expected_code_path = false; - try { - (void)GetUTXOStats(&coins_view_cache, g_setup->m_node.chainman->m_blockman, stats); - } catch (const std::logic_error&) { - expected_code_path = true; - } - assert(expected_code_path); - }, - [&] { (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); }); } diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index 59adec075e..24ae34bd9e 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -194,7 +194,7 @@ int main(int argc, char** argv) return 0; } std::signal(SIGABRT, signal_handler); - int64_t start_time = GetTimeSeconds(); + const auto start_time{Now<SteadySeconds>()}; int tested = 0; for (int i = 1; i < argc; ++i) { fs::path input_path(*(argv + i)); @@ -215,8 +215,8 @@ int main(int argc, char** argv) buffer.clear(); } } - int64_t end_time = GetTimeSeconds(); - std::cout << g_fuzz_target << ": succeeded against " << tested << " files in " << (end_time - start_time) << "s." << std::endl; + const auto end_time{Now<SteadySeconds>()}; + std::cout << g_fuzz_target << ": succeeded against " << tested << " files in " << count_seconds(end_time - start_time) << "s." << std::endl; #endif return 0; } diff --git a/src/test/fuzz/hex.cpp b/src/test/fuzz/hex.cpp index cc1bc1c8cf..e637975b48 100644 --- a/src/test/fuzz/hex.cpp +++ b/src/test/fuzz/hex.cpp @@ -25,6 +25,8 @@ FUZZ_TARGET_INIT(hex, initialize_hex) { const std::string random_hex_string(buffer.begin(), buffer.end()); const std::vector<unsigned char> data = ParseHex(random_hex_string); + const std::vector<std::byte> bytes{ParseHex<std::byte>(random_hex_string)}; + assert(AsBytes(Span{data}) == Span{bytes}); const std::string hex_data = HexStr(data); if (IsHex(random_hex_string)) { assert(ToLower(random_hex_string) == hex_data); diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp new file mode 100644 index 0000000000..6be75322b4 --- /dev/null +++ b/src/test/fuzz/miniscript.cpp @@ -0,0 +1,167 @@ +// 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 <core_io.h> +#include <hash.h> +#include <key.h> +#include <script/miniscript.h> +#include <script/script.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <util/strencodings.h> + +namespace { + +//! Some pre-computed data for more efficient string roundtrips. +struct TestData { + typedef CPubKey Key; + + // Precomputed public keys. + std::vector<Key> dummy_keys; + std::map<Key, int> dummy_key_idx_map; + std::map<CKeyID, Key> dummy_keys_map; + + //! Set the precomputed data. + void Init() { + unsigned char keydata[32] = {1}; + for (size_t i = 0; i < 256; i++) { + keydata[31] = i; + CKey privkey; + privkey.Set(keydata, keydata + 32, true); + const Key pubkey = privkey.GetPubKey(); + + dummy_keys.push_back(pubkey); + dummy_key_idx_map.emplace(pubkey, i); + dummy_keys_map.insert({pubkey.GetID(), pubkey}); + } + } +} TEST_DATA; + +/** + * Context to parse a Miniscript node to and from Script or text representation. + * Uses an integer (an index in the dummy keys array from the test data) as keys in order + * to focus on fuzzing the Miniscript nodes' test representation, not the key representation. + */ +struct ParserContext { + typedef CPubKey Key; + + bool KeyCompare(const Key& a, const Key& b) const { + return a < b; + } + + std::optional<std::string> ToString(const Key& key) const + { + auto it = TEST_DATA.dummy_key_idx_map.find(key); + if (it == TEST_DATA.dummy_key_idx_map.end()) return {}; + uint8_t idx = it->second; + return HexStr(Span{&idx, 1}); + } + + template<typename I> + std::optional<Key> FromString(I first, I last) const { + if (last - first != 2) return {}; + auto idx = ParseHex(std::string(first, last)); + if (idx.size() != 1) return {}; + return TEST_DATA.dummy_keys[idx[0]]; + } + + template<typename I> + std::optional<Key> FromPKBytes(I first, I last) const { + Key key; + key.Set(first, last); + if (!key.IsValid()) return {}; + return key; + } + + template<typename I> + std::optional<Key> FromPKHBytes(I first, I last) const { + assert(last - first == 20); + CKeyID keyid; + std::copy(first, last, keyid.begin()); + const auto it = TEST_DATA.dummy_keys_map.find(keyid); + if (it == TEST_DATA.dummy_keys_map.end()) return {}; + return it->second; + } +} PARSER_CTX; + +//! Context that implements naive conversion from/to script only, for roundtrip testing. +struct ScriptParserContext { + //! For Script roundtrip we never need the key from a key hash. + struct Key { + bool is_hash; + std::vector<unsigned char> data; + }; + + bool KeyCompare(const Key& a, const Key& b) const { + return a.data < b.data; + } + + const std::vector<unsigned char>& ToPKBytes(const Key& key) const + { + assert(!key.is_hash); + return key.data; + } + + const std::vector<unsigned char> ToPKHBytes(const Key& key) const + { + if (key.is_hash) return key.data; + const auto h = Hash160(key.data); + return {h.begin(), h.end()}; + } + + template<typename I> + std::optional<Key> FromPKBytes(I first, I last) const + { + Key key; + key.data.assign(first, last); + key.is_hash = false; + return key; + } + + template<typename I> + std::optional<Key> FromPKHBytes(I first, I last) const + { + Key key; + key.data.assign(first, last); + key.is_hash = true; + return key; + } +} SCRIPT_PARSER_CONTEXT; + +} // namespace + +void FuzzInit() +{ + ECC_Start(); + TEST_DATA.Init(); +} + +/* Fuzz tests that test parsing from a string, and roundtripping via string. */ +FUZZ_TARGET_INIT(miniscript_string, FuzzInit) +{ + FuzzedDataProvider provider(buffer.data(), buffer.size()); + auto str = provider.ConsumeRemainingBytesAsString(); + auto parsed = miniscript::FromString(str, PARSER_CTX); + if (!parsed) return; + + const auto str2 = parsed->ToString(PARSER_CTX); + assert(str2); + auto parsed2 = miniscript::FromString(*str2, PARSER_CTX); + assert(parsed2); + assert(*parsed == *parsed2); +} + +/* Fuzz tests that test parsing from a script, and roundtripping via script. */ +FUZZ_TARGET(miniscript_script) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider); + if (!script) return; + + const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT); + if (!ms) return; + + assert(ms->ToScript(SCRIPT_PARSER_CONTEXT) == *script); +} diff --git a/src/test/fuzz/miniscript_decode.cpp b/src/test/fuzz/miniscript_decode.cpp deleted file mode 100644 index 4cc0a1be8f..0000000000 --- a/src/test/fuzz/miniscript_decode.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include <core_io.h> -#include <hash.h> -#include <key.h> -#include <script/miniscript.h> -#include <script/script.h> -#include <span.h> -#include <test/fuzz/FuzzedDataProvider.h> -#include <test/fuzz/fuzz.h> -#include <test/fuzz/util.h> -#include <util/strencodings.h> - -#include <optional> - -using miniscript::operator""_mst; - - -struct Converter { - typedef CPubKey Key; - - bool ToString(const Key& key, std::string& ret) const { - ret = HexStr(key); - return true; - } - const std::vector<unsigned char> ToPKBytes(const Key& key) const { - return {key.begin(), key.end()}; - } - const std::vector<unsigned char> ToPKHBytes(const Key& key) const { - const auto h = Hash160(key); - return {h.begin(), h.end()}; - } - - template<typename I> - bool FromString(I first, I last, Key& key) const { - const auto bytes = ParseHex(std::string(first, last)); - key.Set(bytes.begin(), bytes.end()); - return key.IsValid(); - } - template<typename I> - bool FromPKBytes(I first, I last, CPubKey& key) const { - key.Set(first, last); - return key.IsValid(); - } - template<typename I> - bool FromPKHBytes(I first, I last, CPubKey& key) const { - assert(last - first == 20); - return false; - } -}; - -const Converter CONVERTER; - -FUZZ_TARGET(miniscript_decode) -{ - FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider); - if (!script) return; - - const auto ms = miniscript::FromScript(*script, CONVERTER); - if (!ms) return; - - // We can roundtrip it to its string representation. - std::string ms_str; - assert(ms->ToString(CONVERTER, ms_str)); - assert(*miniscript::FromString(ms_str, CONVERTER) == *ms); - // The Script representation must roundtrip since we parsed it this way the first time. - const CScript ms_script = ms->ToScript(CONVERTER); - assert(ms_script == *script); -} diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 03a84b697d..e4e83c3f32 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -128,6 +128,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "getmempoolancestors", "getmempooldescendants", "getmempoolentry", + "gettxspendingprevout", "getmempoolinfo", "getmininginfo", "getnettotals", diff --git a/src/test/fuzz/script_assets_test_minimizer.cpp b/src/test/fuzz/script_assets_test_minimizer.cpp index a18a0de47e..35d7246ed8 100644 --- a/src/test/fuzz/script_assets_test_minimizer.cpp +++ b/src/test/fuzz/script_assets_test_minimizer.cpp @@ -149,7 +149,7 @@ void Test(const std::string& str) CMutableTransaction tx = TxFromHex(test["tx"].get_str()); const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]); if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts"); - size_t idx = test["index"].get_int64(); + size_t idx = test["index"].getInt<int64_t>(); if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index"); unsigned int test_flags = ParseScriptFlags(test["flags"].get_str()); bool final = test.exists("final") && test["final"].get_bool(); diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp index f6c591aca4..a585680de1 100644 --- a/src/test/fuzz/signature_checker.cpp +++ b/src/test/fuzz/signature_checker.cpp @@ -49,7 +49,7 @@ public: return m_fuzzed_data_provider.ConsumeBool(); } - virtual ~FuzzedSignatureChecker() {} + virtual ~FuzzedSignatureChecker() = default; }; } // namespace diff --git a/src/test/fuzz/tx_out.cpp b/src/test/fuzz/tx_out.cpp index 39a50b6c80..a2421ff582 100644 --- a/src/test/fuzz/tx_out.cpp +++ b/src/test/fuzz/tx_out.cpp @@ -4,6 +4,7 @@ #include <consensus/validation.h> #include <core_memusage.h> +#include <policy/feerate.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <streams.h> diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index f686f4fd86..771d7a11cb 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -97,7 +97,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh BlockAssembler::Options options; options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT); options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)}; - auto assembler = BlockAssembler{chainstate, *static_cast<CTxMemPool*>(&tx_pool), chainstate.m_params, options}; + auto assembler = BlockAssembler{chainstate, *static_cast<CTxMemPool*>(&tx_pool), options}; auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE); Assert(block_template->block.vtx.size() >= 1); } diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 580105e442..3fc6fa1cd5 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -6,7 +6,6 @@ #define BITCOIN_TEST_FUZZ_UTIL_H #include <arith_uint256.h> -#include <attributes.h> #include <chainparamsbase.h> #include <coins.h> #include <compat.h> diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp index e513f1883c..33496a457e 100644 --- a/src/test/fuzz/utxo_snapshot.cpp +++ b/src/test/fuzz/utxo_snapshot.cpp @@ -58,7 +58,7 @@ FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain) if (fuzzed_data_provider.ConsumeBool()) { for (const auto& block : *g_chain) { BlockValidationState dummy; - bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy, ::Params())}; + bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy)}; Assert(processed); const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))}; Assert(index); diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index b06157e99f..e70b8b3dfd 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_parse) continue; } std::string exp_base58string = test[0].get_str(); - std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); + const std::vector<std::byte> exp_payload{ParseHex<std::byte>(test[1].get_str())}; const UniValue &metadata = test[2].get_obj(); bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); SelectParams(find_value(metadata, "chain").get_str()); diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp index cbfb6c67c3..3f6a605945 100644 --- a/src/test/logging_tests.cpp +++ b/src/test/logging_tests.cpp @@ -5,13 +5,55 @@ #include <logging.h> #include <logging/timer.h> #include <test/util/setup_common.h> +#include <util/string.h> #include <chrono> +#include <fstream> +#include <iostream> +#include <utility> +#include <vector> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup) +struct LogSetup : public BasicTestingSetup { + fs::path prev_log_path; + fs::path tmp_log_path; + bool prev_reopen_file; + bool prev_print_to_file; + bool prev_log_timestamps; + bool prev_log_threadnames; + bool prev_log_sourcelocations; + + LogSetup() : prev_log_path{LogInstance().m_file_path}, + tmp_log_path{m_args.GetDataDirBase() / "tmp_debug.log"}, + prev_reopen_file{LogInstance().m_reopen_file}, + prev_print_to_file{LogInstance().m_print_to_file}, + prev_log_timestamps{LogInstance().m_log_timestamps}, + prev_log_threadnames{LogInstance().m_log_threadnames}, + prev_log_sourcelocations{LogInstance().m_log_sourcelocations} + { + LogInstance().m_file_path = tmp_log_path; + LogInstance().m_reopen_file = true; + LogInstance().m_print_to_file = true; + LogInstance().m_log_timestamps = false; + LogInstance().m_log_threadnames = false; + LogInstance().m_log_sourcelocations = true; + } + + ~LogSetup() + { + LogInstance().m_file_path = prev_log_path; + LogPrintf("Sentinel log to reopen log file\n"); + LogInstance().m_print_to_file = prev_print_to_file; + LogInstance().m_reopen_file = prev_reopen_file; + LogInstance().m_log_timestamps = prev_log_timestamps; + LogInstance().m_log_threadnames = prev_log_threadnames; + LogInstance().m_log_sourcelocations = prev_log_sourcelocations; + } +}; + BOOST_AUTO_TEST_CASE(logging_timer) { SetMockTime(1); @@ -30,4 +72,82 @@ BOOST_AUTO_TEST_CASE(logging_timer) BOOST_CHECK_EQUAL(sec_timer.LogMsg("test secs"), "tests: test secs (1.00s)"); } +BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup) +{ + LogPrintf_("fn1", "src1", 1, BCLog::LogFlags::NET, BCLog::Level::Debug, "foo1: %s", "bar1\n"); + LogPrintf_("fn2", "src2", 2, BCLog::LogFlags::NET, BCLog::Level::None, "foo2: %s", "bar2\n"); + LogPrintf_("fn3", "src3", 3, BCLog::LogFlags::NONE, BCLog::Level::Debug, "foo3: %s", "bar3\n"); + LogPrintf_("fn4", "src4", 4, BCLog::LogFlags::NONE, BCLog::Level::None, "foo4: %s", "bar4\n"); + std::ifstream file{tmp_log_path}; + std::vector<std::string> log_lines; + for (std::string log; std::getline(file, log);) { + log_lines.push_back(log); + } + std::vector<std::string> expected = { + "[src1:1] [fn1] [net:debug] foo1: bar1", + "[src2:2] [fn2] [net] foo2: bar2", + "[src3:3] [fn3] [debug] foo3: bar3", + "[src4:4] [fn4] foo4: bar4", + }; + BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); +} + +BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup) +{ + // Prevent tests from failing when the line number of the following log calls changes. + LogInstance().m_log_sourcelocations = false; + + LogPrintf("foo5: %s\n", "bar5"); + LogPrint(BCLog::NET, "foo6: %s\n", "bar6"); + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "foo7: %s\n", "bar7"); + LogPrintLevel(BCLog::NET, BCLog::Level::Info, "foo8: %s\n", "bar8"); + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "foo9: %s\n", "bar9"); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "foo10: %s\n", "bar10"); + std::ifstream file{tmp_log_path}; + std::vector<std::string> log_lines; + for (std::string log; std::getline(file, log);) { + log_lines.push_back(log); + } + std::vector<std::string> expected = { + "foo5: bar5", + "[net] foo6: bar6", + "[net:debug] foo7: bar7", + "[net:info] foo8: bar8", + "[net:warning] foo9: bar9", + "[net:error] foo10: bar10"}; + BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); +} + +BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup) +{ + // Prevent tests from failing when the line number of the following log calls changes. + LogInstance().m_log_sourcelocations = false; + LogInstance().EnableCategory(BCLog::LogFlags::ALL); + const auto concated_categery_names = LogInstance().LogCategoriesString(); + std::vector<std::pair<BCLog::LogFlags, std::string>> expected_category_names; + const auto category_names = SplitString(concated_categery_names, ','); + for (const auto& category_name : category_names) { + BCLog::LogFlags category = BCLog::NONE; + const auto trimmed_category_name = TrimString(category_name); + BOOST_TEST(GetLogCategory(category, trimmed_category_name)); + expected_category_names.emplace_back(category, trimmed_category_name); + } + + std::vector<std::string> expected; + for (const auto& [category, name] : expected_category_names) { + LogPrint(category, "foo: %s\n", "bar"); + std::string expected_log = "["; + expected_log += name; + expected_log += "] foo: bar"; + expected.push_back(expected_log); + } + + std::ifstream file{tmp_log_path}; + std::vector<std::string> log_lines; + for (std::string log; std::getline(file, log);) { + log_lines.push_back(log); + } + BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index cafa5710fa..439ad174b3 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -10,6 +10,7 @@ #include <node/miner.h> #include <policy/policy.h> #include <script/standard.h> +#include <timedata.h> #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> @@ -51,7 +52,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, params, options); + return BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool, options}; } constexpr static struct { @@ -587,7 +588,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) pblock->nNonce = bi.nonce; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr)); pblock->hashPrevBlock = pblock->GetHash(); } diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp index 930582ea24..3877fea907 100644 --- a/src/test/miniscript_tests.cpp +++ b/src/test/miniscript_tests.cpp @@ -71,6 +71,10 @@ std::unique_ptr<const TestData> g_testdata; struct KeyConverter { typedef CPubKey Key; + bool KeyCompare(const Key& a, const Key& b) const { + return a < b; + } + //! Convert a public key to bytes. std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; } @@ -84,27 +88,28 @@ struct KeyConverter { //! Parse a public key from a range of hex characters. template<typename I> - bool FromString(I first, I last, CPubKey& key) const { + std::optional<Key> FromString(I first, I last) const { auto bytes = ParseHex(std::string(first, last)); - key.Set(bytes.begin(), bytes.end()); - return key.IsValid(); + Key key{bytes.begin(), bytes.end()}; + if (key.IsValid()) return key; + return {}; } template<typename I> - bool FromPKBytes(I first, I last, CPubKey& key) const { - key.Set(first, last); - return key.IsValid(); + std::optional<Key> FromPKBytes(I first, I last) const { + Key key{first, last}; + if (key.IsValid()) return key; + return {}; } template<typename I> - bool FromPKHBytes(I first, I last, CPubKey& key) const { + std::optional<Key> FromPKHBytes(I first, I last) const { assert(last - first == 20); CKeyID keyid; std::copy(first, last, keyid.begin()); auto it = g_testdata->pkmap.find(keyid); assert(it != g_testdata->pkmap.end()); - key = it->second; - return true; + return it->second; } }; @@ -272,6 +277,19 @@ BOOST_AUTO_TEST_CASE(fixed_tests) // its subs to all be 'u' (taken from https://github.com/rust-bitcoin/rust-miniscript/discussions/341). const auto ms_minimalif = miniscript::FromString("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),sc:pk_k(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),sdv:older(32))", CONVERTER); BOOST_CHECK(!ms_minimalif); + // A Miniscript with duplicate keys is not sane + const auto ms_dup1 = miniscript::FromString("and_v(v:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER); + BOOST_CHECK(ms_dup1); + BOOST_CHECK(!ms_dup1->IsSane() && !ms_dup1->CheckDuplicateKey()); + // Same with a disjunction, and different key nodes (pk and pkh) + const auto ms_dup2 = miniscript::FromString("or_b(c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER); + BOOST_CHECK(ms_dup2 && !ms_dup2->IsSane() && !ms_dup2->CheckDuplicateKey()); + // Same when the duplicates are leaves or a larger tree + const auto ms_dup3 = miniscript::FromString("or_i(and_b(pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)),and_b(older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER); + BOOST_CHECK(ms_dup3 && !ms_dup3->IsSane() && !ms_dup3->CheckDuplicateKey()); + // Same when the duplicates are on different levels in the tree + const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER); + BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey()); // Timelock tests Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 978a7bee4d..9b2760fd1c 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -26,11 +26,21 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) FastRandomContext ctx2(true); for (int i = 10; i > 0; --i) { - BOOST_CHECK_EQUAL(GetRand(std::numeric_limits<uint64_t>::max()), uint64_t{10393729187455219830U}); - BOOST_CHECK_EQUAL(GetRandInt(std::numeric_limits<int>::max()), int{769702006}); + BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10393729187455219830U}); + BOOST_CHECK_EQUAL(GetRand<int>(), int{769702006}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); } + { + constexpr SteadySeconds time_point{1s}; + FastRandomContext ctx{true}; + BOOST_CHECK_EQUAL(7, ctx.rand_uniform_delay(time_point, 9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-6, ctx.rand_uniform_delay(time_point, -9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1, ctx.rand_uniform_delay(time_point, 0s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1467825113502396065, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-970181367944767837, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(24761, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count()); + } BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand64(), ctx2.rand64()); @@ -47,8 +57,8 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) // Check that a nondeterministic ones are not g_mock_deterministic_tests = false; for (int i = 10; i > 0; --i) { - BOOST_CHECK(GetRand(std::numeric_limits<uint64_t>::max()) != uint64_t{10393729187455219830U}); - BOOST_CHECK(GetRandInt(std::numeric_limits<int>::max()) != int{769702006}); + BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10393729187455219830U}); + BOOST_CHECK(GetRand<int>() != int{769702006}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374}); } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 7cec287e8f..3e9e04da25 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -66,9 +66,9 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams) BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), std::runtime_error); std::string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; BOOST_CHECK_NO_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx)); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").get_int(), 193); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").getInt<int>(), 193); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").getInt<int>(), 1); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").getInt<int>(), 0); BOOST_CHECK_THROW(CallRPC(std::string("decoderawtransaction ")+rawtx+" extra"), std::runtime_error); BOOST_CHECK_NO_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx+" false")); BOOST_CHECK_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx+" false extra"), std::runtime_error); @@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE(rpc_togglenetwork) BOOST_CHECK_NO_THROW(CallRPC("setnetworkactive false")); r = CallRPC("getnetworkinfo"); - int numConnection = find_value(r.get_obj(), "connections").get_int(); + int numConnection = find_value(r.get_obj(), "connections").getInt<int>(); BOOST_CHECK_EQUAL(numConnection, 0); netState = find_value(r.get_obj(), "networkactive").get_bool(); @@ -264,7 +264,7 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - int64_t banned_until{find_value(o1, "banned_until").get_int64()}; + int64_t banned_until{find_value(o1, "banned_until").getInt<int64_t>()}; BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/24"); BOOST_CHECK_EQUAL(banned_until, 9907731200); // absolute time check @@ -279,10 +279,10 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - banned_until = find_value(o1, "banned_until").get_int64(); - const int64_t ban_created{find_value(o1, "ban_created").get_int64()}; - const int64_t ban_duration{find_value(o1, "ban_duration").get_int64()}; - const int64_t time_remaining{find_value(o1, "time_remaining").get_int64()}; + banned_until = find_value(o1, "banned_until").getInt<int64_t>(); + const int64_t ban_created{find_value(o1, "ban_created").getInt<int64_t>()}; + const int64_t ban_duration{find_value(o1, "ban_duration").getInt<int64_t>()}; + const int64_t time_remaining{find_value(o1, "time_remaining").getInt<int64_t>()}; BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/24"); BOOST_CHECK_EQUAL(banned_until, time_remaining_expected + now.count()); BOOST_CHECK_EQUAL(ban_duration, banned_until - ban_created); @@ -337,22 +337,22 @@ BOOST_AUTO_TEST_CASE(rpc_convert_values_generatetoaddress) UniValue result; BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"101", "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 101); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 101); BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"101", "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 101); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 101); BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"1", "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a", "9"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 1); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 1); BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"); - BOOST_CHECK_EQUAL(result[2].get_int(), 9); + BOOST_CHECK_EQUAL(result[2].getInt<int>(), 9); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"1", "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU", "9"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 1); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 1); BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"); - BOOST_CHECK_EQUAL(result[2].get_int(), 9); + BOOST_CHECK_EQUAL(result[2].getInt<int>(), 9); } BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_weight) diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index a7057f8361..907a3fd15b 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <compat/sanity.h> #include <key.h> #include <test/util/setup_common.h> #include <util/time.h> @@ -13,7 +12,6 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(basic_sanity) { - BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); } diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 6e089de0c1..7b5dda8114 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -15,13 +15,13 @@ BOOST_AUTO_TEST_SUITE(scheduler_tests) -static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::system_clock::time_point rescheduleTime) +static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::steady_clock::time_point rescheduleTime) { { std::lock_guard<std::mutex> lock(mutex); counter += delta; } - std::chrono::system_clock::time_point noTime = std::chrono::system_clock::time_point::min(); + auto noTime = std::chrono::steady_clock::time_point::min(); if (rescheduleTime != noTime) { CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); @@ -49,15 +49,15 @@ BOOST_AUTO_TEST_CASE(manythreads) auto randomMsec = [](FastRandomContext& rc) -> int { return -11 + (int)rc.randrange(1012); }; // [-11, 1000] auto randomDelta = [](FastRandomContext& rc) -> int { return -1000 + (int)rc.randrange(2001); }; // [-1000, 1000] - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - std::chrono::system_clock::time_point now = start; - std::chrono::system_clock::time_point first, last; + auto start = std::chrono::steady_clock::now(); + auto now = start; + std::chrono::steady_clock::time_point first, last; size_t nTasks = microTasks.getQueueInfo(first, last); BOOST_CHECK(nTasks == 0); for (int i = 0; i < 100; ++i) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -75,14 +75,14 @@ BOOST_AUTO_TEST_CASE(manythreads) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); UninterruptibleSleep(std::chrono::microseconds{600}); - now = std::chrono::system_clock::now(); + now = std::chrono::steady_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -111,8 +111,8 @@ BOOST_AUTO_TEST_CASE(wait_until_past) Mutex mtx; WAIT_LOCK(mtx, lock); - const auto no_wait= [&](const std::chrono::seconds& d) { - return condvar.wait_until(lock, std::chrono::system_clock::now() - d); + const auto no_wait = [&](const std::chrono::seconds& d) { + return condvar.wait_until(lock, std::chrono::steady_clock::now() - d); }; BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::seconds{1})); @@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(mockforward) scheduler.scheduleFromNow(dummy, std::chrono::minutes{8}); // check taskQueue - std::chrono::system_clock::time_point first, last; + std::chrono::steady_clock::time_point first, last; size_t num_tasks = scheduler.getQueueInfo(first, last); BOOST_CHECK_EQUAL(num_tasks, 3ul); @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(mockforward) BOOST_CHECK_EQUAL(counter, 2); // check that the time of the remaining job has been updated - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + auto now = std::chrono::steady_clock::now(); int delta = std::chrono::duration_cast<std::chrono::seconds>(first - now).count(); // should be between 2 & 3 minutes from now BOOST_CHECK(delta > 2*60 && delta < 3*60); diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 75bc616cf9..25e47c0fb0 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -406,8 +406,8 @@ BOOST_AUTO_TEST_CASE(bip341_spk_test_vectors) if (node.isObject()) { auto script_bytes = ParseHex(node["script"].get_str()); CScript script(script_bytes.begin(), script_bytes.end()); - int idx = node["id"].get_int(); - int leaf_version = node["leafVersion"].get_int(); + int idx = node["id"].getInt<int>(); + int leaf_version = node["leafVersion"].getInt<int>(); scriptposes[{script, leaf_version}] = idx; spktest.Add(depth, script, leaf_version); } else { diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 19e32d0c36..05bb89ab55 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -257,11 +257,11 @@ private: CScriptWitness scriptWitness; CTransactionRef creditTx; CMutableTransaction spendTx; - bool havePush; + bool havePush{false}; std::vector<unsigned char> push; std::string comment; uint32_t flags; - int scriptError; + int scriptError{SCRIPT_ERR_OK}; CAmount nValue; void DoPush() @@ -280,7 +280,7 @@ private: } public: - TestBuilder(const CScript& script_, const std::string& comment_, uint32_t flags_, bool P2SH = false, WitnessMode wm = WitnessMode::NONE, int witnessversion = 0, CAmount nValue_ = 0) : script(script_), havePush(false), comment(comment_), flags(flags_), scriptError(SCRIPT_ERR_OK), nValue(nValue_) + TestBuilder(const CScript& script_, const std::string& comment_, uint32_t flags_, bool P2SH = false, WitnessMode wm = WitnessMode::NONE, int witnessversion = 0, CAmount nValue_ = 0) : script(script_), comment(comment_), flags(flags_), nValue(nValue_) { CScript scriptPubKey = script; if (wm == WitnessMode::PKH) { @@ -1678,7 +1678,7 @@ static void AssetTest(const UniValue& test) CMutableTransaction mtx = TxFromHex(test["tx"].get_str()); const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]); BOOST_CHECK(prevouts.size() == mtx.vin.size()); - size_t idx = test["index"].get_int64(); + size_t idx = test["index"].getInt<int64_t>(); uint32_t test_flags{ParseScriptFlags(test["flags"].get_str())}; bool fin = test.exists("final") && test["final"].get_bool(); @@ -1760,7 +1760,7 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) for (const auto& utxo_spent : vec["given"]["utxosSpent"].getValues()) { auto script_bytes = ParseHex(utxo_spent["scriptPubKey"].get_str()); CScript script{script_bytes.begin(), script_bytes.end()}; - CAmount amount{utxo_spent["amountSats"].get_int()}; + CAmount amount{utxo_spent["amountSats"].getInt<int>()}; utxos.emplace_back(amount, script); } @@ -1775,8 +1775,8 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) BOOST_CHECK_EQUAL(HexStr(txdata.m_sequences_single_hash), vec["intermediary"]["hashSequences"].get_str()); for (const auto& input : vec["inputSpending"].getValues()) { - int txinpos = input["given"]["txinIndex"].get_int(); - int hashtype = input["given"]["hashType"].get_int(); + int txinpos = input["given"]["txinIndex"].getInt<int>(); + int hashtype = input["given"]["hashType"].getInt<int>(); // Load key. auto privkey = ParseHex(input["given"]["internalPrivkey"].get_str()); diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp index 15ffd068c7..0feb68b9b1 100644 --- a/src/test/settings_tests.cpp +++ b/src/test/settings_tests.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(ReadWrite) //! Check settings struct contents against expected json strings. static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val) { - util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false); + util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false, false); util::SettingsValue list_value(util::SettingsValue::VARR); for (const auto& item : GetSettingsList(settings, "section", "name", false)) { list_value.push_back(item); @@ -141,9 +141,9 @@ BOOST_AUTO_TEST_CASE(NullOverride) { util::Settings settings; settings.command_line_options["name"].push_back("value"); - BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false, false).write().c_str()); settings.forced_settings["name"] = {}; - BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false, false).write().c_str()); } // Test different ways settings can be merged, and verify results. This test can @@ -224,7 +224,7 @@ BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) } desc += " || "; - desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write(); + desc += GetSetting(settings, network, name, ignore_default_section_config, /*ignore_nonpersistent=*/false, /*get_chain_name=*/false).write(); desc += " |"; for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) { desc += " "; diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 1601b02356..7376f2ff5c 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -184,8 +184,8 @@ BOOST_AUTO_TEST_CASE(sighash_from_data) // deserialize test data raw_tx = test[0].get_str(); raw_script = test[1].get_str(); - nIn = test[2].get_int(); - nHashType = test[3].get_int(); + nIn = test[2].getInt<int>(); + nHashType = test[3].getInt<int>(); sigHashHex = test[4].get_str(); CDataStream stream(ParseHex(raw_tx), SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index c3a2a66027..4e6c223ccc 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -217,11 +217,11 @@ BOOST_AUTO_TEST_CASE(tx_valid) fValid = false; break; } - COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].get_int())}; + COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].getInt<int>())}; mapprevOutScriptPubKeys[outpoint] = ParseScript(vinput[2].get_str()); if (vinput.size() >= 4) { - mapprevOutValues[outpoint] = vinput[3].get_int64(); + mapprevOutValues[outpoint] = vinput[3].getInt<int64_t>(); } } if (!fValid) @@ -305,11 +305,11 @@ BOOST_AUTO_TEST_CASE(tx_invalid) fValid = false; break; } - COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].get_int())}; + COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].getInt<int>())}; mapprevOutScriptPubKeys[outpoint] = ParseScript(vinput[2].get_str()); if (vinput.size() >= 4) { - mapprevOutValues[outpoint] = vinput[3].get_int64(); + mapprevOutValues[outpoint] = vinput[3].getInt<int64_t>(); } } if (!fValid) diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index d41b54af20..dd4bc5af75 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -15,7 +15,7 @@ struct Dersig100Setup : public TestChain100Setup { Dersig100Setup() - : TestChain100Setup{{"-testactivationheight=dersig@102"}} {} + : TestChain100Setup{CBaseChainParams::REGTEST, {"-testactivationheight=dersig@102"}} {} }; bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 5ed8598e8e..a6d624fe84 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -68,7 +68,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) assert(block->nNonce); } - bool processed{Assert(node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)}; + bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, nullptr)}; assert(processed); return CTxIn{block->vtx[0]->GetHash(), 0}; @@ -77,7 +77,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) { auto block = std::make_shared<CBlock>( - BlockAssembler{Assert(node.chainman)->ActiveChainstate(), *Assert(node.mempool), Params()} + BlockAssembler{Assert(node.chainman)->ActiveChainstate(), *Assert(node.mempool)} .CreateNewBlock(coinbase_scriptPubKey) ->block); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 2fc71c2a6e..8f03481f72 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -12,6 +12,7 @@ #include <consensus/validation.h> #include <crypto/sha256.h> #include <init.h> +#include <init/common.h> #include <interfaces/chain.h> #include <net.h> #include <net_processing.h> @@ -29,6 +30,7 @@ #include <shutdown.h> #include <streams.h> #include <test/util/net.h> +#include <timedata.h> #include <txdb.h> #include <util/strencodings.h> #include <util/string.h> @@ -42,6 +44,7 @@ #include <validationinterface.h> #include <walletinitinterface.h> +#include <algorithm> #include <functional> #include <stdexcept> @@ -124,8 +127,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve InitLogging(*m_node.args); AppInitParameterInteraction(*m_node.args); LogInstance().StartLogging(); - SHA256AutoDetect(); - ECC_Start(); + m_node.kernel = std::make_unique<kernel::Context>(); SetupEnvironment(); SetupNetworking(); InitSignatureCache(); @@ -145,12 +147,13 @@ BasicTestingSetup::~BasicTestingSetup() LogInstance().DisconnectTestLogger(); fs::remove_all(m_path_root); gArgs.ClearArgs(); - ECC_Stop(); } ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) : BasicTestingSetup(chainName, extra_args) { + const CChainParams& chainparams = Params(); + // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. m_node.scheduler = std::make_unique<CScheduler>(); @@ -158,11 +161,15 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler); m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); - m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1); + m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), m_node.args->GetIntArg("-checkmempool", 1)); m_cache_sizes = CalculateCacheSizes(m_args); - m_node.chainman = std::make_unique<ChainstateManager>(); + const ChainstateManager::Options chainman_opts{ + chainparams, + GetAdjustedTime, + }; + 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. @@ -190,7 +197,6 @@ ChainTestingSetup::~ChainTestingSetup() TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) : ChainTestingSetup(chainName, extra_args) { - const CChainParams& chainparams = Params(); // 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); @@ -199,7 +205,6 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const *Assert(m_node.chainman.get()), Assert(m_node.mempool.get()), fPruneMode, - chainparams.GetConsensus(), m_args.GetBoolArg("-reindex-chainstate", false), m_cache_sizes.block_tree_db, m_cache_sizes.coins_db, @@ -212,10 +217,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const *Assert(m_node.chainman), fReindex.load(), m_args.GetBoolArg("-reindex-chainstate", false), - chainparams.GetConsensus(), m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), - m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - /*get_unix_time_seconds=*/static_cast<int64_t(*)()>(GetTime)); + m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); assert(!maybe_verify_error.has_value()); BlockValidationState state; @@ -229,7 +232,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const m_node.args->GetIntArg("-checkaddrman", 0)); m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); // Deterministic randomness for tests. - m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, + m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman, m_node.banman.get(), *m_node.chainman, *m_node.mempool, false); { @@ -239,8 +242,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const } } -TestChain100Setup::TestChain100Setup(const std::vector<const char*>& extra_args) - : TestingSetup{CBaseChainParams::REGTEST, extra_args} +TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args) + : TestingSetup{chain_name, extra_args} { SetMockTime(1598887952); constexpr std::array<unsigned char, 32> vchKey = { @@ -274,9 +277,8 @@ CBlock TestChain100Setup::CreateBlock( const CScript& scriptPubKey, CChainState& chainstate) { - const CChainParams& chainparams = Params(); CTxMemPool empty_pool; - CBlock block = BlockAssembler(chainstate, empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler{chainstate, empty_pool}.CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { @@ -284,7 +286,7 @@ CBlock TestChain100Setup::CreateBlock( } RegenerateCommitments(block, *Assert(m_node.chainman)); - while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; + while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce; return block; } @@ -298,10 +300,9 @@ CBlock TestChain100Setup::CreateAndProcessBlock( chainstate = &Assert(m_node.chainman)->ActiveChainstate(); } - const CChainParams& chainparams = Params(); const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate); std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); - Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr); + Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr); return block; } @@ -356,6 +357,52 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio return mempool_txn; } +std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit) +{ + std::vector<CTransactionRef> mempool_transactions; + std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts; + std::transform(m_coinbase_txns.begin(), m_coinbase_txns.end(), std::back_inserter(unspent_prevouts), + [](const auto& tx){ return std::make_pair(COutPoint(tx->GetHash(), 0), tx->vout[0].nValue); }); + while (num_transactions > 0 && !unspent_prevouts.empty()) { + // The number of inputs and outputs are random, between 1 and 24. + CMutableTransaction mtx = CMutableTransaction(); + const size_t num_inputs = det_rand.randrange(24) + 1; + CAmount total_in{0}; + for (size_t n{0}; n < num_inputs; ++n) { + if (unspent_prevouts.empty()) break; + const auto& [prevout, amount] = unspent_prevouts.front(); + mtx.vin.push_back(CTxIn(prevout, CScript())); + total_in += amount; + unspent_prevouts.pop_front(); + } + const size_t num_outputs = det_rand.randrange(24) + 1; + // Approximately 1000sat "fee," equal output amounts. + const CAmount amount_per_output = (total_in - 1000) / num_outputs; + for (size_t n{0}; n < num_outputs; ++n) { + CScript spk = CScript() << CScriptNum(num_transactions + n); + mtx.vout.push_back(CTxOut(amount_per_output, spk)); + } + CTransactionRef ptx = MakeTransactionRef(mtx); + mempool_transactions.push_back(ptx); + if (amount_per_output > 2000) { + // If the value is high enough to fund another transaction + fees, keep track of it so + // it can be used to build a more complex transaction graph. Insert randomly into + // unspent_prevouts for extra randomness in the resulting structures. + for (size_t n{0}; n < num_outputs; ++n) { + unspent_prevouts.push_back(std::make_pair(COutPoint(ptx->GetHash(), n), amount_per_output)); + std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]); + } + } + if (submit) { + LOCK2(m_node.mempool->cs, cs_main); + LockPoints lp; + m_node.mempool->addUnchecked(CTxMemPoolEntry(ptx, 1000, 0, 1, false, 4, lp)); + } + --num_transactions; + } + return mempool_transactions; +} + CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const { return FromTx(MakeTransactionRef(tx)); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index a1b7525cf4..37407bcb92 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -81,8 +81,7 @@ static constexpr CAmount CENT{1000000}; * This just configures logging, data dir and chain parameters. */ struct BasicTestingSetup { - ECCVerifyHandle globalVerifyHandle; - node::NodeContext m_node; + node::NodeContext m_node; // keep as first member to be destructed last explicit BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {}); ~BasicTestingSetup(); @@ -122,7 +121,8 @@ class CScript; * Testing fixture that pre-creates a 100-block REGTEST-mode block chain */ struct TestChain100Setup : public TestingSetup { - TestChain100Setup(const std::vector<const char*>& extra_args = {}); + TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST, + const std::vector<const char*>& extra_args = {}); /** * Create a new block with just given transactions, coinbase paying to @@ -164,6 +164,19 @@ struct TestChain100Setup : public TestingSetup { CAmount output_amount = CAmount(1 * COIN), bool submit = true); + /** Create transactions spending from m_coinbase_txns. These transactions will only spend coins + * that exist in the current chain, but may be premature coinbase spends, have missing + * signatures, or violate some other consensus rules. They should only be used for testing + * mempool consistency. All transactions will have some random number of inputs and outputs + * (between 1 and 24). Transactions may or may not be dependent upon each other; if dependencies + * exit, every parent will always be somewhere in the list before the child so each transaction + * can be submitted in the same order they appear in the list. + * @param[in] submit When true, submit transactions to the mempool. + * When false, return them but don't submit them. + * @returns A vector of transactions that can be submitted to the mempool. + */ + std::vector<CTransactionRef> PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit); + std::vector<CTransactionRef> m_coinbase_txns; // For convenience, coinbase transactions CKey coinbaseKey; // private/public key needed to spend coinbase transactions }; diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 3b2aca5887..fda56ccff7 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -153,7 +153,7 @@ static const unsigned char ParseHex_expected[65] = { 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f }; -BOOST_AUTO_TEST_CASE(util_ParseHex) +BOOST_AUTO_TEST_CASE(parse_hex) { std::vector<unsigned char> result; std::vector<unsigned char> expected(ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)); @@ -169,6 +169,14 @@ BOOST_AUTO_TEST_CASE(util_ParseHex) result = ParseHex(" 89 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x89 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); + // Embedded null is treated as end + const std::string with_embedded_null{" 11 "s + " \0 " + " 22 "s}; + BOOST_CHECK_EQUAL(with_embedded_null.size(), 11); + result = ParseHex(with_embedded_null); + BOOST_CHECK(result.size() == 1 && result[0] == 0x11); + // Stop parsing at invalid value result = ParseHex("1234 invalid 1234"); BOOST_CHECK(result.size() == 2 && result[0] == 0x12 && result[1] == 0x34); @@ -265,9 +273,6 @@ BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); - - auto time = GetTimeSeconds(); - BOOST_CHECK_EQUAL(ParseISO8601DateTime(FormatISO8601DateTime(time)), time); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) @@ -1488,19 +1493,27 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime) { SetMockTime(111); // Check that mock time does not change after a sleep - for (const auto& num_sleep : {0, 1}) { - UninterruptibleSleep(std::chrono::milliseconds{num_sleep}); + for (const auto& num_sleep : {0ms, 1ms}) { + UninterruptibleSleep(num_sleep); BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter + BOOST_CHECK_EQUAL(111, Now<NodeSeconds>().time_since_epoch().count()); + BOOST_CHECK_EQUAL(111, TicksSinceEpoch<std::chrono::seconds>(NodeClock::now())); + BOOST_CHECK_EQUAL(111, TicksSinceEpoch<SecondsDouble>(Now<NodeSeconds>())); BOOST_CHECK_EQUAL(111, GetTime<std::chrono::seconds>().count()); BOOST_CHECK_EQUAL(111000, GetTime<std::chrono::milliseconds>().count()); + BOOST_CHECK_EQUAL(111000, TicksSinceEpoch<std::chrono::milliseconds>(NodeClock::now())); BOOST_CHECK_EQUAL(111000000, GetTime<std::chrono::microseconds>().count()); } SetMockTime(0); - // Check that system time changes after a sleep + // Check that steady time and system time changes after a sleep + const auto steady_ms_0 = Now<SteadyMilliseconds>(); + const auto steady_0 = std::chrono::steady_clock::now(); const auto ms_0 = GetTime<std::chrono::milliseconds>(); const auto us_0 = GetTime<std::chrono::microseconds>(); - UninterruptibleSleep(std::chrono::milliseconds{1}); + UninterruptibleSleep(1ms); + BOOST_CHECK(steady_ms_0 < Now<SteadyMilliseconds>()); + BOOST_CHECK(steady_0 + 1ms <= std::chrono::steady_clock::now()); BOOST_CHECK(ms_0 < GetTime<std::chrono::milliseconds>()); BOOST_CHECK(us_0 < GetTime<std::chrono::microseconds>()); } @@ -2449,9 +2462,9 @@ struct Tracker //! Points to the original object (possibly itself) we moved/copied from const Tracker* origin; //! How many copies where involved between the original object and this one (moves are not counted) - int copies; + int copies{0}; - Tracker() noexcept : origin(this), copies(0) {} + Tracker() noexcept : origin(this) {} Tracker(const Tracker& t) noexcept : origin(t.origin), copies(t.copies + 1) {} Tracker(Tracker&& t) noexcept : origin(t.origin), copies(t.copies) {} Tracker& operator=(const Tracker& t) noexcept diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index c5b1dabcb7..331da691b5 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -65,7 +65,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) static int i = 0; static uint64_t time = Params().GenesisBlock().nTime; - auto ptemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE); + auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(CScript{} << i++ << OP_TRUE); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -89,7 +89,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock) { const CBlockIndex* prev_block{WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(pblock->hashPrevBlock))}; - GenerateCoinbaseCommitment(*pblock, prev_block, Params().GetConsensus()); + m_node.chainman->GenerateCoinbaseCommitment(*pblock, prev_block); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -100,7 +100,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> // submit block header, so that miner can get the block height from the // global state and the node has the topology of the chain BlockValidationState ignored; - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored, Params())); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored)); return pblock; } @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) bool ignored; // Connect the genesis block and drain any outstanding events - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); SyncWithValidationInterfaceQueue(); // subscribe to events (this subscriber will validate event ordering) @@ -179,13 +179,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) FastRandomContext insecure; for (int i = 0; i < 1000; i++) { auto block = blocks[insecure.randrange(blocks.size() - 1)]; - Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); + Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); } // to make sure that eventually we process the full chain - do it here for (auto block : blocks) { if (block->vtx.size() == 1) { - bool processed = Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored); + bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); assert(processed); } } @@ -224,7 +224,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { bool ignored; auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool { - return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /*force_processing=*/true, /*new_block=*/&ignored); + return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored); }; // Process all mined blocks @@ -327,7 +327,7 @@ BOOST_AUTO_TEST_CASE(witness_commitment_index) { CScript pubKey; pubKey << 1 << OP_TRUE; - auto ptemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, Params()).CreateNewBlock(pubKey); + auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(pubKey); CBlock pblock = ptemplate->block; CTxOut witness; diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 2a3990bb7c..98cb713a81 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -3,13 +3,14 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <chainparams.h> -#include <random.h> -#include <uint256.h> #include <consensus/validation.h> -#include <sync.h> +#include <random.h> #include <rpc/blockchain.h> +#include <sync.h> #include <test/util/chainstate.h> #include <test/util/setup_common.h> +#include <timedata.h> +#include <uint256.h> #include <validation.h> #include <vector> @@ -22,7 +23,12 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, TestingSetup) //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { - ChainstateManager manager; + const ChainstateManager::Options chainman_opts{ + Params(), + GetAdjustedTime, + }; + ChainstateManager manager{chainman_opts}; + WITH_LOCK(::cs_main, manager.m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(1 << 20, true)); CTxMemPool mempool; diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index bf87812a8a..e6203af4b4 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -5,9 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <consensus/params.h> -#include <deploymentstatus.h> #include <test/util/setup_common.h> -#include <validation.h> #include <versionbits.h> #include <boost/test/unit_test.hpp> @@ -184,7 +182,7 @@ public: CBlockIndex* Tip() { return vpblock.empty() ? nullptr : vpblock.back(); } }; -BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(versionbits_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(versionbits_test) { @@ -257,10 +255,10 @@ BOOST_AUTO_TEST_CASE(versionbits_test) } /** Check that ComputeBlockVersion will set the appropriate bit correctly */ -static void check_computeblockversion(const Consensus::Params& params, Consensus::DeploymentPos dep) +static void check_computeblockversion(VersionBitsCache& versionbitscache, const Consensus::Params& params, Consensus::DeploymentPos dep) { - // This implicitly uses g_versionbitscache, so clear it every time - g_versionbitscache.Clear(); + // Clear the cache every time + versionbitscache.Clear(); int64_t bit = params.vDeployments[dep].bit; int64_t nStartTime = params.vDeployments[dep].nStartTime; @@ -268,13 +266,14 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus int min_activation_height = params.vDeployments[dep].min_activation_height; // should not be any signalling for first block - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); // always/never active deployments shouldn't need to be tested further if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE || nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) { BOOST_CHECK_EQUAL(min_activation_height, 0); + BOOST_CHECK_EQUAL(nTimeout, Consensus::BIP9Deployment::NO_TIMEOUT); return; } @@ -288,7 +287,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Check min_activation_height is on a retarget boundary BOOST_REQUIRE_EQUAL(min_activation_height % params.nMinerConfirmationWindow, 0U); - const uint32_t bitmask{g_versionbitscache.Mask(params, dep)}; + const uint32_t bitmask{versionbitscache.Mask(params, dep)}; BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit); // In the first chain, test that the bit is set by CBV until it has failed. @@ -307,9 +306,9 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // earlier time, so will transition from DEFINED to STARTED at the // end of the first period by mining blocks at nTime == 0 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then we'll keep mining at nStartTime... } else { // use a time 1s earlier than start time to check we stay DEFINED @@ -317,28 +316,28 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Start generating blocks before nStartTime lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); // Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet. for (uint32_t i = 1; i < params.nMinerConfirmationWindow - 4; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Now mine 5 more blocks at the start time -- MTP should not have passed yet, so // CBV should still not yet set the bit. nTime = nStartTime; for (uint32_t i = params.nMinerConfirmationWindow - 4; i <= params.nMinerConfirmationWindow; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Next we will advance to the next period and transition to STARTED, } lastBlock = firstChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); // so ComputeBlockVersion should now set the bit, - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // and should also be using the VERSIONBITS_TOP_BITS. - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); // Check that ComputeBlockVersion will set the bit until nTimeout nTime += 600; @@ -347,8 +346,8 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // These blocks are all before nTimeout is reached. while (nTime < nTimeout && blocksToMine > 0) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); blocksToMine--; nTime += 600; nHeight += 1; @@ -362,7 +361,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // finish the last period before we start timing out while (nHeight % params.nMinerConfirmationWindow != 0) { lastBlock = firstChain.Mine(nHeight+1, nTime - 1, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } @@ -370,12 +369,12 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // the bit until the period transition. for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } // The next block should trigger no longer setting the bit. lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // On a new chain: @@ -386,34 +385,36 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Mine one period worth of blocks, and check that the bit will be on for the // next period. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Mine another period worth of blocks, signaling the new bit. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip(); // After one period of setting the bit on each block, it should have locked in. // We keep setting the bit for one more period though, until activation. - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Now check that we keep mining the block until the end of this period, and // then stop at the beginning of the next period. lastBlock = secondChain.Mine((params.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); if (lastBlock->nHeight + 1 < min_activation_height) { // check signalling continues while min_activation_height is not reached lastBlock = secondChain.Mine(min_activation_height - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then reach min_activation_height, which was already REQUIRE'd to start a new period lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); } // Check that we don't signal after activation - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) { + VersionBitsCache vbcache; + // check that any deployment on any chain can conceivably reach both // ACTIVE and FAILED states in roughly the way we expect for (const auto& chain_name : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET, CBaseChainParams::REGTEST}) { @@ -426,10 +427,10 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) // not take precedence over STARTED/LOCKED_IN. So all softforks on // the same bit might overlap, even when non-overlapping start-end // times are picked. - const uint32_t dep_mask{g_versionbitscache.Mask(chainParams->GetConsensus(), dep)}; + const uint32_t dep_mask{vbcache.Mask(chainParams->GetConsensus(), dep)}; BOOST_CHECK(!(chain_all_vbits & dep_mask)); chain_all_vbits |= dep_mask; - check_computeblockversion(chainParams->GetConsensus(), dep); + check_computeblockversion(vbcache, chainParams->GetConsensus(), dep); } } @@ -439,7 +440,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) ArgsManager args; args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999"); // January 1, 2008 - December 31, 2008 const auto chainParams = CreateChainParams(args, CBaseChainParams::REGTEST); - check_computeblockversion(chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); + check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); } { @@ -449,7 +450,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) ArgsManager args; args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999:403200"); // January 1, 2008 - December 31, 2008, min act height 403200 const auto chainParams = CreateChainParams(args, CBaseChainParams::REGTEST); - check_computeblockversion(chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); + check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); } } diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index cb9a5fbf8b..992016b4f6 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -21,11 +21,11 @@ class CThreadInterrupt public: CThreadInterrupt(); explicit operator bool() const; - void operator()(); + void operator()() EXCLUSIVE_LOCKS_REQUIRED(!mut); void reset(); - bool sleep_for(std::chrono::milliseconds rel_time); - bool sleep_for(std::chrono::seconds rel_time); - bool sleep_for(std::chrono::minutes rel_time); + bool sleep_for(std::chrono::milliseconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); + bool sleep_for(std::chrono::seconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); + bool sleep_for(std::chrono::minutes rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); private: std::condition_variable cond; diff --git a/src/timedata.cpp b/src/timedata.cpp index 06659871e5..7faf22aba0 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -99,7 +99,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) } } - if (LogAcceptCategory(BCLog::NET)) { + if (LogAcceptCategory(BCLog::NET, BCLog::Level::Debug)) { std::string log_message{"time data samples: "}; for (const int64_t n : vSorted) { log_message += strprintf("%+d ", n); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 05dbc6057f..82b8529d76 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -94,7 +94,7 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) self->reply_handlers.front()(*self, self->message); self->reply_handlers.pop_front(); } else { - LogPrint(BCLog::TOR, "tor: Received unexpected sync reply %i\n", self->message.code); + LogPrint(BCLog::TOR, "Received unexpected sync reply %i\n", self->message.code); } } self->message.Clear(); @@ -113,13 +113,13 @@ void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ct { TorControlConnection *self = static_cast<TorControlConnection*>(ctx); if (what & BEV_EVENT_CONNECTED) { - LogPrint(BCLog::TOR, "tor: Successfully connected!\n"); + LogPrint(BCLog::TOR, "Successfully connected!\n"); self->connected(*self); } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { if (what & BEV_EVENT_ERROR) { - LogPrint(BCLog::TOR, "tor: Error connecting to Tor control socket\n"); + LogPrint(BCLog::TOR, "Error connecting to Tor control socket\n"); } else { - LogPrint(BCLog::TOR, "tor: End of stream\n"); + LogPrint(BCLog::TOR, "End of stream\n"); } self->Disconnect(); self->disconnected(*self); @@ -304,8 +304,7 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target): base(_base), - m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(nullptr), - reconnect_timeout(RECONNECT_TIMEOUT_START), + m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_timeout(RECONNECT_TIMEOUT_START), m_target(target) { reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); @@ -319,7 +318,7 @@ TorController::TorController(struct event_base* _base, const std::string& tor_co // Read service private key if cached std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); if (pkf.first) { - LogPrint(BCLog::TOR, "tor: Reading cached private key from %s\n", fs::PathToString(GetPrivateKeyFile())); + LogPrint(BCLog::TOR, "Reading cached private key from %s\n", fs::PathToString(GetPrivateKeyFile())); private_key = pkf.second; } } @@ -360,7 +359,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe } } if (!socks_location.empty()) { - LogPrint(BCLog::TOR, "tor: Get SOCKS port command yielded %s\n", socks_location); + LogPrint(BCLog::TOR, "Get SOCKS port command yielded %s\n", socks_location); } else { LogPrintf("tor: Get SOCKS port command returned nothing\n"); } @@ -381,7 +380,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe } Assume(resolved.IsValid()); - LogPrint(BCLog::TOR, "tor: Configuring onion proxy for %s\n", resolved.ToStringIPPort()); + LogPrint(BCLog::TOR, "Configuring onion proxy for %s\n", resolved.ToStringIPPort()); Proxy addrOnion = Proxy(resolved, true); SetProxy(NET_ONION, addrOnion); @@ -405,7 +404,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: ADD_ONION successful\n"); + LogPrint(BCLog::TOR, "ADD_ONION successful\n"); for (const std::string &s : reply.lines) { std::map<std::string,std::string> m = ParseTorReplyMapping(s); std::map<std::string,std::string>::iterator i; @@ -424,7 +423,7 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe service = LookupNumeric(std::string(service_id+".onion"), Params().GetDefaultPort()); LogPrintf("tor: Got service ID %s, advertising service %s\n", service_id, service.ToString()); if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { - LogPrint(BCLog::TOR, "tor: Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); + LogPrint(BCLog::TOR, "Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } else { LogPrintf("tor: Error writing service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } @@ -440,7 +439,7 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: Authentication successful\n"); + LogPrint(BCLog::TOR, "Authentication successful\n"); // Now that we know Tor is running setup the proxy for onion addresses // if -onion isn't set to something else. @@ -491,7 +490,7 @@ static std::vector<uint8_t> ComputeResponse(const std::string &key, const std::v void TorController::authchallenge_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: SAFECOOKIE authentication challenge successful\n"); + LogPrint(BCLog::TOR, "SAFECOOKIE authentication challenge successful\n"); std::pair<std::string,std::string> l = SplitTorReplyLine(reply.lines[0]); if (l.first == "AUTHCHALLENGE") { std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); @@ -501,7 +500,7 @@ void TorController::authchallenge_cb(TorControlConnection& _conn, const TorContr } std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); - LogPrint(BCLog::TOR, "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); + LogPrint(BCLog::TOR, "AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); if (serverNonce.size() != 32) { LogPrintf("tor: ServerNonce is not 32 bytes, as required by spec\n"); return; @@ -548,12 +547,12 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); std::map<std::string,std::string>::iterator i; if ((i = m.find("Tor")) != m.end()) { - LogPrint(BCLog::TOR, "tor: Connected to Tor version %s\n", i->second); + LogPrint(BCLog::TOR, "Connected to Tor version %s\n", i->second); } } } for (const std::string &s : methods) { - LogPrint(BCLog::TOR, "tor: Supported authentication method: %s\n", s); + LogPrint(BCLog::TOR, "Supported authentication method: %s\n", s); } // Prefer NULL, otherwise SAFECOOKIE. If a password is provided, use HASHEDPASSWORD /* Authentication: @@ -563,18 +562,18 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro std::string torpassword = gArgs.GetArg("-torpassword", ""); if (!torpassword.empty()) { if (methods.count("HASHEDPASSWORD")) { - LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); + LogPrint(BCLog::TOR, "Using HASHEDPASSWORD authentication\n"); ReplaceAll(torpassword, "\"", "\\\""); _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); } } else if (methods.count("NULL")) { - LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); + LogPrint(BCLog::TOR, "Using NULL authentication\n"); _conn.Command("AUTHENTICATE", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie - LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); + LogPrint(BCLog::TOR, "Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); std::pair<bool,std::string> status_cookie = ReadBinaryFile(fs::PathFromString(cookiefile), TOR_COOKIE_SIZE); if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); @@ -616,7 +615,7 @@ void TorController::disconnected_cb(TorControlConnection& _conn) if (!reconnect) return; - LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center); + LogPrint(BCLog::TOR, "Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center); // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); diff --git a/src/txdb.cpp b/src/txdb.cpp index afcd1985f5..a0939873ad 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -207,7 +207,7 @@ public: // cache warmup on instantiation. CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn): CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} - ~CCoinsViewDBCursor() {} + ~CCoinsViewDBCursor() = default; bool GetKey(COutPoint &key) const override; bool GetValue(Coin &coin) const override; diff --git a/src/univalue/README.md b/src/univalue/README.md index 7c62c33970..d622f5b1e0 100644 --- a/src/univalue/README.md +++ b/src/univalue/README.md @@ -15,7 +15,7 @@ This class is aligned with the JSON standard, [RFC ## Library usage This is a fork of univalue used by Bitcoin Core. It is not maintained for usage -by other projects. Notably, the API may break in non-backward-compatible ways. +by other projects. Notably, the API is broken in non-backward-compatible ways. Other projects looking for a maintained library should use the upstream univalue at https://github.com/jgarzik/univalue. diff --git a/src/univalue/TODO b/src/univalue/TODO deleted file mode 100644 index 5530048e92..0000000000 --- a/src/univalue/TODO +++ /dev/null @@ -1,10 +0,0 @@ - -Rearrange tree for easier 'git subtree' style use - -Move towards C++11 etc. - -Namespace support - must come up with useful shorthand, avoiding -long Univalue::Univalue::Univalue usages forced upon library users. - -Improve test suite - diff --git a/src/univalue/configure.ac b/src/univalue/configure.ac index 495b25a53d..ed9c5f0c5c 100644 --- a/src/univalue/configure.ac +++ b/src/univalue/configure.ac @@ -45,8 +45,8 @@ AC_SUBST(LIBUNIVALUE_AGE) LT_INIT LT_LANG([C++]) -dnl Require C++11 compiler (no GNU extensions) -AX_CXX_COMPILE_STDCXX([11], [noext], [mandatory], [nodefault]) +dnl Require C++17 compiler (no GNU extensions) +AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory], [nodefault]) case $host in *mingw*) diff --git a/src/univalue/gen/gen.cpp b/src/univalue/gen/gen.cpp index b8a6c73f4e..ca5b470ddc 100644 --- a/src/univalue/gen/gen.cpp +++ b/src/univalue/gen/gen.cpp @@ -8,9 +8,11 @@ // $ ./gen > univalue_escapes.h // -#include <stdio.h> -#include <string.h> -#include "univalue.h" +#include <univalue.h> + +#include <cstdio> +#include <cstring> +#include <string> static bool initEscapes; static std::string escapes[256]; diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index fc5cf402be..f0d4de2035 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -6,13 +6,14 @@ #ifndef __UNIVALUE_H__ #define __UNIVALUE_H__ -#include <stdint.h> -#include <string.h> - +#include <charconv> +#include <cstdint> +#include <cstring> +#include <map> +#include <stdexcept> #include <string> +#include <type_traits> #include <vector> -#include <map> -#include <cassert> class UniValue { public: @@ -82,66 +83,10 @@ public: bool isObject() const { return (typ == VOBJ); } bool push_back(const UniValue& val); - bool push_back(const std::string& val_) { - UniValue tmpVal(VSTR, val_); - return push_back(tmpVal); - } - bool push_back(const char *val_) { - std::string s(val_); - return push_back(s); - } - bool push_back(uint64_t val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(int64_t val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(bool val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(int val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(double val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } bool push_backV(const std::vector<UniValue>& vec); void __pushKV(const std::string& key, const UniValue& val); bool pushKV(const std::string& key, const UniValue& val); - bool pushKV(const std::string& key, const std::string& val_) { - UniValue tmpVal(VSTR, val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, const char *val_) { - std::string _val(val_); - return pushKV(key, _val); - } - bool pushKV(const std::string& key, int64_t val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, uint64_t val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, bool val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, int val_) { - UniValue tmpVal((int64_t)val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, double val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } bool pushKVs(const UniValue& obj); std::string write(unsigned int prettyIndent = 0, @@ -168,10 +113,22 @@ public: // value is of unexpected type const std::vector<std::string>& getKeys() const; const std::vector<UniValue>& getValues() const; + template <typename Int> + auto getInt() const + { + static_assert(std::is_integral<Int>::value); + if (typ != VNUM) { + throw std::runtime_error("JSON value is not an integer as expected"); + } + Int result; + const auto [first_nonmatching, error_condition] = std::from_chars(val.data(), val.data() + val.size(), result); + if (first_nonmatching != val.data() + val.size() || error_condition != std::errc{}) { + throw std::runtime_error("JSON integer out of range"); + } + return result; + } bool get_bool() const; const std::string& get_str() const; - int get_int() const; - int64_t get_int64() const; double get_real() const; const UniValue& get_obj() const; const UniValue& get_array() const; diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp index c4e59fae74..3553995c28 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -3,12 +3,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> +#include <univalue.h> + #include <iomanip> +#include <map> +#include <memory> #include <sstream> -#include <stdlib.h> - -#include "univalue.h" +#include <string> +#include <utility> +#include <vector> const UniValue NullUniValue; diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp index 5af89a3561..9bbdb1fe69 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -3,17 +3,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> -#include <errno.h> -#include <string.h> -#include <stdlib.h> -#include <stdexcept> -#include <vector> +#include <univalue.h> + +#include <cerrno> +#include <cstdint> +#include <cstdlib> +#include <cstring> #include <limits> -#include <string> +#include <locale> #include <sstream> - -#include "univalue.h" +#include <stdexcept> +#include <string> +#include <vector> namespace { @@ -28,37 +29,6 @@ static bool ParsePrechecks(const std::string& str) return true; } -bool ParseInt32(const std::string& str, int32_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = nullptr; - errno = 0; // strtol will not set errno if valid - long int n = strtol(str.c_str(), &endp, 10); - if(out) *out = (int32_t)n; - // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow - // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit - // platforms the size of these types may be different. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits<int32_t>::min() && - n <= std::numeric_limits<int32_t>::max(); -} - -bool ParseInt64(const std::string& str, int64_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = nullptr; - errno = 0; // strtoll will not set errno if valid - long long int n = strtoll(str.c_str(), &endp, 10); - if(out) *out = (int64_t)n; - // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow - // we still have to check that the returned value is within the range of an *int64_t*. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits<int64_t>::min() && - n <= std::numeric_limits<int64_t>::max(); -} - bool ParseDouble(const std::string& str, double *out) { if (!ParsePrechecks(str)) @@ -102,26 +72,6 @@ const std::string& UniValue::get_str() const return getValStr(); } -int UniValue::get_int() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int32_t retval; - if (!ParseInt32(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -int64_t UniValue::get_int64() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int64_t retval; - if (!ParseInt64(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - double UniValue::get_real() const { if (typ != VNUM) diff --git a/src/univalue/lib/univalue_read.cpp b/src/univalue/lib/univalue_read.cpp index be39bfe57a..a6ed75e57a 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -2,19 +2,22 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <string.h> -#include <vector> -#include <stdio.h> -#include "univalue.h" +#include <univalue.h> #include "univalue_utffilter.h" +#include <cstdio> +#include <cstdint> +#include <cstring> +#include <string> +#include <vector> + /* * According to stackexchange, the original json test suite wanted * to limit depth to 22. Widely-deployed PHP bails at depth 512, * so we will follow PHP's lead, which should be more than sufficient * (further stackexchange comments indicate depth > 32 rarely occurs). */ -static const size_t MAX_JSON_DEPTH = 512; +static constexpr size_t MAX_JSON_DEPTH = 512; static bool json_isdigit(int ch) { diff --git a/src/univalue/lib/univalue_write.cpp b/src/univalue/lib/univalue_write.cpp index 3a2c580c7f..18833077b7 100644 --- a/src/univalue/lib/univalue_write.cpp +++ b/src/univalue/lib/univalue_write.cpp @@ -2,11 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <iomanip> -#include <stdio.h> -#include "univalue.h" +#include <univalue.h> #include "univalue_escapes.h" +#include <memory> +#include <string> +#include <vector> + static std::string json_escape(const std::string& inS) { std::string outS; diff --git a/src/univalue/test/no_nul.cpp b/src/univalue/test/no_nul.cpp index 83d292200b..3a7a727abb 100644 --- a/src/univalue/test/no_nul.cpp +++ b/src/univalue/test/no_nul.cpp @@ -1,4 +1,4 @@ -#include "univalue.h" +#include <univalue.h> int main (int argc, char *argv[]) { diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp index c2f52f83ac..8a35bf914d 100644 --- a/src/univalue/test/object.cpp +++ b/src/univalue/test/object.cpp @@ -3,13 +3,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> -#include <vector> -#include <string> -#include <map> +#include <univalue.h> + #include <cassert> +#include <cstdint> +#include <map> +#include <memory> #include <stdexcept> -#include <univalue.h> +#include <string> +#include <vector> #define BOOST_FIXTURE_TEST_SUITE(a, b) #define BOOST_AUTO_TEST_CASE(funcName) void funcName() @@ -90,23 +92,30 @@ BOOST_AUTO_TEST_CASE(univalue_typecheck) BOOST_CHECK(v1.isNum()); BOOST_CHECK_THROW(v1.get_bool(), std::runtime_error); + { + UniValue v_negative; + BOOST_CHECK(v_negative.setNumStr("-1")); + BOOST_CHECK_THROW(v_negative.getInt<uint8_t>(), std::runtime_error); + BOOST_CHECK_EQUAL(v_negative.getInt<int8_t>(), -1); + } + UniValue v2; BOOST_CHECK(v2.setBool(true)); BOOST_CHECK_EQUAL(v2.get_bool(), true); - BOOST_CHECK_THROW(v2.get_int(), std::runtime_error); + BOOST_CHECK_THROW(v2.getInt<int>(), std::runtime_error); UniValue v3; BOOST_CHECK(v3.setNumStr("32482348723847471234")); - BOOST_CHECK_THROW(v3.get_int64(), std::runtime_error); + BOOST_CHECK_THROW(v3.getInt<int64_t>(), std::runtime_error); BOOST_CHECK(v3.setNumStr("1000")); - BOOST_CHECK_EQUAL(v3.get_int64(), 1000); + BOOST_CHECK_EQUAL(v3.getInt<int64_t>(), 1000); UniValue v4; BOOST_CHECK(v4.setNumStr("2147483648")); - BOOST_CHECK_EQUAL(v4.get_int64(), 2147483648); - BOOST_CHECK_THROW(v4.get_int(), std::runtime_error); + BOOST_CHECK_EQUAL(v4.getInt<int64_t>(), 2147483648); + BOOST_CHECK_THROW(v4.getInt<int>(), std::runtime_error); BOOST_CHECK(v4.setNumStr("1000")); - BOOST_CHECK_EQUAL(v4.get_int(), 1000); + BOOST_CHECK_EQUAL(v4.getInt<int>(), 1000); BOOST_CHECK_THROW(v4.get_str(), std::runtime_error); BOOST_CHECK_EQUAL(v4.get_real(), 1000); BOOST_CHECK_THROW(v4.get_array(), std::runtime_error); @@ -118,10 +127,10 @@ BOOST_AUTO_TEST_CASE(univalue_typecheck) BOOST_CHECK(v5.read("[true, 10]")); BOOST_CHECK_NO_THROW(v5.get_array()); std::vector<UniValue> vals = v5.getValues(); - BOOST_CHECK_THROW(vals[0].get_int(), std::runtime_error); + BOOST_CHECK_THROW(vals[0].getInt<int>(), std::runtime_error); BOOST_CHECK_EQUAL(vals[0].get_bool(), true); - BOOST_CHECK_EQUAL(vals[1].get_int(), 10); + BOOST_CHECK_EQUAL(vals[1].getInt<int>(), 10); BOOST_CHECK_THROW(vals[1].get_bool(), std::runtime_error); } diff --git a/src/univalue/test/test_json.cpp b/src/univalue/test/test_json.cpp index 2943bae2b1..f8c10238d4 100644 --- a/src/univalue/test/test_json.cpp +++ b/src/univalue/test/test_json.cpp @@ -4,9 +4,11 @@ // It reads JSON input from stdin and exits with code 0 if it can be parsed // successfully. It also pretty prints the parsed JSON value to stdout. +#include <univalue.h> + #include <iostream> +#include <iterator> #include <string> -#include "univalue.h" using namespace std; diff --git a/src/univalue/test/unitester.cpp b/src/univalue/test/unitester.cpp index 02e1a83c6d..81b1c5d3b1 100644 --- a/src/univalue/test/unitester.cpp +++ b/src/univalue/test/unitester.cpp @@ -2,12 +2,11 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdlib.h> -#include <stdio.h> -#include <string.h> +#include <univalue.h> + #include <cassert> +#include <cstdio> #include <string> -#include "univalue.h" #ifndef JSON_TEST_SRC #error JSON_TEST_SRC must point to test source directory diff --git a/src/util/bip32.h b/src/util/bip32.h index 8f86f2aaa6..aa4eac3791 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_UTIL_BIP32_H #define BITCOIN_UTIL_BIP32_H -#include <attributes.h> #include <string> #include <vector> diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp index bc060a44c9..9054db4759 100644 --- a/src/util/bytevectorhash.cpp +++ b/src/util/bytevectorhash.cpp @@ -6,10 +6,10 @@ #include <random.h> #include <util/bytevectorhash.h> -ByteVectorHash::ByteVectorHash() +ByteVectorHash::ByteVectorHash() : + m_k0(GetRand<uint64_t>()), + m_k1(GetRand<uint64_t>()) { - GetRandBytes({reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)}); - GetRandBytes({reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)}); } size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const diff --git a/src/util/designator.h b/src/util/designator.h new file mode 100644 index 0000000000..3670b11e00 --- /dev/null +++ b/src/util/designator.h @@ -0,0 +1,21 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_DESIGNATOR_H +#define BITCOIN_UTIL_DESIGNATOR_H + +/** + * Designated initializers can be used to avoid ordering mishaps in aggregate + * initialization. However, they do not prevent uninitialized members. The + * checks can be disabled by defining DISABLE_DESIGNATED_INITIALIZER_ERRORS. + * This should only be needed on MSVC 2019. MSVC 2022 supports them with the + * option "/std:c++20" + */ +#ifndef DISABLE_DESIGNATED_INITIALIZER_ERRORS +#define Desig(field_name) .field_name = +#else +#define Desig(field_name) +#endif + +#endif // BITCOIN_UTIL_DESIGNATOR_H diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index 5900daf050..c21941eb88 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -7,11 +7,11 @@ #include <limits> -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} -SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} -SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand(std::numeric_limits<uint64_t>::max())), m_k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand<uint64_t>()), m_k1(GetRand<uint64_t>()) {} size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const { diff --git a/src/util/moneystr.h b/src/util/moneystr.h index 8180604342..3d33bd7f99 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_MONEYSTR_H #define BITCOIN_UTIL_MONEYSTR_H -#include <attributes.h> #include <consensus/amount.h> #include <optional> diff --git a/src/util/settings.cpp b/src/util/settings.cpp index 26439b010b..924a9cfab2 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -127,6 +127,7 @@ SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name) { SettingsValue result; @@ -162,6 +163,9 @@ SettingsValue GetSetting(const Settings& settings, return; } + // Ignore nonpersistent settings if requested. + if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return; + // Skip negated command line settings. if (skip_negated_command_line && span.last_negated()) return; diff --git a/src/util/settings.h b/src/util/settings.h index ed36349232..e97158dc09 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -20,7 +20,7 @@ namespace util { //! @note UniValue is used here for convenience and because it can be easily //! serialized in a readable format. But any other variant type that can //! be assigned strings, int64_t, and bool values and has get_str(), -//! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and +//! getInt<int64_t>(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and //! isNull() methods can be substituted if there's a need to move away //! from UniValue. (An implementation with boost::variant was posted at //! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) @@ -55,12 +55,18 @@ bool WriteSettings(const fs::path& path, //! @param ignore_default_section_config - ignore values in the default section //! of the config file (part before any //! [section] keywords) +//! @param ignore_nonpersistent - ignore non-persistent settings values (forced +//! settings values and values specified on the +//! command line). Only return settings in the +//! read-only config and read-write settings +//! files. //! @param get_chain_name - enable special backwards compatible behavior //! for GetChainName SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name); //! Get combined setting value similar to GetSetting(), except if setting was diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index bcedd4f517..675fe7d2d7 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -77,10 +77,10 @@ bool IsHexNumber(std::string_view str) return str.size() > 0; } -std::vector<unsigned char> ParseHex(std::string_view str) +template <typename Byte> +std::vector<Byte> ParseHex(std::string_view str) { - // convert hex dump to vector - std::vector<unsigned char> vch; + std::vector<Byte> vch; auto it = str.begin(); while (it != str.end() && it + 1 != str.end()) { if (IsSpace(*it)) { @@ -90,10 +90,12 @@ std::vector<unsigned char> ParseHex(std::string_view str) auto c1 = HexDigit(*(it++)); auto c2 = HexDigit(*(it++)); if (c1 < 0 || c2 < 0) break; - vch.push_back(uint8_t(c1 << 4) | c2); + vch.push_back(Byte(c1 << 4) | Byte(c2)); } return vch; } +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) { diff --git a/src/util/strencodings.h b/src/util/strencodings.h index ebb6d88952..9a96bbe67b 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_STRENCODINGS_H #define BITCOIN_UTIL_STRENCODINGS_H -#include <attributes.h> #include <span.h> #include <util/string.h> @@ -55,7 +54,9 @@ enum class ByteUnit : uint64_t { * @return A new string without unsafe chars */ std::string SanitizeString(std::string_view str, int rule = SAFE_CHARS_DEFAULT); -std::vector<unsigned char> ParseHex(std::string_view str); +/** Parse the hex string into bytes (uint8_t or std::byte). Ignores whitespace. */ +template <typename Byte = uint8_t> +std::vector<Byte> ParseHex(std::string_view str); signed char HexDigit(char c); /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ diff --git a/src/util/system.cpp b/src/util/system.cpp index 44ebf5cb3e..f88b0fac77 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -236,7 +236,7 @@ KeyInfo InterpretKey(std::string key) * @return parsed settings value if it is valid, otherwise nullopt accompanied * by a descriptive error string */ -static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string& value, +static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string* value, unsigned int flags, std::string& error) { // Return negated settings as false values. @@ -246,20 +246,24 @@ static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, con return std::nullopt; } // Double negatives like -nofoo=0 are supported (but discouraged) - if (!InterpretBool(value)) { - LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, value); + if (value && !InterpretBool(*value)) { + LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, *value); return true; } return false; } - return value; + if (!value && (flags & ArgsManager::DISALLOW_ELISION)) { + error = strprintf("Can not set -%s with no value. Please specify value with -%s=value.", key.name, key.name); + return std::nullopt; + } + return value ? *value : ""; } // Define default constructor and destructor that are not inline, so code instantiating this class doesn't need to // #include class definitions for all members. // For example, m_settings has an internal dependency on univalue. -ArgsManager::ArgsManager() {} -ArgsManager::~ArgsManager() {} +ArgsManager::ArgsManager() = default; +ArgsManager::~ArgsManager() = default; const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { @@ -320,7 +324,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin #endif if (key == "-") break; //bitcoin-tx using stdin - std::string val; + std::optional<std::string> val; size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); @@ -366,7 +370,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return false; } - std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error); if (!value) return false; m_settings.command_line_options[keyinfo.name].push_back(*value); @@ -526,12 +530,15 @@ bool ArgsManager::InitSettings(std::string& error) return true; } -bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const +bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const { fs::path settings = GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME}); if (settings.empty()) { return false; } + if (backup) { + settings += ".bak"; + } if (filepath) { *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings); } @@ -572,10 +579,10 @@ bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors) return true; } -bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const +bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors, bool backup) const { fs::path path, path_tmp; - if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { + if (!GetSettingsPath(&path, /*temp=*/false, backup) || !GetSettingsPath(&path_tmp, /*temp=*/true, backup)) { throw std::logic_error("Attempt to write settings file when dynamic settings are disabled."); } @@ -592,6 +599,13 @@ bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const return true; } +util::SettingsValue ArgsManager::GetPersistentSetting(const std::string& name) const +{ + LOCK(cs_args); + return util::GetSetting(m_settings, m_network, name, !UseDefaultSection("-" + name), + /*ignore_nonpersistent=*/true, /*get_chain_name=*/false); +} + bool ArgsManager::IsArgNegated(const std::string& strArg) const { return GetSetting(strArg).isFalse(); @@ -600,18 +614,33 @@ bool ArgsManager::IsArgNegated(const std::string& strArg) const std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToString(value, strDefault); +} + +std::string SettingToString(const util::SettingsValue& value, const std::string& strDefault) +{ return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.isNum() ? value.getValStr() : value.get_str(); } int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const { const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : LocaleIndependentAtoi<int64_t>(value.get_str()); + return SettingToInt(value, nDefault); +} + +int64_t SettingToInt(const util::SettingsValue& value, int64_t nDefault) +{ + return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.getInt<int64_t>() : LocaleIndependentAtoi<int64_t>(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToBool(value, fDefault); +} + +bool SettingToBool(const util::SettingsValue& value, bool fDefault) +{ return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); } @@ -887,7 +916,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file KeyInfo key = InterpretKey(option.first); std::optional<unsigned int> flags = GetArgFlags('-' + key.name); if (flags) { - std::optional<util::SettingsValue> value = InterpretValue(key, option.second, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(key, &option.second, *flags, error); if (!value) { return false; } @@ -1002,6 +1031,7 @@ std::string ArgsManager::GetChainName() const LOCK(cs_args); util::SettingsValue value = util::GetSetting(m_settings, /* section= */ "", SettingName(arg), /* ignore_default_section_config= */ false, + /*ignore_nonpersistent=*/false, /* get_chain_name= */ true); return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); }; @@ -1034,7 +1064,8 @@ util::SettingsValue ArgsManager::GetSetting(const std::string& arg) const { LOCK(cs_args); return util::GetSetting( - m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), /* get_chain_name= */ false); + m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false); } std::vector<util::SettingsValue> ArgsManager::GetSettingsList(const std::string& arg) const diff --git a/src/util/system.h b/src/util/system.h index a7f4d16911..07d7a533aa 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,7 +14,6 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> #include <compat.h> #include <compat/assumptions.h> #include <fs.h> @@ -161,6 +160,10 @@ struct SectionInfo int m_line; }; +std::string SettingToString(const util::SettingsValue&, const std::string&); +int64_t SettingToInt(const util::SettingsValue&, int64_t); +bool SettingToBool(const util::SettingsValue&, bool); + class ArgsManager { public: @@ -175,6 +178,7 @@ public: // ALLOW_STRING = 0x08, //!< unimplemented, draft implementation in #16545 // ALLOW_LIST = 0x10, //!< unimplemented, draft implementation in #16545 DISALLOW_NEGATION = 0x20, //!< disallow -nofoo syntax + DISALLOW_ELISION = 0x40, //!< disallow -foo syntax that doesn't assign any value DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for @@ -436,7 +440,7 @@ protected: * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ - bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false) const; + bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false, bool backup = false) const; /** * Read settings file. Push errors to vector, or log them if null. @@ -444,9 +448,16 @@ protected: bool ReadSettingsFile(std::vector<std::string>* errors = nullptr); /** - * Write settings file. Push errors to vector, or log them if null. + * Write settings file or backup settings file. Push errors to vector, or + * log them if null. + */ + bool WriteSettingsFile(std::vector<std::string>* errors = nullptr, bool backup = false) const; + + /** + * Get current setting from config file or read/write settings file, + * ignoring nonpersistent command line or forced settings values. */ - bool WriteSettingsFile(std::vector<std::string>* errors = nullptr) const; + util::SettingsValue GetPersistentSetting(const std::string& name) const; /** * Access settings with lock held. diff --git a/src/util/time.cpp b/src/util/time.cpp index e428430bac..7d9d6bcff1 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -66,20 +66,16 @@ bool ChronoSanityCheck() return true; } -template <typename T> -T GetTime() +NodeClock::time_point NodeClock::now() noexcept { const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)}; const auto ret{ mocktime.count() ? mocktime : - std::chrono::duration_cast<T>(std::chrono::system_clock::now().time_since_epoch())}; + std::chrono::system_clock::now().time_since_epoch()}; assert(ret > 0s); - return ret; -} -template std::chrono::seconds GetTime(); -template std::chrono::milliseconds GetTime(); -template std::chrono::microseconds GetTime(); + return time_point{ret}; +}; template <typename T> static T GetSystemTime() @@ -115,11 +111,6 @@ int64_t GetTimeMicros() return int64_t{GetSystemTime<std::chrono::microseconds>().count()}; } -int64_t GetTimeSeconds() -{ - return int64_t{GetSystemTime<std::chrono::seconds>().count()}; -} - int64_t GetTime() { return GetTime<std::chrono::seconds>().count(); } std::string FormatISO8601DateTime(int64_t nTime) { diff --git a/src/util/time.h b/src/util/time.h index 9d92b23725..ad91a72860 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -14,18 +14,37 @@ using namespace std::chrono_literals; +/** Mockable clock in the context of tests, otherwise the system clock */ +struct NodeClock : public std::chrono::system_clock { + using time_point = std::chrono::time_point<NodeClock>; + /** Return current system time or mocked time, if set */ + static time_point now() noexcept; + static std::time_t to_time_t(const time_point&) = delete; // unused + static time_point from_time_t(std::time_t) = delete; // unused +}; +using NodeSeconds = std::chrono::time_point<NodeClock, std::chrono::seconds>; + +using SteadySeconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::seconds>; +using SteadyMilliseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::milliseconds>; +using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>; + void UninterruptibleSleep(const std::chrono::microseconds& n); /** - * Helper to count the seconds of a duration. + * Helper to count the seconds of a duration/time_point. * - * All durations should be using std::chrono and calling this should generally + * All durations/time_points should be using std::chrono and calling this should generally * be avoided in code. Though, it is still preferred to an inline t.count() to * protect against a reliance on the exact type of t. * - * This helper is used to convert durations before passing them over an + * This helper is used to convert durations/time_points before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ +template <typename Duration, typename Timepoint> +constexpr auto TicksSinceEpoch(Timepoint t) +{ + return std::chrono::time_point_cast<Duration>(t).time_since_epoch().count(); +} constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } @@ -39,7 +58,11 @@ inline double CountSecondsDouble(SecondsDouble t) { return t.count(); } /** * DEPRECATED - * Use either GetTimeSeconds (not mockable) or GetTime<T> (mockable) + * Use either ClockType::now() or Now<TimePointType>() if a cast is needed. + * ClockType is + * - std::chrono::steady_clock for steady time + * - std::chrono::system_clock for system time + * - NodeClock for mockable system time */ int64_t GetTime(); @@ -47,8 +70,6 @@ int64_t GetTime(); int64_t GetTimeMillis(); /** Returns the system time (not mockable) */ int64_t GetTimeMicros(); -/** Returns the system time (not mockable) */ -int64_t GetTimeSeconds(); // Like GetTime(), but not mockable /** * DEPRECATED @@ -64,9 +85,21 @@ void SetMockTime(std::chrono::seconds mock_time_in); /** For testing */ std::chrono::seconds GetMockTime(); -/** Return system time (or mocked time, if set) */ +/** + * Return the current time point cast to the given precision. Only use this + * when an exact precision is needed, otherwise use T::clock::now() directly. + */ +template <typename T> +T Now() +{ + return std::chrono::time_point_cast<typename T::duration>(T::clock::now()); +} +/** DEPRECATED, see GetTime */ template <typename T> -T GetTime(); +T GetTime() +{ + return Now<std::chrono::time_point<NodeClock, T>>().time_since_epoch(); +} /** * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date} diff --git a/src/validation.cpp b/src/validation.cpp index b5d6a66088..23ad221ffe 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -16,13 +16,12 @@ #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <cuckoocache.h> -#include <deploymentstatus.h> #include <flatfile.h> #include <hash.h> +#include <kernel/coinstats.h> #include <logging.h> #include <logging/timer.h> #include <node/blockstorage.h> -#include <node/coinstats.h> #include <node/ui_interface.h> #include <node/utxo_snapshot.h> #include <policy/policy.h> @@ -37,7 +36,6 @@ #include <script/sigcache.h> #include <shutdown.h> #include <signet.h> -#include <timedata.h> #include <tinyformat.h> #include <txdb.h> #include <txmempool.h> @@ -60,17 +58,18 @@ #include <optional> #include <string> +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; +using kernel::ComputeUTXOStats; + using node::BLOCKFILE_CHUNK_SIZE; using node::BlockManager; using node::BlockMap; using node::CBlockIndexHeightOnlyComparator; using node::CBlockIndexWorkComparator; -using node::CCoinsStats; -using node::CoinStatsHashType; using node::fImporting; using node::fPruneMode; using node::fReindex; -using node::GetUTXOStats; using node::nPruneTarget; using node::OpenBlockFile; using node::ReadBlockFromDisk; @@ -135,8 +134,6 @@ int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; uint256 hashAssumeValid; arith_uint256 nMinimumChainWork; -CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); - const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const { AssertLockHeld(cs_main); @@ -262,7 +259,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, } // Returns the script flags which should be checked for a given block -static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Consensus::Params& chainparams); +static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const ChainstateManager& chainman); static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, size_t limit, std::chrono::seconds age) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) @@ -815,7 +812,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return false; // state filled in by CheckTxInputs } - // Check for non-standard pay-to-script-hash in inputs if (fRequireStandard && !AreInputsStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); } @@ -1027,7 +1023,6 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) const CTransaction& tx = *ws.m_ptx; const uint256& hash = ws.m_hash; TxValidationState& state = ws.m_state; - const CChainParams& chainparams = args.m_chainparams; // Check again against the current block tip's script verification // flags to cache our script execution flags. This is, of course, @@ -1044,7 +1039,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - unsigned int currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus())}; + unsigned int currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), m_active_chainstate.m_chainman)}; if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, ws.m_precomputed_txdata, m_active_chainstate.CoinsTip())) { LogPrintf("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s\n", hash.ToString(), state.ToString()); @@ -1457,7 +1452,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); std::vector<COutPoint> coins_to_uncache; - const CChainParams& chainparams = Params(); + const CChainParams& chainparams = active_chainstate.m_params; const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (test_accept) { @@ -1515,7 +1510,7 @@ CChainState::CChainState( std::optional<uint256> from_snapshot_blockhash) : m_mempool(mempool), m_blockman(blockman), - m_params(::Params()), + m_params(chainman.GetParams()), m_chainman(chainman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -1909,10 +1904,11 @@ void StopScriptCheckWorkerThreads() class WarningBitsConditionChecker : public AbstractThresholdConditionChecker { private: - int bit; + const ChainstateManager& m_chainman; + int m_bit; public: - explicit WarningBitsConditionChecker(int bitIn) : bit(bitIn) {} + explicit WarningBitsConditionChecker(const ChainstateManager& chainman, int bit) : m_chainman{chainman}, m_bit(bit) {} int64_t BeginTime(const Consensus::Params& params) const override { return 0; } int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits<int64_t>::max(); } @@ -1923,15 +1919,17 @@ public: { return pindex->nHeight >= params.MinBIP9WarningHeight && ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && - ((pindex->nVersion >> bit) & 1) != 0 && - ((g_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> bit) & 1) == 0; + ((pindex->nVersion >> m_bit) & 1) != 0 && + ((m_chainman.m_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> m_bit) & 1) == 0; } }; static std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> warningcache GUARDED_BY(cs_main); -static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Consensus::Params& consensusparams) +static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const ChainstateManager& chainman) { + const Consensus::Params& consensusparams = chainman.GetConsensus(); + // BIP16 didn't become active until Apr 1 2012 (on mainnet, and // retroactively applied to testnet) // However, only one historical block violated the P2SH rules (on both @@ -1947,22 +1945,22 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Co } // Enforce the DERSIG (BIP66) rule - if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_DERSIG)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_DERSIG)) { flags |= SCRIPT_VERIFY_DERSIG; } // Enforce CHECKLOCKTIMEVERIFY (BIP65) - if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_CLTV)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CLTV)) { flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; } // Enforce CHECKSEQUENCEVERIFY (BIP112) - if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CSV)) { flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; } // Enforce BIP147 NULLDUMMY (activated simultaneously with segwit) - if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_SEGWIT)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } @@ -2005,7 +2003,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Also, currently the rule against blocks more than 2 hours in the future // is enforced in ContextualCheckBlockHeader(); we wouldn't want to // re-enforce that rule here (at least until we make it impossible for - // GetAdjustedTime() to go backward). + // m_adjusted_time_callback() to go backward). if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) { if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been @@ -2153,12 +2151,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Enforce BIP68 (sequence locks) int nLockTimeFlags = 0; - if (DeploymentActiveAt(*pindex, m_params.GetConsensus(), Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) { nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; } // Get the script flags for this block - unsigned int flags{GetBlockScriptFlags(*pindex, m_params.GetConsensus())}; + unsigned int flags{GetBlockScriptFlags(*pindex, m_chainman)}; int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1; LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal); @@ -2454,9 +2452,9 @@ bool CChainState::FlushStateToDisk( full_flush_completed = true; TRACE5(utxocache, flush, (int64_t)(GetTimeMicros() - nNow.count()), // in microseconds (µs) - (u_int32_t)mode, - (u_int64_t)coins_count, - (u_int64_t)coins_mem_usage, + (uint32_t)mode, + (uint64_t)coins_count, + (uint64_t)coins_mem_usage, (bool)fFlushForPrune); } } @@ -2556,7 +2554,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) if (!this->IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { - WarningBitsConditionChecker checker(bit); + WarningBitsConditionChecker checker(m_chainman, bit); ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache.at(bit)); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); @@ -2653,7 +2651,7 @@ static int64_t nTimePostConnect = 0; struct PerBlockConnectTrace { CBlockIndex* pindex = nullptr; std::shared_ptr<const CBlock> pblock; - PerBlockConnectTrace() {} + PerBlockConnectTrace() = default; }; /** * Used to track blocks whose transactions were applied to the UTXO state as a @@ -3280,7 +3278,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi pindexNew->nDataPos = pos.nPos; pindexNew->nUndoPos = 0; pindexNew->nStatus |= BLOCK_HAVE_DATA; - if (DeploymentActiveAt(*pindexNew, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(*pindexNew, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { pindexNew->nStatus |= BLOCK_OPT_WITNESS; } pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); @@ -3398,11 +3396,11 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu return true; } -void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) +void ChainstateManager::UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const { int commitpos = GetWitnessCommitmentIndex(block); static const std::vector<unsigned char> nonce(32, 0x00); - if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) { + if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, *this, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) { CMutableTransaction tx(*block.vtx[0]); tx.vin[0].scriptWitness.stack.resize(1); tx.vin[0].scriptWitness.stack[0] = nonce; @@ -3410,7 +3408,7 @@ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPr } } -std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) +std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const { std::vector<unsigned char> commitment; int commitpos = GetWitnessCommitmentIndex(block); @@ -3433,7 +3431,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc tx.vout.push_back(out); block.vtx[0] = MakeTransactionRef(std::move(tx)); } - UpdateUncommittedBlockStructures(block, pindexPrev, consensusParams); + UpdateUncommittedBlockStructures(block, pindexPrev); return commitment; } @@ -3446,14 +3444,14 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; // Check proof of work - const Consensus::Params& consensusParams = params.GetConsensus(); + const Consensus::Params& consensusParams = chainman.GetConsensus(); if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); @@ -3462,7 +3460,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). - const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints()); + const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(chainman.GetParams().Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint"); @@ -3478,9 +3476,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); // Reject blocks with outdated version - if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) || - (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_DERSIG)) || - (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CLTV))) { + if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) || + (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_DERSIG)) || + (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CLTV))) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); } @@ -3494,13 +3492,13 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) +static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev) { const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; // Enforce BIP113 (Median Time Past). int nLockTimeFlags = 0; - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CSV)) { assert(pindexPrev != nullptr); nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; } @@ -3517,7 +3515,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat } // Enforce rule that the coinbase starts with serialized block height - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) { CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || @@ -3535,7 +3533,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are // multiple, the last one is used. bool fHaveWitness = false; - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT)) { int commitpos = GetWitnessCommitmentIndex(block); if (commitpos != NO_WITNESS_COMMITMENT) { bool malleated = false; @@ -3576,13 +3574,13 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return true; } -bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; - if (hash != chainparams.GetConsensus().hashGenesisBlock) { + if (hash != GetConsensus().hashGenesisBlock) { if (miSelf != m_blockman.m_block_index.end()) { // Block header is already known. CBlockIndex* pindex = &(miSelf->second); @@ -3595,7 +3593,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida return true; } - if (!CheckBlockHeader(block, state, chainparams.GetConsensus())) { + if (!CheckBlockHeader(block, state, GetConsensus())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -3612,7 +3610,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString()); return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); } - if (!ContextualCheckBlockHeader(block, state, m_blockman, chainparams, pindexPrev, GetAdjustedTime())) { + if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_adjusted_time_callback())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -3665,14 +3663,14 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } // Exposed wrapper for AcceptBlockHeader -bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) +bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CBlockIndex** ppindex) { AssertLockNotHeld(cs_main); { LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - bool accepted{AcceptBlockHeader(header, state, chainparams, &pindex)}; + bool accepted{AcceptBlockHeader(header, state, &pindex)}; ActiveChainstate().CheckBlockIndex(); if (!accepted) { @@ -3686,7 +3684,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& if (NotifyHeaderTip(ActiveChainstate())) { if (ActiveChainstate().IsInitialBlockDownload() && ppindex && *ppindex) { const CBlockIndex& last_accepted{**ppindex}; - const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / chainparams.GetConsensus().nPowTargetSpacing}; + const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / GetConsensus().nPowTargetSpacing}; const double progress{100.0 * last_accepted.nHeight / (last_accepted.nHeight + blocks_left)}; LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", last_accepted.nHeight, progress); } @@ -3705,7 +3703,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - bool accepted_header{m_chainman.AcceptBlockHeader(block, state, m_params, &pindex)}; + bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)}; CheckBlockIndex(); if (!accepted_header) @@ -3745,7 +3743,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block } if (!CheckBlock(block, state, m_params.GetConsensus()) || - !ContextualCheckBlock(block, state, m_params.GetConsensus(), pindex->pprev)) { + !ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_blockman.m_dirty_blockindex.insert(pindex); @@ -3778,7 +3776,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block return true; } -bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) +bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) { AssertLockNotHeld(cs_main); @@ -3796,7 +3794,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s // malleability that cause CheckBlock() to fail; see e.g. CVE-2012-2459 and // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html. Because CheckBlock() is // not very expensive, the anti-DoS benefits of caching failure (of a definitely-invalid block) are not substantial. - bool ret = CheckBlock(*block, state, chainparams.GetConsensus()); + bool ret = CheckBlock(*block, state, GetConsensus()); if (ret) { // Store to disk ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block); @@ -3836,6 +3834,7 @@ bool TestBlockValidity(BlockValidationState& state, CChainState& chainstate, const CBlock& block, CBlockIndex* pindexPrev, + const std::function<int64_t()>& adjusted_time_callback, bool fCheckPOW, bool fCheckMerkleRoot) { @@ -3849,11 +3848,11 @@ bool TestBlockValidity(BlockValidationState& state, indexDummy.phashBlock = &block_hash; // NOTE: CheckBlockHeader is called by CheckBlock - if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) + if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev, adjusted_time_callback())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); - if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) + if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString()); if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) { return false; @@ -4133,7 +4132,7 @@ bool CChainState::NeedsRedownload() const // At and above m_params.SegwitHeight, segwit consensus rules must be validated CBlockIndex* block{m_chain.Tip()}; - while (block != nullptr && DeploymentActiveAt(*block, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + while (block != nullptr && DeploymentActiveAt(*block, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { if (!(block->nStatus & BLOCK_OPT_WITNESS)) { // block is insufficiently validated for a segwit client return true; @@ -4157,7 +4156,7 @@ bool ChainstateManager::LoadBlockIndex() // Load block index from databases bool needs_init = fReindex; if (!fReindex) { - bool ret = m_blockman.LoadBlockIndexDB(); + bool ret = m_blockman.LoadBlockIndexDB(GetConsensus()); if (!ret) return false; std::vector<CBlockIndex*> vSortedByHeight{m_blockman.GetAllBlockIndices()}; @@ -4986,14 +4985,15 @@ bool ChainstateManager::PopulateAndValidateSnapshot( CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return m_blockman.LookupBlockIndex(base_blockhash)); if (!snapshot_start_block) { - // Needed for GetUTXOStats and ExpectedAssumeutxo to determine the height and to avoid a crash when base_blockhash.IsNull() + // Needed for ComputeUTXOStats and ExpectedAssumeutxo to determine the + // height and to avoid a crash when base_blockhash.IsNull() LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n", base_blockhash.ToString()); return false; } int base_height = snapshot_start_block->nHeight; - auto maybe_au_data = ExpectedAssumeutxo(base_height, ::Params()); + auto maybe_au_data = ExpectedAssumeutxo(base_height, GetParams()); if (!maybe_au_data) { LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized " /* Continued */ @@ -5094,22 +5094,22 @@ bool ChainstateManager::PopulateAndValidateSnapshot( assert(coins_cache.GetBestBlock() == base_blockhash); - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ }; // As above, okay to immediately release cs_main here since no other context knows // about the snapshot_chainstate. CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB()); - if (!GetUTXOStats(snapshot_coinsdb, m_blockman, stats, breakpoint_fnc)) { + const std::optional<CCoinsStats> maybe_stats = ComputeUTXOStats(CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, breakpoint_fnc); + if (!maybe_stats.has_value()) { LogPrintf("[snapshot] failed to generate coins stats\n"); return false; } // Assert that the deserialized chainstate contents match the expected assumeutxo value. - if (AssumeutxoHash{stats.hashSerialized} != au_data.hash_serialized) { + if (AssumeutxoHash{maybe_stats->hashSerialized} != au_data.hash_serialized) { LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n", - au_data.hash_serialized.ToString(), stats.hashSerialized.ToString()); + au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString()); return false; } @@ -5147,7 +5147,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() // won't ask to rewind the entire assumed-valid chain on startup. - if (DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) { index->nStatus |= BLOCK_OPT_WITNESS; } @@ -5216,9 +5216,9 @@ ChainstateManager::~ChainstateManager() { LOCK(::cs_main); - // TODO: The version bits cache and warning cache should probably become - // non-globals - g_versionbitscache.Clear(); + m_versionbitscache.Clear(); + + // TODO: The warning cache should probably become non-global for (auto& i : warningcache) { i.clear(); } diff --git a/src/validation.h b/src/validation.h index 42e41502f9..cc94add3cb 100644 --- a/src/validation.h +++ b/src/validation.h @@ -13,7 +13,10 @@ #include <arith_uint256.h> #include <attributes.h> #include <chain.h> +#include <chainparams.h> +#include <kernel/chainstatemanager_opts.h> #include <consensus/amount.h> +#include <deploymentstatus.h> #include <fs.h> #include <node/blockstorage.h> #include <policy/feerate.h> @@ -26,6 +29,7 @@ #include <util/check.h> #include <util/hasher.h> #include <util/translation.h> +#include <versionbits.h> #include <atomic> #include <map> @@ -40,7 +44,6 @@ class CChainState; class CBlockTreeDB; -class CChainParams; class CTxMemPool; class ChainstateManager; struct ChainTxData; @@ -51,9 +54,10 @@ struct AssumeutxoData; namespace node { class SnapshotMetadata; } // namespace node +namespace Consensus { +struct Params; +} // namespace Consensus -/** Default for -minrelaytxfee, minimum relay fee for transactions */ -static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; /** Default for -limitancestorcount, max number of in-mempool ancestors */ static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25; /** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */ @@ -120,8 +124,6 @@ extern bool g_parallel_script_checks; extern bool fRequireStandard; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; -/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ -extern CFeeRate minRelayTxFee; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ extern int64_t nMaxTipAge; @@ -356,15 +358,10 @@ bool TestBlockValidity(BlockValidationState& state, CChainState& chainstate, const CBlock& block, CBlockIndex* pindexPrev, + const std::function<int64_t()>& adjusted_time_callback, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ -void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams); - -/** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */ -std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams); - /** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */ class CVerifyDB { public: @@ -495,6 +492,7 @@ public: 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 @@ -834,6 +832,10 @@ private: CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; + const CChainParams m_chainparams; + + const std::function<int64_t()> m_adjusted_time_callback; + //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( CChainState& snapshot_chainstate, @@ -847,11 +849,19 @@ private: bool AcceptBlockHeader( const CBlockHeader& block, BlockValidationState& state, - const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); friend CChainState; public: + using Options = ChainstateManagerOpts; + + explicit ChainstateManager(const Options& opts) + : m_chainparams{opts.chainparams}, + m_adjusted_time_callback{Assert(opts.adjusted_time_callback)} {}; + + const CChainParams& GetParams() const { return m_chainparams; } + const Consensus::Params& GetConsensus() const { return m_chainparams.GetConsensus(); } + std::thread m_load_block; //! A single BlockManager instance is shared across each constructed //! chainstate to avoid duplicating block metadata. @@ -932,6 +942,11 @@ public: return m_blockman.m_block_index; } + /** + * Track versionbit status + */ + mutable VersionBitsCache m_versionbitscache; + //! @returns true if a snapshot-based chainstate is in use. Also implies //! that a background validation chainstate is also in use. bool IsSnapshotActive() const; @@ -960,7 +975,7 @@ public: * @param[out] new_block A boolean which is set to indicate if the block was first received via this call * @returns If the block was processed, independently of block validity */ - bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main); /** * Process incoming block headers. @@ -970,10 +985,9 @@ public: * * @param[in] block The block headers themselves * @param[out] state This may be set to an Error state if any error occurred processing them - * @param[in] chainparams The params for the chain we want to connect to * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers */ - bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); /** * Try to add a transaction to the memory pool. @@ -991,9 +1005,34 @@ public: //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ + void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const; + + /** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */ + std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const; + ~ChainstateManager(); }; +/** Deployment* info via ChainstateManager */ +template<typename DEP> +bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), dep, chainman.m_versionbitscache); +} + +template<typename DEP> +bool DeploymentActiveAt(const CBlockIndex& index, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAt(index, chainman.GetConsensus(), dep, chainman.m_versionbitscache); +} + +template<typename DEP> +bool DeploymentEnabled(const ChainstateManager& chainman, DEP dep) +{ + return DeploymentEnabled(chainman.GetConsensus(), dep); +} + using FopenFn = std::function<FILE*(const fs::path&, const char*)>; /** Dump the mempool to disk. */ diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index edc4633c01..613c5b65ef 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -5,6 +5,7 @@ #include <validationinterface.h> +#include <attributes.h> #include <chain.h> #include <consensus/validation.h> #include <logging.h> @@ -16,14 +17,16 @@ #include <unordered_map> #include <utility> -//! The MainSignalsInstance manages a list of shared_ptr<CValidationInterface> -//! callbacks. -//! -//! A std::unordered_map is used to track what callbacks are currently -//! registered, and a std::list is to used to store the callbacks that are -//! currently registered as well as any callbacks that are just unregistered -//! and about to be deleted when they are done executing. -struct MainSignalsInstance { +/** + * MainSignalsImpl manages a list of shared_ptr<CValidationInterface> callbacks. + * + * A std::unordered_map is used to track what callbacks are currently + * registered, and a std::list is used to store the callbacks that are + * currently registered as well as any callbacks that are just unregistered + * and about to be deleted when they are done executing. + */ +class MainSignalsImpl +{ private: Mutex m_mutex; //! List entries consist of a callback pointer and reference count. The @@ -40,9 +43,9 @@ public: // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; - explicit MainSignalsInstance(CScheduler& scheduler LIFETIMEBOUND) : m_schedulerClient(scheduler) {} + explicit MainSignalsImpl(CScheduler& scheduler LIFETIMEBOUND) : m_schedulerClient(scheduler) {} - void Register(std::shared_ptr<CValidationInterface> callbacks) + void Register(std::shared_ptr<CValidationInterface> callbacks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); auto inserted = m_map.emplace(callbacks.get(), m_list.end()); @@ -50,7 +53,7 @@ public: inserted.first->second->callbacks = std::move(callbacks); } - void Unregister(CValidationInterface* callbacks) + void Unregister(CValidationInterface* callbacks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); auto it = m_map.find(callbacks); @@ -64,7 +67,7 @@ public: //! map entry. After this call, the list may still contain callbacks that //! are currently executing, but it will be cleared when they are done //! executing. - void Clear() + void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); for (const auto& entry : m_map) { @@ -73,7 +76,7 @@ public: m_map.clear(); } - template<typename F> void Iterate(F&& f) + template<typename F> void Iterate(F&& f) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { WAIT_LOCK(m_mutex, lock); for (auto it = m_list.begin(); it != m_list.end();) { @@ -92,7 +95,7 @@ static CMainSignals g_signals; void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); - m_internals = std::make_unique<MainSignalsInstance>(scheduler); + m_internals = std::make_unique<MainSignalsImpl>(scheduler); } void CMainSignals::UnregisterBackgroundSignalScheduler() diff --git a/src/validationinterface.h b/src/validationinterface.h index ac62f8b467..a929a3d56b 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -17,9 +17,7 @@ class BlockValidationState; class CBlock; class CBlockIndex; struct CBlockLocator; -class CConnman; class CValidationInterface; -class uint256; class CScheduler; enum class MemPoolRemovalReason; @@ -177,10 +175,10 @@ protected: friend class ValidationInterfaceTest; }; -struct MainSignalsInstance; +class MainSignalsImpl; class CMainSignals { private: - std::unique_ptr<MainSignalsInstance> m_internals; + std::unique_ptr<MainSignalsImpl> m_internals; friend void ::RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface>); friend void ::UnregisterValidationInterface(CValidationInterface*); diff --git a/src/versionbits.h b/src/versionbits.h index 1b3fa11e61..9f7ee1b48e 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -92,16 +92,16 @@ public: static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ - ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Get the block height at which the BIP9 deployment switched into the state for the block after pindexPrev. */ - int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Determine what nVersion a new block should use */ - int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params); + int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - void Clear(); + void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); }; #endif // BITCOIN_VERSIONBITS_H diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index 1aa0339445..f8230f7a1d 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -99,7 +99,7 @@ void BerkeleyEnvironment::Close() if (ret != 0) LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret)); if (!fMockDb) - DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); + DbEnv((uint32_t)0).remove(strPath.c_str(), 0); if (error_file) fclose(error_file); @@ -248,7 +248,7 @@ const void* BerkeleyBatch::SafeDbt::get_data() const return m_dbt.get_data(); } -u_int32_t BerkeleyBatch::SafeDbt::get_size() const +uint32_t BerkeleyBatch::SafeDbt::get_size() const { return m_dbt.get_size(); } diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h index 1c99e1f9af..ddab85521b 100644 --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -34,7 +34,7 @@ struct bilingual_str; namespace wallet { struct WalletDatabaseFileId { - u_int8_t value[DB_FILE_ID_LEN]; + uint8_t value[DB_FILE_ID_LEN]; bool operator==(const WalletDatabaseFileId& rhs) const; }; @@ -182,7 +182,7 @@ class BerkeleyBatch : public DatabaseBatch // delegate to Dbt const void* get_data() const; - u_int32_t get_size() const; + uint32_t get_size() const; // conversion operator to access the underlying Dbt operator Dbt*(); diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 0b236e2e48..07df8d9fc8 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -307,7 +307,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, } } - if (LogAcceptCategory(BCLog::SELECTCOINS)) { + if (LogAcceptCategory(BCLog::SELECTCOINS, BCLog::Level::Debug)) { std::string log_message{"Coin selection best subset: "}; for (unsigned int i = 0; i < applicable_groups.size(); i++) { if (vfBest[i]) { @@ -328,24 +328,18 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, ******************************************************************************/ void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) { - // Compute the effective value first - const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes); - const CAmount ev = output.txout.nValue - coin_fee; - // Filter for positive only here before adding the coin - if (positive_only && ev <= 0) return; + if (positive_only && output.GetEffectiveValue() <= 0) return; m_outputs.push_back(output); COutput& coin = m_outputs.back(); - coin.fee = coin_fee; - fee += coin.fee; + fee += coin.GetFee(); coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes); long_term_fee += coin.long_term_fee; - coin.effective_value = ev; - effective_value += coin.effective_value; + effective_value += coin.GetEffectiveValue(); m_from_me &= coin.from_me; m_value += coin.txout.nValue; @@ -380,8 +374,8 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount waste = 0; CAmount selected_effective_value = 0; for (const COutput& coin : inputs) { - waste += coin.fee - coin.long_term_fee; - selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue; + waste += coin.GetFee() - coin.long_term_fee; + selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue; } if (change_cost) { diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 25c672eda0..9135e48104 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -20,6 +20,14 @@ static constexpr CAmount CHANGE_UPPER{1000000}; /** A UTXO under consideration for use in funding a new transaction. */ struct COutput { +private: + /** The output's value minus fees required to spend it.*/ + std::optional<CAmount> effective_value; + + /** The fee required to spend this output at the transaction's target feerate. */ + std::optional<CAmount> fee; + +public: /** The outpoint identifying this UTXO */ COutPoint outpoint; @@ -55,16 +63,10 @@ struct COutput { /** Whether the transaction containing this output is sent from the owning wallet */ bool from_me; - /** The output's value minus fees required to spend it. Initialized as the output's absolute value. */ - CAmount effective_value; - - /** The fee required to spend this output at the transaction's target feerate. */ - CAmount fee{0}; - /** The fee required to spend this output at the consolidation feerate. */ CAmount long_term_fee{0}; - COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me) + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt) : outpoint{outpoint}, txout{txout}, depth{depth}, @@ -73,9 +75,22 @@ struct COutput { solvable{solvable}, safe{safe}, time{time}, - from_me{from_me}, - effective_value{txout.nValue} - {} + from_me{from_me} + { + if (feerate) { + fee = input_bytes < 0 ? 0 : feerate.value().GetFee(input_bytes); + effective_value = txout.nValue - fee.value(); + } + } + + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const CAmount fees) + : COutput(outpoint, txout, depth, input_bytes, spendable, solvable, safe, time, from_me) + { + // if input_bytes is unknown, then fees should be 0, if input_bytes is known, then the fees should be a positive integer or 0 (input_bytes known and fees = 0 only happens in the tests) + assert((input_bytes < 0 && fees == 0) || (input_bytes > 0 && fees >= 0)); + fee = fees; + effective_value = txout.nValue - fee.value(); + } std::string ToString() const; @@ -83,6 +98,18 @@ struct COutput { { return outpoint < rhs.outpoint; } + + CAmount GetFee() const + { + assert(fee.has_value()); + return fee.value(); + } + + CAmount GetEffectiveValue() const + { + assert(effective_value.has_value()); + return effective_value.value(); + } }; /** Parameters for one iteration of Coin Selection. */ diff --git a/src/wallet/context.cpp b/src/wallet/context.cpp index 800aa5bf9c..3d4bf9d703 100644 --- a/src/wallet/context.cpp +++ b/src/wallet/context.cpp @@ -5,6 +5,6 @@ #include <wallet/context.h> namespace wallet { -WalletContext::WalletContext() {} -WalletContext::~WalletContext() {} +WalletContext::WalletContext() = default; +WalletContext::~WalletContext() = default; } // namespace wallet diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 73042424ad..afd2b83971 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -218,21 +218,20 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo // We cannot source new unconfirmed inputs(bip125 rule 2) new_coin_control.m_min_depth = 1; - CTransactionRef tx_new; - CAmount fee_ret; - int change_pos_in_out = -1; // No requested location for change + constexpr int RANDOM_CHANGE_POSITION = -1; bilingual_str fail_reason; FeeCalculation fee_calc_out; - if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) { + std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false); + if (!txr) { errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); return Result::WALLET_ERROR; } // Write back new fee if successful - new_fee = fee_ret; + new_fee = txr->fee; // Write back transaction - mtx = CMutableTransaction(*tx_new); + mtx = CMutableTransaction(*txr->tx); return Result::OK; } diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 98e843385c..e1203817e0 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -80,7 +80,10 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) //! Construct wallet tx status struct. WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { + AssertLockHeld(wallet.cs_wallet); + WalletTxStatus result; result.block_height = wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height : @@ -257,13 +260,14 @@ public: bilingual_str& fail_reason) override { LOCK(m_wallet->cs_wallet); - CTransactionRef tx; FeeCalculation fee_calc_out; - if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos, - fail_reason, coin_control, fee_calc_out, sign)) { - return {}; - } - return tx; + std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos, + fail_reason, coin_control, fee_calc_out, sign); + if (!txr) return {}; + fee = txr->fee; + change_pos = txr->change_pos; + + return txr->tx; } void commitTransaction(CTransactionRef tx, WalletValueMap value_map, diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index cddf94aab2..8cce07b921 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -123,6 +123,8 @@ static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CW CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { + AssertLockHeld(wallet.cs_wallet); + // Must wait until coinbase is safely deep enough in the chain before valuing it if (wallet.IsTxImmatureCoinBase(wtx)) return 0; @@ -164,6 +166,8 @@ CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache) { + AssertLockHeld(wallet.cs_wallet); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } @@ -173,6 +177,8 @@ CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, b CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache) { + AssertLockHeld(wallet.cs_wallet); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } @@ -182,6 +188,8 @@ CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletT CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter) { + AssertLockHeld(wallet.cs_wallet); + // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; diff --git a/src/wallet/receive.h b/src/wallet/receive.h index d7705b5262..1caef293f2 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -24,17 +24,17 @@ bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_ CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx); -CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); //! filter decides which addresses will count towards the debit CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true); -CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true); -// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct -// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The -// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid -// having to resolve the issue of member access into incomplete type CWallet. -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) NO_THREAD_SAFETY_ANALYSIS; +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct COutputEntry { CTxDestination destination; diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index d4f6c9d805..f25ad59528 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -264,7 +264,7 @@ RPCHelpMan addmultisigaddress() if (!request.params[2].isNull()) label = LabelFromValue(request.params[2]); - int required = request.params[0].get_int(); + int required = request.params[0].getInt<int>(); // Get the public keys const UniValue& keys_or_addrs = request.params[1].get_array(); @@ -302,11 +302,11 @@ RPCHelpMan addmultisigaddress() result.pushKV("descriptor", descriptor->ToString()); UniValue warnings(UniValue::VARR); - if (!request.params[3].isNull() && OutputTypeFromDestination(dest) != output_type) { + if (descriptor->GetOutputType() != output_type) { // Only warns if the user has explicitly chosen an address type we cannot generate warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); } - if (warnings.size()) result.pushKV("warnings", warnings); + if (!warnings.empty()) result.pushKV("warnings", warnings); return result; }, @@ -340,9 +340,9 @@ RPCHelpMan keypoolrefill() // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; if (!request.params[0].isNull()) { - if (request.params[0].get_int() < 0) + if (request.params[0].getInt<int>() < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); - kpSize = (unsigned int)request.params[0].get_int(); + kpSize = (unsigned int)request.params[0].getInt<int>(); } EnsureWalletIsUnlocked(*pwallet); diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index b4f01b00de..aa9f23c886 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1232,7 +1232,7 @@ static int64_t GetImportTimestamp(const UniValue& data, int64_t now) if (data.exists("timestamp")) { const UniValue& timestamp = data["timestamp"]; if (timestamp.isNum()) { - return timestamp.get_int64(); + return timestamp.getInt<int64_t>(); } else if (timestamp.isStr() && timestamp.get_str() == "now") { return now; } @@ -1473,7 +1473,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c next_index = range_start; if (data.exists("next_index")) { - next_index = data["next_index"].get_int64(); + next_index = data["next_index"].getInt<int64_t>(); // bound checks if (next_index < range_start || next_index >= range_end) { throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); @@ -1621,7 +1621,7 @@ RPCHelpMan importdescriptors() }, RPCExamples{ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, " - "{ \"desc\": \"<my desccriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + + "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'") }, [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 4eccff3969..2649fa586c 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -18,40 +18,40 @@ namespace wallet { static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - std::set<CTxDestination> address_set; - + std::set<CTxDestination> addresses; if (by_label) { // Get the set of addresses assigned to label - std::string label = LabelFromValue(params[0]); - address_set = wallet.GetLabelAddresses(label); + addresses = wallet.GetLabelAddresses(LabelFromValue(params[0])); + if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet"); } else { // Get the address CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - CScript script_pub_key = GetScriptForDestination(dest); - if (!wallet.IsMine(script_pub_key)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); + addresses.insert(dest); + } + + // Filter by own scripts only + std::set<CScript> output_scripts; + for (const auto& address : addresses) { + auto output_script{GetScriptForDestination(address)}; + if (wallet.IsMine(output_script)) { + output_scripts.insert(output_script); } - address_set.insert(dest); + } + + if (output_scripts.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } // Minimum confirmations int min_depth = 1; if (!params[1].isNull()) - min_depth = params[1].get_int(); + min_depth = params[1].getInt<int>(); const bool include_immature_coinbase{params[2].isNull() ? false : params[2].get_bool()}; - // Excluding coinbase outputs is deprecated - // It can be enabled by setting deprecatedrpc=exclude_coinbase - const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; - - if (include_immature_coinbase && !include_coinbase) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); - } - // Tally CAmount amount = 0; for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { @@ -59,15 +59,14 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b int depth{wallet.GetTxDepthInMainChain(wtx)}; if (depth < min_depth // Coinbase with less than 1 confirmation is no longer in the main chain - || (wtx.IsCoinBase() && (depth < 1 || !include_coinbase)) + || (wtx.IsCoinBase() && (depth < 1)) || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { continue; } for (const CTxOut& txout : wtx.tx->vout) { - CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && wallet.IsMine(address) && address_set.count(address)) { + if (output_scripts.count(txout.scriptPubKey) > 0) { amount += txout.nValue; } } @@ -200,7 +199,7 @@ RPCHelpMan getbalance() int min_depth = 0; if (!request.params[1].isNull()) { - min_depth = request.params[1].get_int(); + min_depth = request.params[1].getInt<int>(); } bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); @@ -324,7 +323,7 @@ RPCHelpMan lockunspent() }); const uint256 txid(ParseHashO(o, "txid")); - const int nOutput = find_value(o, "vout").get_int(); + const int nOutput = find_value(o, "vout").getInt<int>(); if (nOutput < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); } @@ -565,13 +564,13 @@ RPCHelpMan listunspent() int nMinDepth = 1; if (!request.params[0].isNull()) { RPCTypeCheckArgument(request.params[0], UniValue::VNUM); - nMinDepth = request.params[0].get_int(); + nMinDepth = request.params[0].getInt<int>(); } int nMaxDepth = 9999999; if (!request.params[1].isNull()) { RPCTypeCheckArgument(request.params[1], UniValue::VNUM); - nMaxDepth = request.params[1].get_int(); + nMaxDepth = request.params[1].getInt<int>(); } std::set<CTxDestination> destinations; @@ -623,7 +622,7 @@ RPCHelpMan listunspent() nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); if (options.exists("maximumCount")) - nMaximumCount = options["maximumCount"].get_int64(); + nMaximumCount = options["maximumCount"].getInt<int64_t>(); } // Make sure the results are valid at least up to the most recent block @@ -639,7 +638,7 @@ RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index 802cc63d6d..931c64ca06 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -54,7 +54,7 @@ RPCHelpMan walletpassphrase() strWalletPass = request.params[0].get_str().c_str(); // Get the timeout - nSleepTime = request.params[1].get_int64(); + nSleepTime = request.params[1].getInt<int64_t>(); // Timeout cannot be negative, otherwise it will relock immediately if (nSleepTime < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 07119133b7..d1a0ba50f6 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -155,15 +155,14 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto std::shuffle(recipients.begin(), recipients.end(), FastRandomContext()); // Send - CAmount nFeeRequired = 0; - int nChangePosRet = -1; + constexpr int RANDOM_CHANGE_POSITION = -1; bilingual_str error; - CTransactionRef tx; FeeCalculation fee_calc_out; - const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); - if (!fCreated) { + std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true); + if (!txr) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); } + CTransactionRef tx = txr->tx; wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */); if (verbose) { UniValue entry(UniValue::VOBJ); @@ -552,7 +551,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, } if (options.exists("changePosition") || options.exists("change_position")) { - change_position = (options.exists("change_position") ? options["change_position"] : options["changePosition"]).get_int(); + change_position = (options.exists("change_position") ? options["change_position"] : options["changePosition"]).getInt<int>(); } if (options.exists("change_type")) { @@ -660,7 +659,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, if (!vout_v.isNum()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); } - int vout = vout_v.get_int(); + int vout = vout_v.getInt<int>(); if (vout < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); } @@ -669,7 +668,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, if (!weight_v.isNum()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing weight key"); } - int64_t weight = weight_v.get_int64(); + int64_t weight = weight_v.getInt<int64_t>(); const int64_t min_input_weight = GetTransactionInputWeight(CTxIn()); CHECK_NONFATAL(min_input_weight == 165); if (weight < min_input_weight) { @@ -690,7 +689,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { - int pos = subtractFeeFromOutputs[idx].get_int(); + int pos = subtractFeeFromOutputs[idx].getInt<int>(); if (setSubtractFeeFromOutputs.count(pos)) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos)); if (pos < 0) @@ -1399,7 +1398,7 @@ RPCHelpMan sendall() total_input_value += tx->tx->vout[input.prevout.n].nValue; } } else { - AvailableCoins(*pwallet, all_the_utxos, &coin_control, /*nMinimumAmount=*/0); + AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0); for (const COutput& output : all_the_utxos) { CHECK_NONFATAL(output.input_bytes > 0); if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) { diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index c87af2ea30..fae9bf3ea5 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -15,6 +15,7 @@ using interfaces::FoundBlock; namespace wallet { static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { interfaces::Chain& chain = wallet.chain(); int confirms = wallet.GetTxDepthInMainChain(wtx); @@ -63,9 +64,7 @@ struct tallyitem int nConf{std::numeric_limits<int>::max()}; std::vector<uint256> txids; bool fIsWatchonly{false}; - tallyitem() - { - } + tallyitem() = default; }; static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) @@ -73,7 +72,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) - nMinDepth = params[0].get_int(); + nMinDepth = params[0].getInt<int>(); // Whether to include empty labels bool fIncludeEmpty = false; @@ -96,14 +95,6 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons has_filtered_address = true; } - // Excluding coinbase outputs is deprecated - // It can be enabled by setting deprecatedrpc=exclude_coinbase - const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; - - if (include_immature_coinbase && !include_coinbase) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); - } - // Tally std::map<CTxDestination, tallyitem> mapTally; for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) { @@ -114,7 +105,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons continue; // Coinbase with less than 1 confirmation is no longer in the main chain - if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase)) + if ((wtx.IsCoinBase() && (nDepth < 1)) || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { continue; @@ -513,10 +504,10 @@ RPCHelpMan listtransactions() } int nCount = 10; if (!request.params[1].isNull()) - nCount = request.params[1].get_int(); + nCount = request.params[1].getInt<int>(); int nFrom = 0; if (!request.params[2].isNull()) - nFrom = request.params[2].get_int(); + nFrom = request.params[2].getInt<int>(); isminefilter filter = ISMINE_SPENDABLE; if (ParseIncludeWatchonly(request.params[3], *pwallet)) { @@ -639,7 +630,7 @@ RPCHelpMan listsinceblock() } if (!request.params[1].isNull()) { - target_confirms = request.params[1].get_int(); + target_confirms = request.params[1].getInt<int>(); if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); @@ -893,14 +884,14 @@ RPCHelpMan rescanblockchain() int tip_height = pwallet->GetLastBlockHeight(); if (!request.params[0].isNull()) { - start_height = request.params[0].get_int(); + start_height = request.params[0].getInt<int>(); if (start_height < 0 || start_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } if (!request.params[1].isNull()) { - stop_height = request.params[1].get_int(); + stop_height = request.params[1].getInt<int>(); if (*stop_height < 0 || *stop_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } else if (*stop_height < start_height) { diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index efb4d8fc7e..4c44c333a6 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -559,7 +559,7 @@ static RPCHelpMan upgradewallet() int version = 0; if (!request.params[0].isNull()) { - version = request.params[0].get_int(); + version = request.params[0].getInt<int>(); } bilingual_str error; const int previous_version{pwallet->GetVersion()}; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 55c0a2cb7f..6f79ae4e9b 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -84,7 +84,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control); } -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) { AssertLockHeld(wallet.cs_wallet); @@ -192,7 +192,7 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly)); - vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me); + vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -211,13 +211,18 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C } } +void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +{ + AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); +} + CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { LOCK(wallet.cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(wallet, vCoins, coinControl); + AvailableCoinsListUnspent(wallet, vCoins, coinControl); for (const COutput& out : vCoins) { if (out.spendable) { balance += out.txout.nValue; @@ -257,7 +262,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(wallet, availableCoins); + AvailableCoinsListUnspent(wallet, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -477,12 +482,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec } /* 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); - output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes); + 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.effective_value; + 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. @@ -656,12 +660,10 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng } } -static bool CreateTransactionInternal( +static std::optional<CreatedTransactionResult> CreateTransactionInternal( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, + int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, @@ -669,6 +671,11 @@ static bool CreateTransactionInternal( { AssertLockHeld(wallet.cs_wallet); + // out variables, to be packed into returned result structure + CTransactionRef tx; + CAmount nFeeRet; + int nChangePosInOut = change_pos; + FastRandomContext rng_fast; CMutableTransaction txNew; // The resulting transaction that we make @@ -742,12 +749,12 @@ static bool CreateTransactionInternal( // provided one if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); - return false; + return std::nullopt; } if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { // eventually allow a fallback fee error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return false; + return std::nullopt; } // Calculate the cost of change @@ -774,7 +781,7 @@ static bool CreateTransactionInternal( if (IsDust(txout, wallet.chain().relayDustFee())) { error = _("Transaction amount too small"); - return false; + return std::nullopt; } txNew.vout.push_back(txout); } @@ -785,13 +792,13 @@ static bool CreateTransactionInternal( // Get available coins std::vector<COutput> vAvailableCoins; - AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0); // Choose coins to use std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); if (!result) { error = _("Insufficient funds"); - return false; + return std::nullopt; } TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue()); @@ -808,7 +815,7 @@ static bool CreateTransactionInternal( else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { error = _("Transaction change output index out of range"); - return false; + return std::nullopt; } assert(nChangePosInOut != -1); @@ -836,7 +843,7 @@ static bool CreateTransactionInternal( int nBytes = tx_sizes.vsize; if (nBytes == -1) { error = _("Missing solving data for estimating transaction size"); - return false; + return std::nullopt; } nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); @@ -900,7 +907,7 @@ static bool CreateTransactionInternal( } else { error = _("The transaction amount is too small to send after the fee has been deducted"); } - return false; + return std::nullopt; } } ++i; @@ -910,12 +917,12 @@ static bool CreateTransactionInternal( // Give up if change keypool ran out and change is required if (scriptChange.empty() && nChangePosInOut != -1) { - return false; + return std::nullopt; } if (sign && !wallet.SignTransaction(txNew)) { error = _("Signing transaction failed"); - return false; + return std::nullopt; } // Return the constructed transaction data. @@ -926,19 +933,19 @@ static bool CreateTransactionInternal( (!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT)) { error = _("Transaction too large"); - return false; + return std::nullopt; } if (nFeeRet > wallet.m_default_max_tx_fee) { error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return false; + return std::nullopt; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!wallet.chain().checkChainLimits(tx)) { error = _("Transaction has too long of a mempool chain"); - return false; + return std::nullopt; } } @@ -955,15 +962,13 @@ static bool CreateTransactionInternal( feeCalc.est.fail.start, feeCalc.est.fail.end, (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); - return true; + return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut); } -bool CreateTransaction( +std::optional<CreatedTransactionResult> CreateTransaction( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, + int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, @@ -971,42 +976,38 @@ bool CreateTransaction( { if (vecSend.empty()) { error = _("Transaction must have at least one recipient"); - return false; + return std::nullopt; } if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) { error = _("Transaction amounts must not be negative"); - return false; + return std::nullopt; } LOCK(wallet.cs_wallet); - int nChangePosIn = nChangePosInOut; - Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) - bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); - TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res, nFeeRet, nChangePosInOut); + std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign); + TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(), + txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0); + if (!txr_ungrouped) return std::nullopt; // try with avoidpartialspends unless it's enabled already - if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str()); CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; - CAmount nFeeRet2; - CTransactionRef tx2; - int nChangePosInOut2 = nChangePosIn; bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - if (CreateTransactionInternal(wallet, vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { - // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee; - wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); - TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, res, nFeeRet2, nChangePosInOut2); - if (use_aps) { - tx = tx2; - nFeeRet = nFeeRet2; - nChangePosInOut = nChangePosInOut2; - } + std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign); + // if fee of this alternative one is within the range of the max fee, we use this one + const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee) : false}; + TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(), + txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0); + if (txr_grouped) { + wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", + txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped"); + if (use_aps) return txr_grouped; } } - return res; + return txr_ungrouped; } bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) @@ -1030,11 +1031,12 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, // CreateTransaction call and LockCoin calls (when lockUnspents is true). LOCK(wallet.cs_wallet); - CTransactionRef tx_new; FeeCalculation fee_calc_out; - if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { - return false; - } + std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false); + if (!txr) return false; + CTransactionRef tx_new = txr->tx; + nFeeRet = txr->fee; + nChangePosInOut = txr->change_pos; if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); diff --git a/src/wallet/spend.h b/src/wallet/spend.h index e43aac5273..988058a25a 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -10,6 +10,8 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> +#include <optional> + namespace wallet { /** Get the marginal bytes if spending the specified output from this transaction. * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the @@ -35,7 +37,13 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle /** * populate vCoins with vector of available COutputs. */ -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, 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); +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, 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) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** + * Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function + * to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction. + */ +void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, 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); CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); @@ -82,12 +90,22 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +struct CreatedTransactionResult +{ + CTransactionRef tx; + CAmount fee; + int change_pos; + + CreatedTransactionResult(CTransactionRef tx, CAmount fee, int change_pos) + : tx(tx), fee(fee), change_pos(change_pos) {} +}; + /** * Create a new transaction paying the recipients with a set of coins * selected by SelectCoins(); Also create the change output, when needed - * @note passing nChangePosInOut as -1 will result in setting a random position + * @note passing change_pos as -1 will result in setting a random position */ -bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true); +std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true); /** * Insert additional inputs into the transaction by diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 72e749477b..76f28917a4 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -41,7 +41,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& se tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); + set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); } static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) @@ -50,7 +50,7 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); OutputGroup group; group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true); result.AddInput(group); @@ -62,14 +62,12 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); - coin.effective_value = nValue - fee; - coin.fee = fee; + COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ 148, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fee); coin.long_term_fee = long_term_fee; set.insert(coin); } -static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) { CMutableTransaction tx; tx.nLockTime = nextLockTime++; // so all transactions get different hashes @@ -88,7 +86,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{})); assert(ret.second); CWalletTx& wtx = (*ret.first).second; - coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe); + coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate); } /** Check if SelectionResult a is equivalent to SelectionResult b. @@ -311,13 +309,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) std::vector<COutput> coins; - add_coin(coins, *wallet, 1); + add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate); coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change)); // Test fees subtracted from output: coins.clear(); - add_coin(coins, *wallet, 1 * CENT); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate); coins.at(0).input_bytes = 40; coin_selection_params_bnb.m_subtract_fee_outputs = true; const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change); @@ -334,9 +332,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) std::vector<COutput> coins; - add_coin(coins, *wallet, 5 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 3 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); CCoinControl coin_control; coin_control.fAllowOtherInputs = true; coin_control.Select(coins.at(0).outpoint); @@ -344,6 +342,61 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(result10); } + { + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + wallet->LoadWallet(); + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet->SetupDescriptorScriptPubKeyMans(); + + std::vector<COutput> coins; + + // single coin should be selected when effective fee > long term fee + coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + + expected_result.Clear(); + add_coin(10 * CENT, 2, expected_result); + CCoinControl coin_control; + const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + BOOST_CHECK(EquivalentResult(expected_result, *result11)); + coins.clear(); + + // more coins should be selected when effective fee < long term fee + coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + + expected_result.Clear(); + add_coin(9 * CENT, 2, expected_result); + add_coin(1 * CENT, 2, expected_result); + const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + BOOST_CHECK(EquivalentResult(expected_result, *result12)); + coins.clear(); + + // pre selected coin should be selected even if disadvantageous + coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + + expected_result.Clear(); + add_coin(9 * CENT, 2, expected_result); + add_coin(1 * CENT, 2, expected_result); + coin_control.fAllowOtherInputs = true; + coin_control.Select(coins.at(1).outpoint); // pre select 9 coin + const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + BOOST_CHECK(EquivalentResult(expected_result, *result13)); + } } BOOST_AUTO_TEST_CASE(knapsack_solver_test) @@ -367,7 +420,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // with an empty wallet we can't even pay one cent BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); - add_coin(coins, *wallet, 1*CENT, 4); // add a new 1 cent coin + add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); @@ -388,7 +441,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT); add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin, - add_coin(coins, *wallet, 10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 @@ -815,5 +868,40 @@ BOOST_AUTO_TEST_CASE(waste_test) BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target)); } +BOOST_AUTO_TEST_CASE(effective_value_test) +{ + const int input_bytes = 148; + const CFeeRate feerate(1000); + const CAmount nValue = 10000; + const int nInput = 0; + + CMutableTransaction tx; + tx.vout.resize(1); + tx.vout[nInput].nValue = nValue; + + // standard case, pass feerate in constructor + COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + const CAmount expected_ev1 = 9852; // 10000 - 148 + BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass feerate in constructor + COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 + + // negative effective value, pass feerate in constructor + COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, CFeeRate(100000)); + const CAmount expected_ev3 = -4800; // 10000 - 14800 + BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3); + + // standard case, pass fees in constructor + const CAmount fees = 148; + COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fees); + BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass fees in constructor + COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); + BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 +} + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp index 2693b68cca..3465f2f331 100644 --- a/src/wallet/test/fuzz/coinselection.cpp +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -14,13 +14,13 @@ namespace wallet { -static void AddCoin(const CAmount& value, int n_input, int n_input_bytes, int locktime, std::vector<COutput>& coins) +static void AddCoin(const CAmount& value, int n_input, int n_input_bytes, int locktime, std::vector<COutput>& coins, CFeeRate fee_rate) { CMutableTransaction tx; tx.vout.resize(n_input + 1); tx.vout[n_input].nValue = value; tx.nLockTime = locktime; // all transactions get different hashes - coins.emplace_back(COutPoint(tx.GetHash(), n_input), tx.vout.at(n_input), /*depth=*/0, n_input_bytes, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true); + coins.emplace_back(COutPoint(tx.GetHash(), n_input), tx.vout.at(n_input), /*depth=*/0, n_input_bytes, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true, fee_rate); } // Randomly distribute coins to instances of OutputGroup @@ -70,7 +70,7 @@ FUZZ_TARGET(coinselection) if (total_balance + amount >= MAX_MONEY) { break; } - AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool); + AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool, coin_params.m_effective_feerate); total_balance += amount; } diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index 334bd5b8bc..bdc148afb4 100644 --- a/src/wallet/test/spend_tests.cpp +++ b/src/wallet/test/spend_tests.cpp @@ -27,9 +27,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) // instead of the miner. auto check_tx = [&wallet](CAmount leftover_input_amount) { CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */}; - CTransactionRef tx; - CAmount fee; - int change_pos = -1; + constexpr int RANDOM_CHANGE_POSITION = -1; bilingual_str error; CCoinControl coin_control; coin_control.m_feerate.emplace(10000); @@ -37,11 +35,12 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) // We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output coin_control.m_change_type = OutputType::LEGACY; FeeCalculation fee_calc; - BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc)); - BOOST_CHECK_EQUAL(tx->vout.size(), 1); - BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee); - BOOST_CHECK_GT(fee, 0); - return fee; + std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc); + BOOST_CHECK(txr.has_value()); + BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1); + BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee); + BOOST_CHECK_GT(txr->fee, 0); + return txr->fee; }; // Send full input amount to recipient, check that only nonzero fee is diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 683f0eb327..70863f5464 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -521,13 +521,14 @@ public: CWalletTx& AddTx(CRecipient recipient) { CTransactionRef tx; - CAmount fee; - int changePos = -1; bilingual_str error; CCoinControl dummy; FeeCalculation fee_calc_out; { - BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out)); + constexpr int RANDOM_CHANGE_POSITION = -1; + std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out); + BOOST_CHECK(txr.has_value()); + tx = txr->tx; } wallet->CommitTransaction(tx, {}, {}); CMutableTransaction blocktx; @@ -583,7 +584,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - AvailableCoins(*wallet, available); + AvailableCoinsListUnspent(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -595,7 +596,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - AvailableCoins(*wallet, available); + AvailableCoinsListUnspent(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 356d0ce5a0..0c83c3d954 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1707,8 +1707,10 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r */ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate) { - int64_t nNow = GetTime(); - int64_t start_time = GetTimeMillis(); + using Clock = std::chrono::steady_clock; + constexpr auto LOG_INTERVAL{60s}; + auto current_time{Clock::now()}; + auto start_time{Clock::now()}; assert(reserver.isReserved()); @@ -1735,8 +1737,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } - if (GetTime() >= nNow + 60) { - nNow = GetTime(); + if (Clock::now() >= current_time + LOG_INTERVAL) { + current_time = Clock::now(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } @@ -1803,7 +1805,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current); result.status = ScanResult::USER_ABORT; } else { - WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); + auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time); + WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count()); } return result; } @@ -1838,6 +1841,8 @@ void CWallet::ReacceptWalletTransactions() bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const { + AssertLockHeld(cs_wallet); + // Can't relay if wallet is not broadcasting if (!GetBroadcastTransactions()) return false; // Don't relay abandoned transactions @@ -1866,12 +1871,11 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const { - std::set<uint256> result; - { - uint256 myHash = wtx.GetHash(); - result = GetConflicts(myHash); - result.erase(myHash); - } + AssertLockHeld(cs_wallet); + + const uint256 myHash{wtx.GetHash()}; + std::set<uint256> result{GetConflicts(myHash)}; + result.erase(myHash); return result; } @@ -2102,7 +2106,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. - AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) { + CWalletTx* wtx = AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) { CHECK_NONFATAL(wtx.mapValue.empty()); CHECK_NONFATAL(wtx.vOrderForm.empty()); wtx.mapValue = std::move(mapValue); @@ -2112,6 +2116,11 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve return true; }); + // wtx can only be null if the db write failed. + if (!wtx) { + throw std::runtime_error(std::string(__func__) + ": Wallet db error, transaction commit failed"); + } + // Notify that old coins are spent for (const CTxIn& txin : tx->vin) { CWalletTx &coin = mapWallet.at(txin.prevout.hash); @@ -2119,17 +2128,13 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve NotifyTransactionChanged(coin.GetHash(), CT_UPDATED); } - // Get the inserted-CWalletTx from mapWallet so that the - // wtx cached mempool state is updated correctly - CWalletTx& wtx = mapWallet.at(tx->GetHash()); - if (!fBroadcastTransactions) { // Don't submit tx to the mempool return; } std::string err_string; - if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) { + if (!SubmitTxMemoryPoolAndRelay(*wtx, err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } @@ -2961,6 +2966,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf // so that in case of a shutdown event, the rescan will be repeated at the next start. // This is temporary until rescan and notifications delivery are unified under same // interface. + walletInstance->m_attaching_chain = true; //ignores chainStateFlushed notifications walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); // If rescan_required = true, rescan_height remains equal to 0 @@ -2987,7 +2993,6 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf if (tip_height && *tip_height != rescan_height) { - walletInstance->m_attaching_chain = true; //ignores chainStateFlushed notifications if (chain.havePruned()) { int block_height = *tip_height; while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { @@ -3031,6 +3036,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->GetDatabase().IncrementUpdateCounter(); } + walletInstance->m_attaching_chain = false; return true; } @@ -3124,8 +3130,11 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const { - if (!wtx.IsCoinBase()) + AssertLockHeld(cs_wallet); + + if (!wtx.IsCoinBase()) { return 0; + } int chain_depth = GetTxDepthInMainChain(wtx); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); @@ -3133,6 +3142,8 @@ int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const bool CWallet::IsTxImmatureCoinBase(const CWalletTx& wtx) const { + AssertLockHeld(cs_wallet); + // note GetBlocksToMaturity is 0 for non-coinbase tx return GetTxBlocksToMaturity(wtx) > 0; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4e81a2b957..7da601c3b7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -415,13 +415,7 @@ public: const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - std::set<uint256> GetTxConflicts(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; + std::set<uint256> GetTxConflicts(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return depth of transaction in blockchain: @@ -429,22 +423,20 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - int GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; - bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 0; } + int GetTxDepthInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + { + AssertLockHeld(cs_wallet); + return GetTxDepthInMainChain(wtx) > 0; + } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetTxBlocksToMaturity(const CWalletTx& wtx) const; - bool IsTxImmatureCoinBase(const CWalletTx& wtx) const; + int GetTxBlocksToMaturity(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxImmatureCoinBase(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! check whether we support the named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } @@ -584,7 +576,8 @@ public: void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const; + bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const { diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 7bbed7973f..79e0a330b7 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -314,8 +314,7 @@ public: std::map<uint160, CHDChain> m_hd_chains; bool tx_corrupt{false}; - CWalletScanState() { - } + CWalletScanState() = default; }; static bool diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index a53de34db4..26618735f6 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -70,9 +70,9 @@ bool CZMQNotificationInterface::Initialize() { int major = 0, minor = 0, patch = 0; zmq_version(&major, &minor, &patch); - LogPrint(BCLog::ZMQ, "zmq: version %d.%d.%d\n", major, minor, patch); + LogPrint(BCLog::ZMQ, "version %d.%d.%d\n", major, minor, patch); - LogPrint(BCLog::ZMQ, "zmq: Initialize notification interface\n"); + LogPrint(BCLog::ZMQ, "Initialize notification interface\n"); assert(!pcontext); pcontext = zmq_ctx_new(); @@ -85,9 +85,9 @@ bool CZMQNotificationInterface::Initialize() for (auto& notifier : notifiers) { if (notifier->Initialize(pcontext)) { - LogPrint(BCLog::ZMQ, "zmq: Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); } else { - LogPrint(BCLog::ZMQ, "zmq: Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); return false; } } @@ -98,11 +98,11 @@ bool CZMQNotificationInterface::Initialize() // Called during shutdown sequence void CZMQNotificationInterface::Shutdown() { - LogPrint(BCLog::ZMQ, "zmq: Shutdown notification interface\n"); + LogPrint(BCLog::ZMQ, "Shutdown notification interface\n"); if (pcontext) { for (auto& notifier : notifiers) { - LogPrint(BCLog::ZMQ, "zmq: Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); notifier->Shutdown(); } zmq_ctx_term(pcontext); diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 2c6f24a239..011336bf72 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -106,7 +106,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) return false; } - LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); + LogPrint(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); int rc = zmq_setsockopt(psocket, ZMQ_SNDHWM, &outbound_message_high_water_mark, sizeof(outbound_message_high_water_mark)); if (rc != 0) @@ -147,8 +147,8 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) } else { - LogPrint(BCLog::ZMQ, "zmq: Reusing socket for address %s\n", address); - LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); + LogPrint(BCLog::ZMQ, "Reusing socket for address %s\n", address); + LogPrint(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); psocket = i->second->psocket; mapPublishNotifiers.insert(std::make_pair(address, this)); @@ -179,7 +179,7 @@ void CZMQAbstractPublishNotifier::Shutdown() if (count == 1) { - LogPrint(BCLog::ZMQ, "zmq: Close socket at address %s\n", address); + LogPrint(BCLog::ZMQ, "Close socket at address %s\n", address); int linger = 0; zmq_setsockopt(psocket, ZMQ_LINGER, &linger, sizeof(linger)); zmq_close(psocket); @@ -208,7 +208,7 @@ bool CZMQAbstractPublishNotifier::SendZmqMessage(const char *command, const void bool CZMQPublishHashBlockNotifier::NotifyBlock(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashblock %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashblock %s to %s\n", hash.GetHex(), this->address); uint8_t data[32]; for (unsigned int i = 0; i < 32; i++) { data[31 - i] = hash.begin()[i]; @@ -219,7 +219,7 @@ bool CZMQPublishHashBlockNotifier::NotifyBlock(const CBlockIndex *pindex) bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &transaction) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx %s to %s\n", hash.GetHex(), this->address); uint8_t data[32]; for (unsigned int i = 0; i < 32; i++) { data[31 - i] = hash.begin()[i]; @@ -229,7 +229,7 @@ bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &t bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) { - LogPrint(BCLog::ZMQ, "zmq: Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address); const Consensus::Params& consensusParams = Params().GetConsensus(); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); @@ -251,7 +251,7 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish rawtx %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish rawtx %s to %s\n", hash.GetHex(), this->address); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ss << transaction; return SendZmqMessage(MSG_RAWTX, &(*ss.begin()), ss.size()); @@ -273,27 +273,27 @@ static bool SendSequenceMsg(CZMQAbstractPublishNotifier& notifier, uint256 hash, bool CZMQPublishSequenceNotifier::NotifyBlockConnect(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish sequence block connect %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish sequence block connect %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Block (C)onnect */ 'C'); } bool CZMQPublishSequenceNotifier::NotifyBlockDisconnect(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish sequence block disconnect %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish sequence block disconnect %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Block (D)isconnect */ 'D'); } bool CZMQPublishSequenceNotifier::NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool acceptance %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx mempool acceptance %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Mempool (A)cceptance */ 'A', mempool_sequence); } bool CZMQPublishSequenceNotifier::NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool removal %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx mempool removal %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Mempool (R)emoval */ 'R', mempool_sequence); } diff --git a/src/zmq/zmqutil.cpp b/src/zmq/zmqutil.cpp index f0568634d4..cf3a0b2d71 100644 --- a/src/zmq/zmqutil.cpp +++ b/src/zmq/zmqutil.cpp @@ -12,5 +12,5 @@ void zmqError(const std::string& str) { - LogPrint(BCLog::ZMQ, "zmq: Error: %s, msg: %s\n", str, zmq_strerror(errno)); + LogPrint(BCLog::ZMQ, "Error: %s, msg: %s\n", str, zmq_strerror(errno)); } |