diff options
Diffstat (limited to 'src')
182 files changed, 2254 insertions, 1604 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index c2e0c7b5b8..b749651b72 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -230,6 +230,7 @@ BITCOIN_CORE_H = \ node/peerman_args.h \ node/protocol_version.h \ node/psbt.h \ + node/timeoffsets.h \ node/transaction.h \ node/txreconciliation.h \ node/utxo_snapshot.h \ @@ -280,7 +281,6 @@ BITCOIN_CORE_H = \ support/lockedpool.h \ sync.h \ threadsafety.h \ - timedata.h \ torcontrol.h \ txdb.h \ txmempool.h \ @@ -434,6 +434,7 @@ libbitcoin_node_a_SOURCES = \ node/minisketchwrapper.cpp \ node/peerman_args.cpp \ node/psbt.cpp \ + node/timeoffsets.cpp \ node/transaction.cpp \ node/txreconciliation.cpp \ node/utxo_snapshot.cpp \ @@ -461,7 +462,6 @@ libbitcoin_node_a_SOURCES = \ rpc/txoutproof.cpp \ script/sigcache.cpp \ signet.cpp \ - timedata.cpp \ torcontrol.cpp \ txdb.cpp \ txmempool.cpp \ @@ -945,7 +945,6 @@ libbitcoinkernel_la_SOURCES = \ kernel/disconnected_transactions.cpp \ kernel/mempool_persist.cpp \ kernel/mempool_removal_reason.cpp \ - key.cpp \ logging.cpp \ node/blockstorage.cpp \ node/chainstate.cpp \ @@ -975,7 +974,6 @@ libbitcoinkernel_la_SOURCES = \ txdb.cpp \ txmempool.cpp \ uint256.cpp \ - util/batchpriority.cpp \ util/chaintype.cpp \ util/check.cpp \ util/feefrac.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 942e0bf69b..cfd28b0a4d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -153,7 +153,7 @@ BITCOIN_TESTS =\ test/streams_tests.cpp \ test/sync_tests.cpp \ test/system_tests.cpp \ - test/timedata_tests.cpp \ + test/timeoffsets_tests.cpp \ test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ test/translation_tests.cpp \ @@ -384,7 +384,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/string.cpp \ test/fuzz/strprintf.cpp \ test/fuzz/system.cpp \ - test/fuzz/timedata.cpp \ + test/fuzz/timeoffsets.cpp \ test/fuzz/torcontrol.cpp \ test/fuzz/transaction.cpp \ test/fuzz/tx_in.cpp \ diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 14dc314c36..4d34c24ba9 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <addrdb.h> diff --git a/src/addrman.cpp b/src/addrman.cpp index ef8ed92bb5..d0b820ee65 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <addrman.h> #include <addrman_impl.h> @@ -175,7 +173,7 @@ void AddrManImpl::Serialize(Stream& s_) const */ // Always serialize in the latest version (FILE_FORMAT). - ParamsStream s{CAddress::V2_DISK, s_}; + ParamsStream s{s_, CAddress::V2_DISK}; s << static_cast<uint8_t>(FILE_FORMAT); @@ -240,7 +238,7 @@ void AddrManImpl::Unserialize(Stream& s_) s_ >> Using<CustomUintFormatter<1>>(format); const auto ser_params = (format >= Format::V3_BIP155 ? CAddress::V2_DISK : CAddress::V1_DISK); - ParamsStream s{ser_params, s_}; + ParamsStream s{s_, ser_params}; uint8_t compat; s >> compat; diff --git a/src/addrman_impl.h b/src/addrman_impl.h index 867c894d01..dd7f7b318f 100644 --- a/src/addrman_impl.h +++ b/src/addrman_impl.h @@ -11,7 +11,6 @@ #include <protocol.h> #include <serialize.h> #include <sync.h> -#include <timedata.h> #include <uint256.h> #include <util/time.h> diff --git a/src/bench/bip324_ecdh.cpp b/src/bench/bip324_ecdh.cpp index fb10c2957e..88f3932ad8 100644 --- a/src/bench/bip324_ecdh.cpp +++ b/src/bench/bip324_ecdh.cpp @@ -14,7 +14,7 @@ static void BIP324_ECDH(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; FastRandomContext rng; std::array<std::byte, 32> key_data; @@ -44,8 +44,6 @@ static void BIP324_ECDH(benchmark::Bench& bench) // - Copy 16 bytes from the resulting shared secret into the middle of their ellswift key. std::copy(ret.begin() + 16, ret.end(), their_ellswift_data.begin() + 24); }); - - ECC_Stop(); } BENCHMARK(BIP324_ECDH, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 4a3ec67c2b..05b2f5435c 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -18,7 +18,7 @@ // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CCoinsCaching(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; FillableSigningProvider keystore; CCoinsView coinsDummy; @@ -47,7 +47,6 @@ static void CCoinsCaching(benchmark::Bench& bench) bool success{AreInputsStandard(tx_1, coins)}; assert(success); }); - ECC_Stop(); } BENCHMARK(CCoinsCaching, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 114dd9d39c..c973fe9f71 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -25,7 +25,7 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) // We shouldn't ever be running with the checkqueue on a single core machine. if (GetNumCores() <= 1) return; - ECC_Start(); + ECC_Context ecc_context{}; struct PrevectorJob { prevector<PREVECTOR_SIZE, uint8_t> p; @@ -62,6 +62,5 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) // it is done explicitly here for clarity control.Wait(); }); - ECC_Stop(); } BENCHMARK(CCheckQueueSpeedPrevectorJob, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/descriptors.cpp b/src/bench/descriptors.cpp index fbef1395fb..5d6bcb8ce8 100644 --- a/src/bench/descriptors.cpp +++ b/src/bench/descriptors.cpp @@ -12,7 +12,7 @@ static void ExpandDescriptor(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; const auto desc_str = "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))"; const std::pair<int64_t, int64_t> range = {0, 1000}; @@ -27,8 +27,6 @@ static void ExpandDescriptor(benchmark::Bench& bench) assert(success); } }); - - ECC_Stop(); } BENCHMARK(ExpandDescriptor, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ellswift.cpp b/src/bench/ellswift.cpp index 9441b4863e..4780db8e1c 100644 --- a/src/bench/ellswift.cpp +++ b/src/bench/ellswift.cpp @@ -9,7 +9,7 @@ static void EllSwiftCreate(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; CKey key = GenerateRandomKey(); uint256 entropy = GetRandHash(); @@ -22,8 +22,6 @@ static void EllSwiftCreate(benchmark::Bench& bench) /* Use the last 32 bytes of the ellswift encoded public key as next entropy. */ std::copy(ret.begin() + 32, ret.begin() + 64, MakeWritableByteSpan(entropy).begin()); }); - - ECC_Stop(); } BENCHMARK(EllSwiftCreate, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index ee750bc1f8..f38aa49a23 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -15,7 +15,7 @@ // modified to measure performance of other types of scripts. static void VerifyScriptBench(benchmark::Bench& bench) { - ECC_Start(); + ECC_Context ecc_context{}; const uint32_t flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; const int witnessversion = 0; @@ -57,7 +57,6 @@ static void VerifyScriptBench(benchmark::Bench& bench) assert(err == SCRIPT_ERR_OK); assert(success); }); - ECC_Stop(); } static void VerifyNestedIfScript(benchmark::Bench& bench) diff --git a/src/bench/wallet_create.cpp b/src/bench/wallet_create.cpp index 32f55f51e1..618d5bc80c 100644 --- a/src/bench/wallet_create.cpp +++ b/src/bench/wallet_create.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <bench/bench.h> #include <node/context.h> @@ -51,9 +49,14 @@ static void WalletCreate(benchmark::Bench& bench, bool encrypted) static void WalletCreatePlain(benchmark::Bench& bench) { WalletCreate(bench, /*encrypted=*/false); } static void WalletCreateEncrypted(benchmark::Bench& bench) { WalletCreate(bench, /*encrypted=*/true); } +#ifndef _MSC_VER +// TODO: Being built with MSVC, the fs::remove_all() call in +// the WalletCreate() fails with the error "The process cannot +// access the file because it is being used by another process." #ifdef USE_SQLITE BENCHMARK(WalletCreatePlain, benchmark::PriorityLevel::LOW); BENCHMARK(WalletCreateEncrypted, benchmark::PriorityLevel::LOW); #endif +#endif } // namespace wallet diff --git a/src/bench/wallet_ismine.cpp b/src/bench/wallet_ismine.cpp index 3f922e18a5..753404b526 100644 --- a/src/bench/wallet_ismine.cpp +++ b/src/bench/wallet_ismine.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif // HAVE_CONFIG_H +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <bench/bench.h> #include <interfaces/chain.h> #include <key.h> diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp index 6305126c7d..02582deda4 100644 --- a/src/bench/wallet_loading.cpp +++ b/src/bench/wallet_loading.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <bench/bench.h> #include <interfaces/chain.h> diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index 642af06e82..4927634233 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -26,6 +26,7 @@ #include <script/sigcache.h> #include <util/chaintype.h> #include <util/fs.h> +#include <util/signalinterrupt.h> #include <util/task_runner.h> #include <validation.h> #include <validationinterface.h> diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 8901d10ef6..b7e4e64103 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparamsbase.h> #include <clientversion.h> @@ -300,7 +298,7 @@ public: } addresses.pushKV("total", total); result.pushKV("addresses_known", addresses); - return JSONRPCReplyObj(result, NullUniValue, 1); + return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY); } }; @@ -369,7 +367,7 @@ public: } result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]); result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]); - return JSONRPCReplyObj(result, NullUniValue, 1); + return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY); } }; @@ -624,7 +622,7 @@ public: } } - return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1); + return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY); } const std::string m_help_doc{ @@ -711,7 +709,7 @@ public: UniValue result(UniValue::VOBJ); result.pushKV("address", address_str); result.pushKV("blocks", reply.get_obj()["result"]); - return JSONRPCReplyObj(result, NullUniValue, 1); + return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V1_LEGACY); } protected: std::string address_str; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 320624c419..cfac50e090 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparamsbase.h> #include <clientversion.h> @@ -694,21 +692,10 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) tx = mergedTx; } -class Secp256k1Init -{ -public: - Secp256k1Init() { - ECC_Start(); - } - ~Secp256k1Init() { - ECC_Stop(); - } -}; - static void MutateTx(CMutableTransaction& tx, const std::string& command, const std::string& commandVal) { - std::unique_ptr<Secp256k1Init> ecc; + std::unique_ptr<ECC_Context> ecc; if (command == "nversion") MutateTxVersion(tx, commandVal); @@ -728,10 +715,10 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, else if (command == "outaddr") MutateTxAddOutAddr(tx, commandVal); else if (command == "outpubkey") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxAddOutPubKey(tx, commandVal); } else if (command == "outmultisig") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxAddOutMultiSig(tx, commandVal); } else if (command == "outscript") MutateTxAddOutScript(tx, commandVal); @@ -739,7 +726,7 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, MutateTxAddOutData(tx, commandVal); else if (command == "sign") { - ecc.reset(new Secp256k1Init()); + ecc.reset(new ECC_Context()); MutateTxSign(tx, commandVal); } diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp index 96387e8c71..c8f5bc5026 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <arith_uint256.h> #include <chain.h> diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index fe90958a5f..e6d20b55c2 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparams.h> #include <chainparamsbase.h> @@ -130,10 +128,9 @@ MAIN_FUNCTION return EXIT_FAILURE; } - ECC_Start(); + ECC_Context ecc_context{}; if (!wallet::WalletTool::ExecuteWalletToolFunc(args, command->command)) { return EXIT_FAILURE; } - ECC_Stop(); return EXIT_SUCCESS; } diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 54796c5abb..0b89aa42af 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparams.h> #include <clientversion.h> @@ -16,11 +14,13 @@ #include <init.h> #include <interfaces/chain.h> #include <interfaces/init.h> +#include <kernel/context.h> #include <node/context.h> #include <node/interface_ui.h> #include <noui.h> #include <util/check.h> #include <util/exception.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/syserror.h> #include <util/threadnames.h> @@ -182,6 +182,7 @@ static bool AppInit(NodeContext& node) } node.kernel = std::make_unique<kernel::Context>(); + node.ecc_context = std::make_unique<ECC_Context>(); if (!AppInitSanityChecks(*node.kernel)) { // InitError will have been called with detailed error, which ends up on console diff --git a/src/clientversion.cpp b/src/clientversion.cpp index bf5579ee69..6b9727a158 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -2,16 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <clientversion.h> +#include <util/string.h> #include <util/translation.h> #include <tinyformat.h> -#include <sstream> #include <string> #include <vector> @@ -66,19 +64,9 @@ std::string FormatFullVersion() */ std::string FormatSubVersion(const std::string& name, int nClientVersion, const std::vector<std::string>& comments) { - std::ostringstream ss; - ss << "/"; - ss << name << ":" << FormatVersion(nClientVersion); - if (!comments.empty()) - { - std::vector<std::string>::const_iterator it(comments.begin()); - ss << "(" << *it; - for(++it; it != comments.end(); ++it) - ss << "; " << *it; - ss << ")"; - } - ss << "/"; - return ss.str(); + std::string comments_str; + if (!comments.empty()) comments_str = strprintf("(%s)", Join(comments, "; ")); + return strprintf("/%s:%s%s/", name, FormatVersion(nClientVersion), comments_str); } std::string CopyrightHolders(const std::string& strPrefix) diff --git a/src/clientversion.h b/src/clientversion.h index 9da0cd0b39..73aaf868e4 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -7,9 +7,7 @@ #include <util/macros.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif //HAVE_CONFIG_H +#include <config/bitcoin-config.h> // IWYU pragma: keep // Check that required client information is defined #if !defined(CLIENT_VERSION_MAJOR) || !defined(CLIENT_VERSION_MINOR) || !defined(CLIENT_VERSION_BUILD) || !defined(CLIENT_VERSION_IS_RELEASE) || !defined(COPYRIGHT_YEAR) diff --git a/src/common/run_command.cpp b/src/common/run_command.cpp index 347b486095..67608b985f 100644 --- a/src/common/run_command.cpp +++ b/src/common/run_command.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <common/run_command.h> diff --git a/src/common/settings.cpp b/src/common/settings.cpp index db1001111a..c1520dacd2 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -4,9 +4,7 @@ #include <common/settings.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <tinyformat.h> #include <univalue.h> diff --git a/src/common/system.cpp b/src/common/system.cpp index 1fa53a5f34..ddd0feda3b 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <common/system.h> diff --git a/src/common/system.h b/src/common/system.h index 83280d46ee..d9115d3b33 100644 --- a/src/common/system.h +++ b/src/common/system.h @@ -6,9 +6,7 @@ #ifndef BITCOIN_COMMON_SYSTEM_H #define BITCOIN_COMMON_SYSTEM_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <cstdint> #include <string> diff --git a/src/crypto/sha256.cpp b/src/crypto/sha256.cpp index 301f22a248..c883bd2f03 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <crypto/sha256.h> #include <crypto/common.h> diff --git a/src/crypto/sha256_sse4.cpp b/src/crypto/sha256_sse4.cpp index f4557291ce..f0e255a23c 100644 --- a/src/crypto/sha256_sse4.cpp +++ b/src/crypto/sha256_sse4.cpp @@ -13,6 +13,13 @@ namespace sha256_sse4 { void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks) +#if defined(__clang__) && !defined(__OPTIMIZE__) + /* + clang is unable to compile this with -O0 and -fsanitize=address. + See upstream bug: https://github.com/llvm/llvm-project/issues/92182 + */ + __attribute__((no_sanitize("address"))) +#endif { static const uint32_t K256 alignas(16) [] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, diff --git a/src/httprpc.cpp b/src/httprpc.cpp index c72dbf10bc..3eb34dbe6a 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -73,8 +73,11 @@ static std::vector<std::vector<std::string>> g_rpcauth; static std::map<std::string, std::set<std::string>> g_rpc_whitelist; static bool g_rpc_whitelist_default = false; -static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) +static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCRequest& jreq) { + // Sending HTTP errors is a legacy JSON-RPC behavior. + Assume(jreq.m_json_version != JSONRPCVersion::V2); + // Send error reply from json-rpc error object int nStatus = HTTP_INTERNAL_SERVER_ERROR; int code = objError.find_value("code").getInt<int>(); @@ -84,7 +87,7 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; - std::string strReply = JSONRPCReply(NullUniValue, objError, id); + std::string strReply = JSONRPCReplyObj(NullUniValue, std::move(objError), jreq.id, jreq.m_json_version).write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(nStatus, strReply); @@ -185,7 +188,7 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) // Set the URI jreq.URI = req->GetURI(); - std::string strReply; + UniValue reply; bool user_has_whitelist = g_rpc_whitelist.count(jreq.authUser); if (!user_has_whitelist && g_rpc_whitelist_default) { LogPrintf("RPC User %s not allowed to call any methods\n", jreq.authUser); @@ -200,13 +203,23 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) req->WriteReply(HTTP_FORBIDDEN); return false; } - UniValue result = tableRPC.execute(jreq); - // Send reply - strReply = JSONRPCReply(result, NullUniValue, jreq.id); + // Legacy 1.0/1.1 behavior is for failed requests to throw + // exceptions which return HTTP errors and RPC errors to the client. + // 2.0 behavior is to catch exceptions and return HTTP success with + // RPC errors, as long as there is not an actual HTTP server error. + const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2}; + reply = JSONRPCExec(jreq, catch_errors); + + if (jreq.IsNotification()) { + // Even though we do execute notifications, we do not respond to them + req->WriteReply(HTTP_NO_CONTENT); + return true; + } // array of requests } else if (valRequest.isArray()) { + // Check authorization for each request's method if (user_has_whitelist) { for (unsigned int reqIdx = 0; reqIdx < valRequest.size(); reqIdx++) { if (!valRequest[reqIdx].isObject()) { @@ -223,18 +236,49 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) } } } - strReply = JSONRPCExecBatch(jreq, valRequest.get_array()); + + // Execute each request + reply = UniValue::VARR; + for (size_t i{0}; i < valRequest.size(); ++i) { + // Batches never throw HTTP errors, they are always just included + // in "HTTP OK" responses. Notifications never get any response. + UniValue response; + try { + jreq.parse(valRequest[i]); + response = JSONRPCExec(jreq, /*catch_errors=*/true); + } catch (UniValue& e) { + response = JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version); + } catch (const std::exception& e) { + response = JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version); + } + if (!jreq.IsNotification()) { + reply.push_back(std::move(response)); + } + } + // Return no response for an all-notification batch, but only if the + // batch request is non-empty. Technically according to the JSON-RPC + // 2.0 spec, an empty batch request should also return no response, + // However, if the batch request is empty, it means the request did + // not contain any JSON-RPC version numbers, so returning an empty + // response could break backwards compatibility with old RPC clients + // relying on previous behavior. Return an empty array instead of an + // empty response in this case to favor being backwards compatible + // over complying with the JSON-RPC 2.0 spec in this case. + if (reply.size() == 0 && valRequest.size() > 0) { + req->WriteReply(HTTP_NO_CONTENT); + return true; + } } else throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); req->WriteHeader("Content-Type", "application/json"); - req->WriteReply(HTTP_OK, strReply); - } catch (const UniValue& objError) { - JSONErrorReply(req, objError, jreq.id); + req->WriteReply(HTTP_OK, reply.write() + "\n"); + } catch (UniValue& e) { + JSONErrorReply(req, std::move(e), jreq); return false; } catch (const std::exception& e) { - JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq); return false; } return true; diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 71134d442f..b1d4dc9234 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <httpserver.h> diff --git a/src/init.cpp b/src/init.cpp index c19d596c7f..51ade4de93 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <init.h> @@ -34,6 +32,8 @@ #include <interfaces/chain.h> #include <interfaces/init.h> #include <interfaces/node.h> +#include <kernel/context.h> +#include <key.h> #include <logging.h> #include <mapport.h> #include <net.h> @@ -67,17 +67,18 @@ #include <scheduler.h> #include <script/sigcache.h> #include <sync.h> -#include <timedata.h> #include <torcontrol.h> #include <txdb.h> #include <txmempool.h> #include <util/asmap.h> +#include <util/batchpriority.h> #include <util/chaintype.h> #include <util/check.h> #include <util/fs.h> #include <util/fs_helpers.h> #include <util/moneystr.h> #include <util/result.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/syserror.h> @@ -374,6 +375,7 @@ void Shutdown(NodeContext& node) node.chainman.reset(); node.validation_signals.reset(); node.scheduler.reset(); + node.ecc_context.reset(); node.kernel.reset(); RemovePidFile(*node.args); @@ -523,7 +525,6 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> automatic connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection memory usage for the send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #if HAVE_SOCKADDR_UN argsman.AddArg("-onion=<ip:port|path>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy). May be a local file path prefixed with 'unix:'.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -537,16 +538,14 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); - // TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from - // https://github.com/bitcoin/bitcoin/pull/23542 have become widespread. - argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u). Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); #if HAVE_SOCKADDR_UN argsman.AddArg("-proxy=<ip:port|path>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled). May be a local file path prefixed with 'unix:' if the proxy supports it.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); #else argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); #endif 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("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes. During startup, seednodes will be tried before dnsseeds.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); @@ -636,7 +635,7 @@ void SetupServerArgs(ArgsManager& argsman) MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); - argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, + argsman.AddArg("-permitbaremultisig", strprintf("Relay transactions creating non-P2SH multisig outputs (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); @@ -992,10 +991,8 @@ bool AppInitParameterInteraction(const ArgsManager& args) InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); // ********************************************************* Step 3: parameter-to-internal-flags - auto result = init::SetLoggingCategories(args); - if (!result) return InitError(util::ErrorString(result)); - result = init::SetLoggingLevel(args); - if (!result) return InitError(util::ErrorString(result)); + if (auto result{init::SetLoggingCategories(args)}; !result) return InitError(util::ErrorString(result)); + if (auto result{init::SetLoggingLevel(args)}; !result) return InitError(util::ErrorString(result)); nConnectTimeout = args.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT); if (nConnectTimeout <= 0) { @@ -1090,6 +1087,10 @@ bool AppInitSanityChecks(const kernel::Context& kernel) return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } + if (!ECC_InitSanityCheck()) { + return InitError(strprintf(_("Elliptic curve cryptography sanity check failure. %s is shutting down."), PACKAGE_NAME)); + } + // Probe the data directory lock to give an early error message, if possible // We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened, // and a fork will cause weird behavior to it. @@ -1301,7 +1302,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } - for (const auto &port_option : std::vector<std::pair<std::string, bool>>{ + for ([[maybe_unused]] const auto& [arg, unix] : std::vector<std::pair<std::string, bool>>{ // arg name UNIX socket support {"-i2psam", false}, {"-onion", true}, @@ -1313,10 +1314,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) {"-zmqpubhashtx", true}, {"-zmqpubrawblock", true}, {"-zmqpubrawtx", true}, - {"-zmqpubsequence", true} + {"-zmqpubsequence", true}, }) { - const std::string arg{port_option.first}; - const bool unix{port_option.second}; for (const std::string& socket_addr : args.GetArgs(arg)) { std::string host_out; uint16_t port_out{0}; @@ -1743,6 +1742,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } chainman.m_thread_load = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] { + ScheduleBatchPriority(); // Import blocks ImportBlocks(chainman, vImportFiles); if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) { diff --git a/src/init/common.cpp b/src/init/common.cpp index 0800cd93d8..3a6df3e8bd 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <clientversion.h> #include <common/args.h> diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp index 45a5e25093..e4a13ee4cc 100644 --- a/src/kernel/checks.cpp +++ b/src/kernel/checks.cpp @@ -4,8 +4,8 @@ #include <kernel/checks.h> -#include <key.h> #include <random.h> +#include <util/result.h> #include <util/translation.h> #include <memory> @@ -14,10 +14,6 @@ namespace kernel { util::Result<void> SanityChecks(const Context&) { - if (!ECC_InitSanityCheck()) { - return util::Error{Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")}; - } - if (!Random_SanityCheck()) { return util::Error{Untranslated("OS cryptographic RNG sanity check failure. Aborting.")}; } diff --git a/src/kernel/context.cpp b/src/kernel/context.cpp index c60f1638d1..bfb17915fd 100644 --- a/src/kernel/context.cpp +++ b/src/kernel/context.cpp @@ -5,9 +5,7 @@ #include <kernel/context.h> #include <crypto/sha256.h> -#include <key.h> #include <logging.h> -#include <pubkey.h> #include <random.h> #include <string> @@ -19,12 +17,7 @@ Context::Context() std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); RandomInit(); - ECC_Start(); } -Context::~Context() -{ - ECC_Stop(); -} } // namespace kernel diff --git a/src/kernel/context.h b/src/kernel/context.h index ff4df20473..159af12689 100644 --- a/src/kernel/context.h +++ b/src/kernel/context.h @@ -5,10 +5,6 @@ #ifndef BITCOIN_KERNEL_CONTEXT_H #define BITCOIN_KERNEL_CONTEXT_H -#include <util/signalinterrupt.h> - -#include <memory> - namespace kernel { //! Context struct holding the kernel library's logically global state, and //! passed to external libbitcoin_kernel functions which need access to this @@ -19,7 +15,6 @@ namespace kernel { //! should be stored to std::unique_ptr members pointing to opaque types. struct Context { Context(); - ~Context(); }; } // namespace kernel diff --git a/src/kernel/mempool_persist.cpp b/src/kernel/mempool_persist.cpp index f06f609379..53028a45ae 100644 --- a/src/kernel/mempool_persist.cpp +++ b/src/kernel/mempool_persist.cpp @@ -96,7 +96,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active if (amountdelta && opts.apply_fee_delta_priority) { pool.PrioritiseTransaction(tx->GetHash(), amountdelta); } - if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) { + if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) { LOCK(cs_main); const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false); if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) { @@ -174,11 +174,11 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock } try { - const uint64_t version{pool.m_persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION}; + const uint64_t version{pool.m_opts.persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION}; file << version; std::vector<std::byte> xor_key(8); - if (!pool.m_persist_v1_dat) { + if (!pool.m_opts.persist_v1_dat) { FastRandomContext{}.fillrand(xor_key); file << xor_key; } diff --git a/src/key.cpp b/src/key.cpp index 2bd6396298..e8458f2e3b 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -432,7 +432,8 @@ bool ECC_InitSanityCheck() { return key.VerifyPubKey(pubkey); } -void ECC_Start() { +/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ +static void ECC_Start() { assert(secp256k1_context_sign == nullptr); secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -449,7 +450,8 @@ void ECC_Start() { secp256k1_context_sign = ctx; } -void ECC_Stop() { +/** Deinitialize the elliptic curve support. No-op if ECC_Start wasn't called first. */ +static void ECC_Stop() { secp256k1_context *ctx = secp256k1_context_sign; secp256k1_context_sign = nullptr; @@ -457,3 +459,13 @@ void ECC_Stop() { secp256k1_context_destroy(ctx); } } + +ECC_Context::ECC_Context() +{ + ECC_Start(); +} + +ECC_Context::~ECC_Context() +{ + ECC_Stop(); +} @@ -236,13 +236,21 @@ struct CExtKey { void SetSeed(Span<const std::byte> seed); }; -/** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ -void ECC_Start(); - -/** Deinitialize the elliptic curve support. No-op if ECC_Start wasn't called first. */ -void ECC_Stop(); - /** Check that required EC support is available at runtime. */ bool ECC_InitSanityCheck(); +/** + * RAII class initializing and deinitializing global state for elliptic curve support. + * Only one instance may be initialized at a time. + * + * In the future global ECC state could be removed, and this class could contain + * state and be passed as an argument to ECC key functions. + */ +class ECC_Context +{ +public: + ECC_Context(); + ~ECC_Context(); +}; + #endif // BITCOIN_KEY_H diff --git a/src/mapport.cpp b/src/mapport.cpp index 08b365db4b..80670230c7 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <mapport.h> diff --git a/src/net.cpp b/src/net.cpp index 4801f5c1f9..f05b517dd2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <net.h> @@ -198,8 +196,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn) const auto one_week{7 * 24h}; std::vector<CAddress> vSeedsOut; FastRandomContext rng; - DataStream underlying_stream{vSeedsIn}; - ParamsStream s{CAddress::V2_NETWORK, underlying_stream}; + ParamsStream s{DataStream{vSeedsIn}, CAddress::V2_NETWORK}; while (!s.eof()) { CService endpoint; s >> endpoint; @@ -615,7 +612,6 @@ void CNode::CopyStats(CNodeStats& stats) X(m_last_tx_time); X(m_last_block_time); X(m_connected); - X(nTimeOffset); X(m_addr_name); X(nVersion); { @@ -2184,11 +2180,36 @@ void CConnman::WakeMessageHandler() void CConnman::ThreadDNSAddressSeed() { + constexpr int TARGET_OUTBOUND_CONNECTIONS = 2; + int outbound_connection_count = 0; + + if (gArgs.IsArgSet("-seednode")) { + auto start = NodeClock::now(); + constexpr std::chrono::seconds SEEDNODE_TIMEOUT = 30s; + LogPrintf("-seednode enabled. Trying the provided seeds for %d seconds before defaulting to the dnsseeds.\n", SEEDNODE_TIMEOUT.count()); + while (!interruptNet) { + if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) + return; + + // Abort if we have spent enough time without reaching our target. + // Giving seed nodes 30 seconds so this does not become a race against fixedseeds (which triggers after 1 min) + if (NodeClock::now() > start + SEEDNODE_TIMEOUT) { + LogPrintf("Couldn't connect to enough peers via seed nodes. Handing fetch logic to the DNS seeds.\n"); + break; + } + + outbound_connection_count = GetFullOutboundConnCount(); + if (outbound_connection_count >= TARGET_OUTBOUND_CONNECTIONS) { + LogPrintf("P2P peers available. Finished fetching data from seed nodes.\n"); + break; + } + } + } + FastRandomContext rng; std::vector<std::string> seeds = m_params.DNSSeeds(); Shuffle(seeds.begin(), seeds.end(), rng); int seeds_right_now = 0; // Number of seeds left before testing if we have enough connections - int found = 0; if (gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED)) { // When -forcednsseed is provided, query all. @@ -2200,102 +2221,101 @@ void CConnman::ThreadDNSAddressSeed() seeds_right_now = seeds.size(); } - // goal: only query DNS seed if address need is acute - // * If we have a reasonable number of peers in addrman, spend - // some time trying them first. This improves user privacy by - // creating fewer identifying DNS requests, reduces trust by - // giving seeds less influence on the network topology, and - // reduces traffic to the seeds. - // * When querying DNS seeds query a few at once, this ensures - // that we don't give DNS seeds the ability to eclipse nodes - // that query them. - // * If we continue having problems, eventually query all the - // DNS seeds, and if that fails too, also try the fixed seeds. - // (done in ThreadOpenConnections) - const std::chrono::seconds seeds_wait_time = (addrman.Size() >= DNSSEEDS_DELAY_PEER_THRESHOLD ? DNSSEEDS_DELAY_MANY_PEERS : DNSSEEDS_DELAY_FEW_PEERS); - - for (const std::string& seed : seeds) { - if (seeds_right_now == 0) { - seeds_right_now += DNSSEEDS_TO_QUERY_AT_ONCE; - - if (addrman.Size() > 0) { - LogPrintf("Waiting %d seconds before querying DNS seeds.\n", seeds_wait_time.count()); - std::chrono::seconds to_wait = seeds_wait_time; - while (to_wait.count() > 0) { - // if sleeping for the MANY_PEERS interval, wake up - // early to see if we have enough peers and can stop - // this thread entirely freeing up its resources - std::chrono::seconds w = std::min(DNSSEEDS_DELAY_FEW_PEERS, to_wait); - if (!interruptNet.sleep_for(w)) return; - to_wait -= w; - - int nRelevant = 0; - { - LOCK(m_nodes_mutex); - for (const CNode* pnode : m_nodes) { - if (pnode->fSuccessfullyConnected && pnode->IsFullOutboundConn()) ++nRelevant; - } - } - if (nRelevant >= 2) { - if (found > 0) { - LogPrintf("%d addresses found from DNS seeds\n", found); - LogPrintf("P2P peers available. Finished DNS seeding.\n"); - } else { - LogPrintf("P2P peers available. Skipped DNS seeding.\n"); + // Proceed with dnsseeds if seednodes hasn't reached the target or if forcednsseed is set + if (outbound_connection_count < TARGET_OUTBOUND_CONNECTIONS || seeds_right_now) { + // goal: only query DNS seed if address need is acute + // * If we have a reasonable number of peers in addrman, spend + // some time trying them first. This improves user privacy by + // creating fewer identifying DNS requests, reduces trust by + // giving seeds less influence on the network topology, and + // reduces traffic to the seeds. + // * When querying DNS seeds query a few at once, this ensures + // that we don't give DNS seeds the ability to eclipse nodes + // that query them. + // * If we continue having problems, eventually query all the + // DNS seeds, and if that fails too, also try the fixed seeds. + // (done in ThreadOpenConnections) + int found = 0; + const std::chrono::seconds seeds_wait_time = (addrman.Size() >= DNSSEEDS_DELAY_PEER_THRESHOLD ? DNSSEEDS_DELAY_MANY_PEERS : DNSSEEDS_DELAY_FEW_PEERS); + + for (const std::string& seed : seeds) { + if (seeds_right_now == 0) { + seeds_right_now += DNSSEEDS_TO_QUERY_AT_ONCE; + + if (addrman.Size() > 0) { + LogPrintf("Waiting %d seconds before querying DNS seeds.\n", seeds_wait_time.count()); + std::chrono::seconds to_wait = seeds_wait_time; + while (to_wait.count() > 0) { + // if sleeping for the MANY_PEERS interval, wake up + // early to see if we have enough peers and can stop + // this thread entirely freeing up its resources + std::chrono::seconds w = std::min(DNSSEEDS_DELAY_FEW_PEERS, to_wait); + if (!interruptNet.sleep_for(w)) return; + to_wait -= w; + + if (GetFullOutboundConnCount() >= TARGET_OUTBOUND_CONNECTIONS) { + if (found > 0) { + LogPrintf("%d addresses found from DNS seeds\n", found); + LogPrintf("P2P peers available. Finished DNS seeding.\n"); + } else { + LogPrintf("P2P peers available. Skipped DNS seeding.\n"); + } + return; } - return; } } } - } - if (interruptNet) return; - - // hold off on querying seeds if P2P network deactivated - if (!fNetworkActive) { - LogPrintf("Waiting for network to be reactivated before querying DNS seeds.\n"); - do { - if (!interruptNet.sleep_for(std::chrono::seconds{1})) return; - } while (!fNetworkActive); - } + if (interruptNet) return; - LogPrintf("Loading addresses from DNS seed %s\n", seed); - // If -proxy is in use, we make an ADDR_FETCH connection to the DNS resolved peer address - // for the base dns seed domain in chainparams - if (HaveNameProxy()) { - AddAddrFetch(seed); - } else { - std::vector<CAddress> vAdd; - constexpr ServiceFlags requiredServiceBits{SeedsServiceFlags()}; - std::string host = strprintf("x%x.%s", requiredServiceBits, seed); - CNetAddr resolveSource; - if (!resolveSource.SetInternal(host)) { - continue; + // hold off on querying seeds if P2P network deactivated + if (!fNetworkActive) { + LogPrintf("Waiting for network to be reactivated before querying DNS seeds.\n"); + do { + if (!interruptNet.sleep_for(std::chrono::seconds{1})) return; + } while (!fNetworkActive); } - // Limit number of IPs learned from a single DNS seed. This limit exists to prevent the results from - // one DNS seed from dominating AddrMan. Note that the number of results from a UDP DNS query is - // bounded to 33 already, but it is possible for it to use TCP where a larger number of results can be - // returned. - unsigned int nMaxIPs = 32; - const auto addresses{LookupHost(host, nMaxIPs, true)}; - if (!addresses.empty()) { - for (const CNetAddr& ip : addresses) { - CAddress addr = CAddress(CService(ip, m_params.GetDefaultPort()), requiredServiceBits); - addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - 3 * 24h, -4 * 24h); // use a random age between 3 and 7 days old - vAdd.push_back(addr); - found++; - } - addrman.Add(vAdd, resolveSource); - } else { - // If the seed does not support a subdomain with our desired service bits, - // we make an ADDR_FETCH connection to the DNS resolved peer address for the - // base dns seed domain in chainparams + + LogPrintf("Loading addresses from DNS seed %s\n", seed); + // If -proxy is in use, we make an ADDR_FETCH connection to the DNS resolved peer address + // for the base dns seed domain in chainparams + if (HaveNameProxy()) { AddAddrFetch(seed); + } else { + std::vector<CAddress> vAdd; + constexpr ServiceFlags requiredServiceBits{SeedsServiceFlags()}; + std::string host = strprintf("x%x.%s", requiredServiceBits, seed); + CNetAddr resolveSource; + if (!resolveSource.SetInternal(host)) { + continue; + } + // Limit number of IPs learned from a single DNS seed. This limit exists to prevent the results from + // one DNS seed from dominating AddrMan. Note that the number of results from a UDP DNS query is + // bounded to 33 already, but it is possible for it to use TCP where a larger number of results can be + // returned. + unsigned int nMaxIPs = 32; + const auto addresses{LookupHost(host, nMaxIPs, true)}; + if (!addresses.empty()) { + for (const CNetAddr& ip : addresses) { + CAddress addr = CAddress(CService(ip, m_params.GetDefaultPort()), requiredServiceBits); + addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - 3 * 24h, -4 * 24h); // use a random age between 3 and 7 days old + vAdd.push_back(addr); + found++; + } + addrman.Add(vAdd, resolveSource); + } else { + // If the seed does not support a subdomain with our desired service bits, + // we make an ADDR_FETCH connection to the DNS resolved peer address for the + // base dns seed domain in chainparams + AddAddrFetch(seed); + } } + --seeds_right_now; } - --seeds_right_now; + LogPrintf("%d addresses found from DNS seeds\n", found); + } else { + LogPrintf("Skipping DNS seeds. Enough peers have been found\n"); } - LogPrintf("%d addresses found from DNS seeds\n", found); } void CConnman::DumpAddresses() @@ -2346,6 +2366,19 @@ void CConnman::StartExtraBlockRelayPeers() m_start_extra_block_relay_peers = true; } +// Return the number of outbound connections that are full relay (not blocks only) +int CConnman::GetFullOutboundConnCount() const +{ + int nRelevant = 0; + { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { + if (pnode->fSuccessfullyConnected && pnode->IsFullOutboundConn()) ++nRelevant; + } + } + return nRelevant; +} + // Return the number of peers we have over our outbound connection limit // Exclude peers that are marked for disconnect, or are going to be // disconnected soon (eg ADDR_FETCH and FEELER) @@ -2798,7 +2831,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo(bool include_connected) co } for (const auto& addr : lAddresses) { - CService service(LookupNumeric(addr.m_added_node, GetDefaultPort(addr.m_added_node))); + CService service{MaybeFlipIPv6toCJDNS(LookupNumeric(addr.m_added_node, GetDefaultPort(addr.m_added_node)))}; AddedNodeInfo addedNode{addr, CService(), false, false}; if (service.IsValid()) { // strAddNode is an IP:port @@ -3077,8 +3110,7 @@ void Discover() { if (ifa->ifa_addr == nullptr) continue; if ((ifa->ifa_flags & IFF_UP) == 0) continue; - if (strcmp(ifa->ifa_name, "lo") == 0) continue; - if (strcmp(ifa->ifa_name, "lo0") == 0) continue; + if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) continue; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr); @@ -137,8 +137,7 @@ struct CSerializedNetMsg { /** * Look up IP addresses from all interfaces on the machine and add them to the * list of local addresses to self-advertise. - * The loopback interface is skipped and only the first address from each - * interface is used. + * The loopback interface is skipped. */ void Discover(); @@ -191,7 +190,6 @@ public: std::chrono::seconds m_last_tx_time; std::chrono::seconds m_last_block_time; std::chrono::seconds m_connected; - int64_t nTimeOffset; std::string m_addr_name; int nVersion; std::string cleanSubVer; @@ -703,7 +701,6 @@ public: std::atomic<std::chrono::seconds> m_last_recv{0s}; //! Unix epoch time at peer connection const std::chrono::seconds m_connected; - std::atomic<int64_t> nTimeOffset{0}; // Address of this peer const CAddress addr; // Bind address of our side of the connection @@ -1175,6 +1172,8 @@ public: void StartExtraBlockRelayPeers(); + // Count the number of full-relay peer we have. + int GetFullOutboundConnCount() const; // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set // to false, we may have extra peers that we wish to disconnect). This may diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 39ffff97d2..6374cb52c1 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -23,6 +23,7 @@ #include <netbase.h> #include <netmessagemaker.h> #include <node/blockstorage.h> +#include <node/timeoffsets.h> #include <node/txreconciliation.h> #include <policy/fees.h> #include <policy/policy.h> @@ -34,7 +35,6 @@ #include <scheduler.h> #include <streams.h> #include <sync.h> -#include <timedata.h> #include <tinyformat.h> #include <txmempool.h> #include <txorphanage.h> @@ -118,6 +118,7 @@ static const unsigned int MAX_HEADERS_RESULTS = 2000; static const int MAX_CMPCTBLOCK_DEPTH = 5; /** Maximum depth of blocks we're willing to respond to GETBLOCKTXN requests for. */ static const int MAX_BLOCKTXN_DEPTH = 10; +static_assert(MAX_BLOCKTXN_DEPTH <= MIN_BLOCKS_TO_KEEP, "MAX_BLOCKTXN_DEPTH too high"); /** Size of the "block download window": how far ahead of our current height do we fetch? * Larger windows tolerate larger download speed differences between peer, but increase the potential * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably @@ -390,6 +391,10 @@ struct Peer { /** Whether this peer wants invs or headers (when possible) for block announcements */ bool m_prefers_headers GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; + /** Time offset computed during the version handshake based on the + * timestamp the peer sent in the version message. */ + std::atomic<std::chrono::seconds> m_time_offset{0s}; + explicit Peer(NodeId id, ServiceFlags our_services) : m_id{id} , m_our_services{our_services} @@ -513,7 +518,7 @@ public: std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - bool IgnoresIncomingTxs() override { return m_opts.ignore_incoming_txs; } + PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); 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 SetBestBlock(int height, std::chrono::seconds time) override @@ -586,7 +591,7 @@ private: * @param[in] maybe_add_extra_compact_tx Whether this tx should be added to vExtraTxnForCompact. * Set to false if the tx has already been rejected before, * e.g. is an orphan, to avoid adding duplicate entries. - * Updates m_txrequest, m_recent_rejects, m_orphanage, and vExtraTxnForCompact. */ + * Updates m_txrequest, m_recent_rejects, m_recent_rejects_reconsiderable, m_orphanage, and vExtraTxnForCompact. */ void ProcessInvalidTx(NodeId nodeid, const CTransactionRef& tx, const TxValidationState& result, bool maybe_add_extra_compact_tx) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main); @@ -596,6 +601,42 @@ private: void ProcessValidTx(NodeId nodeid, const CTransactionRef& tx, const std::list<CTransactionRef>& replaced_transactions) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main); + struct PackageToValidate { + const Package m_txns; + const std::vector<NodeId> m_senders; + /** Construct a 1-parent-1-child package. */ + explicit PackageToValidate(const CTransactionRef& parent, + const CTransactionRef& child, + NodeId parent_sender, + NodeId child_sender) : + m_txns{parent, child}, + m_senders {parent_sender, child_sender} + {} + + std::string ToString() const { + Assume(m_txns.size() == 2); + return strprintf("parent %s (wtxid=%s, sender=%d) + child %s (wtxid=%s, sender=%d)", + m_txns.front()->GetHash().ToString(), + m_txns.front()->GetWitnessHash().ToString(), + m_senders.front(), + m_txns.back()->GetHash().ToString(), + m_txns.back()->GetWitnessHash().ToString(), + m_senders.back()); + } + }; + + /** Handle the results of package validation: calls ProcessValidTx and ProcessInvalidTx for + * individual transactions, and caches rejection for the package as a group. + */ + void ProcessPackageResult(const PackageToValidate& package_to_validate, const PackageMempoolAcceptResult& package_result) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main); + + /** Look for a child of this transaction in the orphanage to form a 1-parent-1-child package, + * skipping any combinations that have already been tried. Return the resulting package along with + * the senders of its respective transactions, or std::nullopt if no package is found. */ + std::optional<PackageToValidate> Find1P1CPackage(const CTransactionRef& ptx, NodeId nodeid) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main); + /** * Reconsider orphan transactions after a parent has been accepted to the mempool. * @@ -749,6 +790,8 @@ private: /** Next time to check for stale tip */ std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s}; + TimeOffsets m_outbound_time_offsets; + const Options m_opts; bool RejectIncomingTxs(const CNode& peer) const; @@ -806,7 +849,16 @@ private: /** Stalling timeout for blocks in IBD */ std::atomic<std::chrono::seconds> m_block_stalling_timeout{BLOCK_STALLING_TIMEOUT_DEFAULT}; - bool AlreadyHaveTx(const GenTxid& gtxid) + /** Check whether we already have this gtxid in: + * - mempool + * - orphanage + * - m_recent_rejects + * - m_recent_rejects_reconsiderable (if include_reconsiderable = true) + * - m_recent_confirmed_transactions + * Also responsible for resetting m_recent_rejects and m_recent_rejects_reconsiderable if the + * chain tip has changed. + * */ + bool AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable) EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_recent_confirmed_transactions_mutex); /** @@ -844,8 +896,32 @@ private: * Memory used: 1.3 MB */ CRollingBloomFilter m_recent_rejects GUARDED_BY(::cs_main){120'000, 0.000'001}; + /** Block hash of chain tip the last time we reset m_recent_rejects and + * m_recent_rejects_reconsiderable. */ uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main); + /** + * Filter for: + * (1) wtxids of transactions that were recently rejected by the mempool but are + * eligible for reconsideration if submitted with other transactions. + * (2) packages (see GetPackageHash) we have already rejected before and should not retry. + * + * Similar to m_recent_rejects, this filter is used to save bandwidth when e.g. all of our peers + * have larger mempools and thus lower minimum feerates than us. + * + * When a transaction's error is TxValidationResult::TX_RECONSIDERABLE (in a package or by + * itself), add its wtxid to this filter. When a package fails for any reason, add the combined + * hash to this filter. + * + * Upon receiving an announcement for a transaction, if it exists in this filter, do not + * download the txdata. When considering packages, if it exists in this filter, drop it. + * + * Reset this filter when the chain tip changes. + * + * Parameters are picked to be the same as m_recent_rejects, with the same rationale. + */ + CRollingBloomFilter m_recent_rejects_reconsiderable GUARDED_BY(::cs_main){120'000, 0.000'001}; + /* * Filter for transactions that have been recently confirmed. * We use this to avoid requesting transactions that have already been @@ -1792,10 +1868,19 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c stats.presync_height = peer->m_headers_sync->GetPresyncHeight(); } } + stats.time_offset = peer->m_time_offset; return true; } +PeerManagerInfo PeerManagerImpl::GetInfo() const +{ + return PeerManagerInfo{ + .median_outbound_time_offset = m_outbound_time_offsets.Median(), + .ignores_incoming_txs = m_opts.ignore_incoming_txs, + }; +} + void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) { if (m_opts.max_extra_txs <= 0) @@ -2194,7 +2279,7 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta // -bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) +bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable) { if (m_chainman.ActiveChain().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { // If the chain tip has changed previously rejected transactions @@ -2203,11 +2288,30 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) // txs a second chance. hashRecentRejectsChainTip = m_chainman.ActiveChain().Tip()->GetBlockHash(); m_recent_rejects.reset(); + m_recent_rejects_reconsiderable.reset(); } const uint256& hash = gtxid.GetHash(); - if (m_orphanage.HaveTx(gtxid)) return true; + if (gtxid.IsWtxid()) { + // Normal query by wtxid. + if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true; + } else { + // Never query by txid: it is possible that the transaction in the orphanage has the same + // txid but a different witness, which would give us a false positive result. If we decided + // not to request the transaction based on this result, an attacker could prevent us from + // downloading a transaction by intentionally creating a malleated version of it. While + // only one (or none!) of these transactions can ultimately be confirmed, we have no way of + // discerning which one that is, so the orphanage can store multiple transactions with the + // same txid. + // + // While we won't query by txid, we can try to "guess" what the wtxid is based on the txid. + // A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will + // help us find non-segwit transactions, saving bandwidth, and should have no false positives. + if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true; + } + + if (include_reconsiderable && m_recent_rejects_reconsiderable.contains(hash)) return true; { LOCK(m_recent_confirmed_transactions_mutex); @@ -2333,38 +2437,48 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& } } - LOCK(cs_main); - const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); - if (!pindex) { - return; - } - if (!BlockRequestAllowed(pindex)) { - LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId()); - return; - } - // disconnect node in case we have reached the outbound limit for serving historical blocks - if (m_connman.OutboundTargetReached(true) && - (((m_chainman.m_best_header != nullptr) && (m_chainman.m_best_header->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && - !pfrom.HasPermission(NetPermissionFlags::Download) // nodes with the download permission may exceed target - ) { - LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId()); - pfrom.fDisconnect = true; - return; - } - // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (!pfrom.HasPermission(NetPermissionFlags::NoBan) && ( - (((peer.m_our_services & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((peer.m_our_services & NODE_NETWORK) != NODE_NETWORK) && (m_chainman.ActiveChain().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) - )) { - LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold, disconnect peer=%d\n", pfrom.GetId()); - //disconnect node and prevent it from stalling (would otherwise wait for the missing block) - pfrom.fDisconnect = true; - return; - } - // Pruned nodes may have deleted the block, so check whether - // it's available before trying to send. - if (!(pindex->nStatus & BLOCK_HAVE_DATA)) { - return; + const CBlockIndex* pindex{nullptr}; + const CBlockIndex* tip{nullptr}; + bool can_direct_fetch{false}; + FlatFilePos block_pos{}; + { + LOCK(cs_main); + pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); + if (!pindex) { + return; + } + if (!BlockRequestAllowed(pindex)) { + LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId()); + return; + } + // disconnect node in case we have reached the outbound limit for serving historical blocks + if (m_connman.OutboundTargetReached(true) && + (((m_chainman.m_best_header != nullptr) && (m_chainman.m_best_header->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && + !pfrom.HasPermission(NetPermissionFlags::Download) // nodes with the download permission may exceed target + ) { + LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId()); + pfrom.fDisconnect = true; + return; + } + tip = m_chainman.ActiveChain().Tip(); + // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold + if (!pfrom.HasPermission(NetPermissionFlags::NoBan) && ( + (((peer.m_our_services & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((peer.m_our_services & NODE_NETWORK) != NODE_NETWORK) && (tip->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + )) { + LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold, disconnect peer=%d\n", pfrom.GetId()); + //disconnect node and prevent it from stalling (would otherwise wait for the missing block) + pfrom.fDisconnect = true; + return; + } + // Pruned nodes may have deleted the block, so check whether + // it's available before trying to send. + if (!(pindex->nStatus & BLOCK_HAVE_DATA)) { + return; + } + can_direct_fetch = CanDirectFetch(); + block_pos = pindex->GetBlockPos(); } + std::shared_ptr<const CBlock> pblock; if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { pblock = a_recent_block; @@ -2372,16 +2486,28 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& // Fast-path: in this case it is possible to serve the block directly from disk, // as the network format matches the format on disk std::vector<uint8_t> block_data; - if (!m_chainman.m_blockman.ReadRawBlockFromDisk(block_data, pindex->GetBlockPos())) { - assert(!"cannot load block from disk"); + if (!m_chainman.m_blockman.ReadRawBlockFromDisk(block_data, block_pos)) { + if (WITH_LOCK(m_chainman.GetMutex(), return m_chainman.m_blockman.IsBlockPruned(*pindex))) { + LogPrint(BCLog::NET, "Block was pruned before it could be read, disconnect peer=%s\n", pfrom.GetId()); + } else { + LogError("Cannot load block from disk, disconnect peer=%d\n", pfrom.GetId()); + } + pfrom.fDisconnect = true; + return; } MakeAndPushMessage(pfrom, NetMsgType::BLOCK, Span{block_data}); // Don't set pblock as we've sent the block } else { // Send block from disk std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>(); - if (!m_chainman.m_blockman.ReadBlockFromDisk(*pblockRead, *pindex)) { - assert(!"cannot load block from disk"); + if (!m_chainman.m_blockman.ReadBlockFromDisk(*pblockRead, block_pos)) { + if (WITH_LOCK(m_chainman.GetMutex(), return m_chainman.m_blockman.IsBlockPruned(*pindex))) { + LogPrint(BCLog::NET, "Block was pruned before it could be read, disconnect peer=%s\n", pfrom.GetId()); + } else { + LogError("Cannot load block from disk, disconnect peer=%d\n", pfrom.GetId()); + } + pfrom.fDisconnect = true; + return; } pblock = pblockRead; } @@ -2419,7 +2545,7 @@ 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. - if (CanDirectFetch() && pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_CMPCTBLOCK_DEPTH) { + if (can_direct_fetch && pindex->nHeight >= tip->nHeight - MAX_CMPCTBLOCK_DEPTH) { if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block); } else { @@ -2440,7 +2566,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& // and we want it right after the last block so they don't // wait for other stuff first. std::vector<CInv> vInv; - vInv.emplace_back(MSG_BLOCK, m_chainman.ActiveChain().Tip()->GetBlockHash()); + vInv.emplace_back(MSG_BLOCK, tip->GetBlockHash()); MakeAndPushMessage(pfrom, NetMsgType::INV, vInv); peer.m_continuation_block.SetNull(); } @@ -3097,7 +3223,14 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx // See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034 // for concerns around weakening security of unupgraded nodes // if we start doing this too early. - m_recent_rejects.insert(ptx->GetWitnessHash().ToUint256()); + if (state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { + // If the result is TX_RECONSIDERABLE, add it to m_recent_rejects_reconsiderable + // because we should not download or submit this transaction by itself again, but may + // submit it as part of a package later. + m_recent_rejects_reconsiderable.insert(ptx->GetWitnessHash().ToUint256()); + } else { + m_recent_rejects.insert(ptx->GetWitnessHash().ToUint256()); + } m_txrequest.ForgetTxHash(ptx->GetWitnessHash()); // If the transaction failed for TX_INPUTS_NOT_STANDARD, // then we know that the witness was irrelevant to the policy @@ -3107,6 +3240,8 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx // processing of this transaction in the event that child // transactions are later received (resulting in // parent-fetching by txid via the orphan-handling logic). + // We only add the txid if it differs from the wtxid, to avoid wasting entries in the + // rolling bloom filter. if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && ptx->HasWitness()) { m_recent_rejects.insert(ptx->GetHash().ToUint256()); m_txrequest.ForgetTxHash(ptx->GetHash()); @@ -3120,7 +3255,7 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx // If the tx failed in ProcessOrphanTx, it should be removed from the orphanage unless the // tx was still missing inputs. If the tx was not in the orphanage, EraseTx does nothing and returns 0. - if (Assume(state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) && m_orphanage.EraseTx(ptx->GetHash()) > 0) { + if (Assume(state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) && m_orphanage.EraseTx(ptx->GetWitnessHash()) > 0) { LogDebug(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s)\n", ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()); } } @@ -3138,7 +3273,7 @@ void PeerManagerImpl::ProcessValidTx(NodeId nodeid, const CTransactionRef& tx, c m_orphanage.AddChildrenToWorkSet(*tx); // If it came from the orphanage, remove it. No-op if the tx is not in txorphanage. - m_orphanage.EraseTx(tx->GetHash()); + m_orphanage.EraseTx(tx->GetWitnessHash()); LogDebug(BCLog::MEMPOOL, "AcceptToMemoryPool: peer=%d: accepted %s (wtxid=%s) (poolsz %u txn, %u kB)\n", nodeid, @@ -3153,6 +3288,116 @@ void PeerManagerImpl::ProcessValidTx(NodeId nodeid, const CTransactionRef& tx, c } } +void PeerManagerImpl::ProcessPackageResult(const PackageToValidate& package_to_validate, const PackageMempoolAcceptResult& package_result) +{ + AssertLockNotHeld(m_peer_mutex); + AssertLockHeld(g_msgproc_mutex); + AssertLockHeld(cs_main); + + const auto& package = package_to_validate.m_txns; + const auto& senders = package_to_validate.m_senders; + + if (package_result.m_state.IsInvalid()) { + m_recent_rejects_reconsiderable.insert(GetPackageHash(package)); + } + // We currently only expect to process 1-parent-1-child packages. Remove if this changes. + if (!Assume(package.size() == 2)) return; + + // Iterate backwards to erase in-package descendants from the orphanage before they become + // relevant in AddChildrenToWorkSet. + auto package_iter = package.rbegin(); + auto senders_iter = senders.rbegin(); + while (package_iter != package.rend()) { + const auto& tx = *package_iter; + const NodeId nodeid = *senders_iter; + const auto it_result{package_result.m_tx_results.find(tx->GetWitnessHash())}; + + // It is not guaranteed that a result exists for every transaction. + if (it_result != package_result.m_tx_results.end()) { + const auto& tx_result = it_result->second; + switch (tx_result.m_result_type) { + case MempoolAcceptResult::ResultType::VALID: + { + ProcessValidTx(nodeid, tx, tx_result.m_replaced_transactions); + break; + } + case MempoolAcceptResult::ResultType::INVALID: + case MempoolAcceptResult::ResultType::DIFFERENT_WITNESS: + { + // Don't add to vExtraTxnForCompact, as these transactions should have already been + // added there when added to the orphanage or rejected for TX_RECONSIDERABLE. + // This should be updated if package submission is ever used for transactions + // that haven't already been validated before. + ProcessInvalidTx(nodeid, tx, tx_result.m_state, /*maybe_add_extra_compact_tx=*/false); + break; + } + case MempoolAcceptResult::ResultType::MEMPOOL_ENTRY: + { + // AlreadyHaveTx() should be catching transactions that are already in mempool. + Assume(false); + break; + } + } + } + package_iter++; + senders_iter++; + } +} + +std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::Find1P1CPackage(const CTransactionRef& ptx, NodeId nodeid) +{ + AssertLockNotHeld(m_peer_mutex); + AssertLockHeld(g_msgproc_mutex); + AssertLockHeld(cs_main); + + const auto& parent_wtxid{ptx->GetWitnessHash()}; + + Assume(m_recent_rejects_reconsiderable.contains(parent_wtxid.ToUint256())); + + // Prefer children from this peer. This helps prevent censorship attempts in which an attacker + // sends lots of fake children for the parent, and we (unluckily) keep selecting the fake + // children instead of the real one provided by the honest peer. + const auto cpfp_candidates_same_peer{m_orphanage.GetChildrenFromSamePeer(ptx, nodeid)}; + + // These children should be sorted from newest to oldest. In the (probably uncommon) case + // of children that replace each other, this helps us accept the highest feerate (probably the + // most recent) one efficiently. + for (const auto& child : cpfp_candidates_same_peer) { + Package maybe_cpfp_package{ptx, child}; + if (!m_recent_rejects_reconsiderable.contains(GetPackageHash(maybe_cpfp_package))) { + return PeerManagerImpl::PackageToValidate{ptx, child, nodeid, nodeid}; + } + } + + // If no suitable candidate from the same peer is found, also try children that were provided by + // a different peer. This is useful because sometimes multiple peers announce both transactions + // to us, and we happen to download them from different peers (we wouldn't have known that these + // 2 transactions are related). We still want to find 1p1c packages then. + // + // If we start tracking all announcers of orphans, we can restrict this logic to parent + child + // pairs in which both were provided by the same peer, i.e. delete this step. + const auto cpfp_candidates_different_peer{m_orphanage.GetChildrenFromDifferentPeer(ptx, nodeid)}; + + // Find the first 1p1c that hasn't already been rejected. We randomize the order to not + // create a bias that attackers can use to delay package acceptance. + // + // Create a random permutation of the indices. + std::vector<size_t> tx_indices(cpfp_candidates_different_peer.size()); + std::iota(tx_indices.begin(), tx_indices.end(), 0); + Shuffle(tx_indices.begin(), tx_indices.end(), m_rng); + + for (const auto index : tx_indices) { + // If we already tried a package and failed for any reason, the combined hash was + // cached in m_recent_rejects_reconsiderable. + const auto [child_tx, child_sender] = cpfp_candidates_different_peer.at(index); + Package maybe_cpfp_package{ptx, child_tx}; + if (!m_recent_rejects_reconsiderable.contains(GetPackageHash(maybe_cpfp_package))) { + return PeerManagerImpl::PackageToValidate{ptx, child_tx, nodeid, child_sender}; + } + } + return std::nullopt; +} + bool PeerManagerImpl::ProcessOrphanTx(Peer& peer) { AssertLockHeld(g_msgproc_mutex); @@ -3168,9 +3413,7 @@ bool PeerManagerImpl::ProcessOrphanTx(Peer& peer) if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { LogPrint(BCLog::TXPACKAGES, " accepted orphan tx %s (wtxid=%s)\n", orphanHash.ToString(), orphan_wtxid.ToString()); - Assume(result.m_replaced_transactions.has_value()); - std::list<CTransactionRef> empty_replacement_list; - ProcessValidTx(peer.m_id, porphanTx, result.m_replaced_transactions.value_or(empty_replacement_list)); + ProcessValidTx(peer.m_id, porphanTx, result.m_replaced_transactions); return true; } else if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) { LogPrint(BCLog::TXPACKAGES, " invalid orphan tx %s (wtxid=%s) from peer=%d. %s\n", @@ -3666,12 +3909,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, peer->m_starting_height, addrMe.ToStringAddrPort(), fRelay, pfrom.GetId(), remoteAddr, (mapped_as ? strprintf(", mapped_as=%d", mapped_as) : "")); - int64_t nTimeOffset = nTime - GetTime(); - pfrom.nTimeOffset = nTimeOffset; + peer->m_time_offset = NodeSeconds{std::chrono::seconds{nTime}} - Now<NodeSeconds>(); if (!pfrom.IsInboundConn()) { // Don't use timedata samples from inbound peers to make it - // harder for others to tamper with our adjusted time. - AddTimeData(pfrom.addr, nTimeOffset); + // harder for others to create false warnings about our clock being out of sync. + m_outbound_time_offsets.Add(peer->m_time_offset); + m_outbound_time_offsets.WarnIfOutOfSync(); } // If the peer is old enough to have the old alert system, send it the final alert. @@ -4013,7 +4256,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } const GenTxid gtxid = ToGenTxid(inv); - const bool fAlreadyHave = AlreadyHaveTx(gtxid); + const bool fAlreadyHave = AlreadyHaveTx(gtxid, /*include_reconsiderable=*/true); LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); AddKnownTx(*peer, inv.hash); @@ -4162,6 +4405,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + FlatFilePos block_pos{}; { LOCK(cs_main); @@ -4172,15 +4416,21 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_BLOCKTXN_DEPTH) { - CBlock block; - const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pindex)}; - assert(ret); - - SendBlockTransactions(pfrom, *peer, block, req); - return; + block_pos = pindex->GetBlockPos(); } } + if (!block_pos.IsNull()) { + CBlock block; + const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, block_pos)}; + // If height is above MAX_BLOCKTXN_DEPTH then this block cannot get + // pruned after we release cs_main above, so this read should never fail. + assert(ret); + + SendBlockTransactions(pfrom, *peer, block, req); + return; + } + // If an older block is requested (should never happen in practice, // but can happen in tests) send a block response instead of a // blocktxn response. Sending a full block response instead of a @@ -4318,7 +4568,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // already; and an adversary can already relay us old transactions // (older than our recency filter) if trying to DoS us, without any need // for witness malleation. - if (AlreadyHaveTx(GenTxid::Wtxid(wtxid))) { + if (AlreadyHaveTx(GenTxid::Wtxid(wtxid), /*include_reconsiderable=*/true)) { if (pfrom.HasPermission(NetPermissionFlags::ForceRelay)) { // Always relay transactions received from peers with forcerelay // permission, even if they were already in the mempool, allowing @@ -4332,6 +4582,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); } } + + if (m_recent_rejects_reconsiderable.contains(wtxid)) { + // When a transaction is already in m_recent_rejects_reconsiderable, we shouldn't submit + // it by itself again. However, look for a matching child in the orphanage, as it is + // possible that they succeed as a package. + LogPrint(BCLog::TXPACKAGES, "found tx %s (wtxid=%s) in reconsiderable rejects, looking for child in orphanage\n", + txid.ToString(), wtxid.ToString()); + if (auto package_to_validate{Find1P1CPackage(ptx, pfrom.GetId())}) { + const auto package_result{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)}; + LogDebug(BCLog::TXPACKAGES, "package evaluation for %s: %s\n", package_to_validate->ToString(), + package_result.m_state.IsValid() ? "package accepted" : "package rejected"); + ProcessPackageResult(package_to_validate.value(), package_result); + } + } // If a tx is detected by m_recent_rejects it is ignored. Because we haven't // submitted the tx to our mempool, we won't have computed a DoS // score for it or determined exactly why we consider it invalid. @@ -4354,7 +4618,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const TxValidationState& state = result.m_state; if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { - ProcessValidTx(pfrom.GetId(), ptx, result.m_replaced_transactions.value()); + ProcessValidTx(pfrom.GetId(), ptx, result.m_replaced_transactions); pfrom.m_last_tx_time = GetTime<std::chrono::seconds>(); } else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) @@ -4371,10 +4635,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } std::sort(unique_parents.begin(), unique_parents.end()); unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end()); + + // Distinguish between parents in m_recent_rejects and m_recent_rejects_reconsiderable. + // We can tolerate having up to 1 parent in m_recent_rejects_reconsiderable since we + // submit 1p1c packages. However, fail immediately if any are in m_recent_rejects. + std::optional<uint256> rejected_parent_reconsiderable; for (const uint256& parent_txid : unique_parents) { if (m_recent_rejects.contains(parent_txid)) { fRejectedParents = true; break; + } else if (m_recent_rejects_reconsiderable.contains(parent_txid) && !m_mempool.exists(GenTxid::Txid(parent_txid))) { + // More than 1 parent in m_recent_rejects_reconsiderable: 1p1c will not be + // sufficient to accept this package, so just give up here. + if (rejected_parent_reconsiderable.has_value()) { + fRejectedParents = true; + break; + } + rejected_parent_reconsiderable = parent_txid; } } if (!fRejectedParents) { @@ -4388,7 +4665,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // protocol for getting all unconfirmed parents. const auto gtxid{GenTxid::Txid(parent_txid)}; AddKnownTx(*peer, parent_txid); - if (!AlreadyHaveTx(gtxid)) AddTxAnnouncement(pfrom, gtxid, current_time); + // Exclude m_recent_rejects_reconsiderable: the missing parent may have been + // previously rejected for being too low feerate. This orphan might CPFP it. + if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) AddTxAnnouncement(pfrom, gtxid, current_time); } if (m_orphanage.AddTx(ptx, pfrom.GetId())) { @@ -4420,6 +4699,19 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (state.IsInvalid()) { ProcessInvalidTx(pfrom.GetId(), ptx, state, /*maybe_add_extra_compact_tx=*/true); } + // When a transaction fails for TX_RECONSIDERABLE, look for a matching child in the + // orphanage, as it is possible that they succeed as a package. + if (state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { + LogPrint(BCLog::TXPACKAGES, "tx %s (wtxid=%s) failed but reconsiderable, looking for child in orphanage\n", + txid.ToString(), wtxid.ToString()); + if (auto package_to_validate{Find1P1CPackage(ptx, pfrom.GetId())}) { + const auto package_result{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)}; + LogDebug(BCLog::TXPACKAGES, "package evaluation for %s: %s\n", package_to_validate->ToString(), + package_result.m_state.IsValid() ? "package accepted" : "package rejected"); + ProcessPackageResult(package_to_validate.value(), package_result); + } + } + return; } @@ -4951,7 +5243,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, std::vector<unsigned char> vData; vRecv >> vData; - // Nodes must NEVER send a data item > 520 bytes (the max size for a script data object, + // Nodes must NEVER send a data item > MAX_SCRIPT_ELEMENT_SIZE bytes (the max size for a script data object, // and thus, the maximum size any matched object can have) in a filteradd message bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { @@ -5165,16 +5457,19 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seco // unless it's invalid, in which case we should find that out and // disconnect from them elsewhere). if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork) { + // The outbound peer has sent us a block with at least as much work as our current tip, so reset the timeout if it was set if (state.m_chain_sync.m_timeout != 0s) { state.m_chain_sync.m_timeout = 0s; state.m_chain_sync.m_work_header = nullptr; state.m_chain_sync.m_sent_getheaders = false; } } else if (state.m_chain_sync.m_timeout == 0s || (state.m_chain_sync.m_work_header != nullptr && state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= state.m_chain_sync.m_work_header->nChainWork)) { - // Our best block known by this peer is behind our tip, and we're either noticing - // that for the first time, OR this peer was able to catch up to some earlier point - // where we checked against our tip. - // Either way, set a new timeout based on current tip. + // At this point we know that the outbound peer has either never sent us a block/header or they have, but its tip is behind ours + // AND + // we are noticing this for the first time (m_timeout is 0) + // OR we noticed this at some point within the last CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds and set a timeout + // for them, they caught up to our tip at the time of setting the timer but not to our current one (we've also advanced). + // Either way, set a new timeout based on our current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; state.m_chain_sync.m_work_header = m_chainman.ActiveChain().Tip(); state.m_chain_sync.m_sent_getheaders = false; @@ -5496,7 +5791,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi if (current_time > peer.m_next_send_feefilter) { CAmount filterToSend = m_fee_filter_rounder.round(currentFilter); // We always have a fee filter of at least the min relay fee - filterToSend = std::max(filterToSend, m_mempool.m_min_relay_feerate.GetFeePerK()); + filterToSend = std::max(filterToSend, m_mempool.m_opts.min_relay_feerate.GetFeePerK()); if (filterToSend != peer.m_fee_filter_sent) { MakeAndPushMessage(pto, NetMsgType::FEEFILTER, filterToSend); peer.m_fee_filter_sent = filterToSend; @@ -6029,7 +6324,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) entry.second.GetHash().ToString(), entry.first); } for (const GenTxid& gtxid : requestable) { - if (!AlreadyHaveTx(gtxid)) { + // Exclude m_recent_rejects_reconsiderable: we may be requesting a missing parent + // that was previously rejected for being too low feerate. + if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) { LogPrint(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx", gtxid.GetHash().ToString(), pto->GetId()); vGetData.emplace_back(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*peer)), gtxid.GetHash()); diff --git a/src/net_processing.h b/src/net_processing.h index f8d7a8f511..85e399d948 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -9,6 +9,8 @@ #include <net.h> #include <validationinterface.h> +#include <chrono> + class AddrMan; class CChainParams; class CTxMemPool; @@ -41,6 +43,12 @@ struct CNodeStateStats { bool m_addr_relay_enabled{false}; ServiceFlags their_services; int64_t presync_height{-1}; + std::chrono::seconds time_offset{0}; +}; + +struct PeerManagerInfo { + std::chrono::seconds median_outbound_time_offset{0s}; + bool ignores_incoming_txs{false}; }; class PeerManager : public CValidationInterface, public NetEventsInterface @@ -83,8 +91,8 @@ public: /** Get statistics from node state */ virtual bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const = 0; - /** Whether this node ignores txs received over p2p. */ - virtual bool IgnoresIncomingTxs() = 0; + /** Get peer manager info. */ + virtual PeerManagerInfo GetInfo() const = 0; /** Relay transaction to all peers. */ virtual void RelayTransaction(const uint256& txid, const uint256& wtxid) = 0; diff --git a/src/netaddress.h b/src/netaddress.h index c63bd4b4e5..ea2d14336e 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -237,7 +237,7 @@ public: template <typename Stream> void Serialize(Stream& s) const { - if (s.GetParams().enc == Encoding::V2) { + if (s.template GetParams<SerParams>().enc == Encoding::V2) { SerializeV2Stream(s); } else { SerializeV1Stream(s); @@ -250,7 +250,7 @@ public: template <typename Stream> void Unserialize(Stream& s) { - if (s.GetParams().enc == Encoding::V2) { + if (s.template GetParams<SerParams>().enc == Encoding::V2) { UnserializeV2Stream(s); } else { UnserializeV1Stream(s); diff --git a/src/netbase.cpp b/src/netbase.cpp index f0fa298378..e231766487 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <netbase.h> diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 2c0cbe7163..0a2c3b2caf 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -1180,69 +1180,66 @@ public: void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFiles) { - ScheduleBatchPriority(); - - { - ImportingNow imp{chainman.m_blockman.m_importing}; - - // -reindex - if (fReindex) { - int nFile = 0; - // Map of disk positions for blocks with unknown parent (only used for reindex); - // parent hash -> child disk position, multiple children can have the same parent. - std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent; - while (true) { - FlatFilePos pos(nFile, 0); - if (!fs::exists(chainman.m_blockman.GetBlockPosFilename(pos))) { - break; // No block files left to reindex - } - AutoFile file{chainman.m_blockman.OpenBlockFile(pos, true)}; - if (file.IsNull()) { - break; // This error is logged in OpenBlockFile - } - LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); - chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent); - if (chainman.m_interrupt) { - LogPrintf("Interrupt requested. Exit %s\n", __func__); - return; - } - nFile++; + ImportingNow imp{chainman.m_blockman.m_importing}; + + // -reindex + if (fReindex) { + int nFile = 0; + // Map of disk positions for blocks with unknown parent (only used for reindex); + // parent hash -> child disk position, multiple children can have the same parent. + std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent; + while (true) { + FlatFilePos pos(nFile, 0); + if (!fs::exists(chainman.m_blockman.GetBlockPosFilename(pos))) { + break; // No block files left to reindex } - WITH_LOCK(::cs_main, chainman.m_blockman.m_block_tree_db->WriteReindexing(false)); - fReindex = false; - LogPrintf("Reindexing finished\n"); - // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): - chainman.ActiveChainstate().LoadGenesisBlock(); + AutoFile file{chainman.m_blockman.OpenBlockFile(pos, true)}; + if (file.IsNull()) { + break; // This error is logged in OpenBlockFile + } + LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); + chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent); + if (chainman.m_interrupt) { + LogPrintf("Interrupt requested. Exit %s\n", __func__); + return; + } + nFile++; } - - // -loadblock= - for (const fs::path& path : vImportFiles) { - AutoFile file{fsbridge::fopen(path, "rb")}; - if (!file.IsNull()) { - LogPrintf("Importing blocks file %s...\n", fs::PathToString(path)); - chainman.LoadExternalBlockFile(file); - if (chainman.m_interrupt) { - LogPrintf("Interrupt requested. Exit %s\n", __func__); - return; - } - } else { - LogPrintf("Warning: Could not open blocks file %s\n", fs::PathToString(path)); + WITH_LOCK(::cs_main, chainman.m_blockman.m_block_tree_db->WriteReindexing(false)); + fReindex = false; + LogPrintf("Reindexing finished\n"); + // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): + chainman.ActiveChainstate().LoadGenesisBlock(); + } + + // -loadblock= + for (const fs::path& path : vImportFiles) { + AutoFile file{fsbridge::fopen(path, "rb")}; + if (!file.IsNull()) { + LogPrintf("Importing blocks file %s...\n", fs::PathToString(path)); + chainman.LoadExternalBlockFile(file); + if (chainman.m_interrupt) { + LogPrintf("Interrupt requested. Exit %s\n", __func__); + return; } + } else { + LogPrintf("Warning: Could not open blocks file %s\n", fs::PathToString(path)); } + } - // scan for better chains in the block chain database, that are not yet connected in the active best chain + // scan for better chains in the block chain database, that are not yet connected in the active best chain - // We can't hold cs_main during ActivateBestChain even though we're accessing - // the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve - // the relevant pointers before the ABC call. - for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { - BlockValidationState state; - if (!chainstate->ActivateBestChain(state, nullptr)) { - chainman.GetNotifications().fatalError(strprintf(_("Failed to connect best block (%s)."), state.ToString())); - return; - } + // We can't hold cs_main during ActivateBestChain even though we're accessing + // the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve + // the relevant pointers before the ABC call. + for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { + BlockValidationState state; + if (!chainstate->ActivateBestChain(state, nullptr)) { + chainman.GetNotifications().fatalError(strprintf(_("Failed to connect best block (%s)."), state.ToString())); + return; } - } // End scope of ImportingNow + } + // End scope of ImportingNow } std::ostream& operator<<(std::ostream& os, const BlockfileType& type) { diff --git a/src/node/context.cpp b/src/node/context.cpp index ca56fa0b86..e32d21b383 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -8,6 +8,7 @@ #include <banman.h> #include <interfaces/chain.h> #include <kernel/context.h> +#include <key.h> #include <net.h> #include <net_processing.h> #include <netgroup.h> @@ -16,6 +17,7 @@ #include <scheduler.h> #include <txmempool.h> #include <validation.h> +#include <validationinterface.h> namespace node { NodeContext::NodeContext() = default; diff --git a/src/node/context.h b/src/node/context.h index 245f230aec..a7d92989dd 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -5,10 +5,7 @@ #ifndef BITCOIN_NODE_CONTEXT_H #define BITCOIN_NODE_CONTEXT_H -#include <kernel/context.h> - #include <atomic> -#include <cassert> #include <cstdlib> #include <functional> #include <memory> @@ -24,6 +21,7 @@ class ValidationSignals; class CScheduler; class CTxMemPool; class ChainstateManager; +class ECC_Context; class NetGroupManager; class PeerManager; namespace interfaces { @@ -32,6 +30,12 @@ class ChainClient; class Init; class WalletLoader; } // namespace interfaces +namespace kernel { +struct Context; +} +namespace util { +class SignalInterrupt; +} namespace node { class KernelNotifications; @@ -49,6 +53,7 @@ class KernelNotifications; struct NodeContext { //! libbitcoin_kernel context std::unique_ptr<kernel::Context> kernel; + std::unique_ptr<ECC_Context> ecc_context; //! Init interface for initializing current process and connecting to other processes. interfaces::Init* init{nullptr}; //! Interrupt object used to track whether node shutdown was requested. diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 4d2d83812e..216f44ab9e 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -17,6 +17,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <kernel/chain.h> +#include <kernel/context.h> #include <kernel/mempool_entry.h> #include <logging.h> #include <mapport.h> @@ -47,14 +48,13 @@ #include <util/check.h> #include <util/result.h> #include <util/signalinterrupt.h> +#include <util/string.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <warnings.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <any> #include <memory> @@ -91,7 +91,7 @@ public: explicit NodeImpl(NodeContext& context) { setContext(&context); } void initLogging() override { InitLogging(args()); } void initParameterInteraction() override { InitParameterInteraction(args()); } - bilingual_str getWarnings() override { return GetWarnings(true); } + bilingual_str getWarnings() override { return Join(GetWarnings(), Untranslated("<hr />")); } int getExitStatus() override { return Assert(m_context)->exit_status.load(); } uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } bool baseInitialize() override @@ -100,6 +100,7 @@ public: if (!AppInitParameterInteraction(args())) return false; m_context->kernel = std::make_unique<kernel::Context>(); + m_context->ecc_context = std::make_unique<ECC_Context>(); if (!AppInitSanityChecks(*m_context->kernel)) return false; if (!AppInitLockDataDirectory()) return false; @@ -317,7 +318,7 @@ public: CFeeRate getDustRelayFee() override { if (!m_context->mempool) return CFeeRate{DUST_RELAY_TX_FEE}; - return m_context->mempool->m_dust_relay_feerate; + return m_context->mempool->m_opts.dust_relay_feerate; } UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override { @@ -700,7 +701,7 @@ public: { const CTxMemPool::Limits default_limits{}; - const CTxMemPool::Limits& limits{m_node.mempool ? m_node.mempool->m_limits : default_limits}; + const CTxMemPool::Limits& limits{m_node.mempool ? m_node.mempool->m_opts.limits : default_limits}; limit_ancestor_count = limits.ancestor_count; limit_descendant_count = limits.descendant_count; @@ -731,17 +732,17 @@ public: CFeeRate relayMinFee() override { if (!m_node.mempool) return CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}; - return m_node.mempool->m_min_relay_feerate; + return m_node.mempool->m_opts.min_relay_feerate; } CFeeRate relayIncrementalFee() override { if (!m_node.mempool) return CFeeRate{DEFAULT_INCREMENTAL_RELAY_FEE}; - return m_node.mempool->m_incremental_relay_feerate; + return m_node.mempool->m_opts.incremental_relay_feerate; } CFeeRate relayDustFee() override { if (!m_node.mempool) return CFeeRate{DUST_RELAY_TX_FEE}; - return m_node.mempool->m_dust_relay_feerate; + return m_node.mempool->m_opts.dust_relay_feerate; } bool havePruned() override { diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp index 99f909ff75..e326d4a1f2 100644 --- a/src/node/kernel_notifications.cpp +++ b/src/node/kernel_notifications.cpp @@ -4,9 +4,7 @@ #include <node/kernel_notifications.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chain.h> #include <common/args.h> @@ -16,6 +14,7 @@ #include <node/abort.h> #include <node/interface_ui.h> #include <util/check.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> diff --git a/src/node/timeoffsets.cpp b/src/node/timeoffsets.cpp new file mode 100644 index 0000000000..62f527be8a --- /dev/null +++ b/src/node/timeoffsets.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2024-present 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 <logging.h> +#include <node/interface_ui.h> +#include <node/timeoffsets.h> +#include <sync.h> +#include <tinyformat.h> +#include <util/time.h> +#include <util/translation.h> +#include <warnings.h> + +#include <algorithm> +#include <chrono> +#include <cstdint> +#include <deque> +#include <limits> +#include <optional> + +using namespace std::chrono_literals; + +void TimeOffsets::Add(std::chrono::seconds offset) +{ + LOCK(m_mutex); + + if (m_offsets.size() >= MAX_SIZE) { + m_offsets.pop_front(); + } + m_offsets.push_back(offset); + LogDebug(BCLog::NET, "Added time offset %+ds, total samples %d\n", + Ticks<std::chrono::seconds>(offset), m_offsets.size()); +} + +std::chrono::seconds TimeOffsets::Median() const +{ + LOCK(m_mutex); + + // Only calculate the median if we have 5 or more offsets + if (m_offsets.size() < 5) return 0s; + + auto sorted_copy = m_offsets; + std::sort(sorted_copy.begin(), sorted_copy.end()); + return sorted_copy[sorted_copy.size() / 2]; // approximate median is good enough, keep it simple +} + +bool TimeOffsets::WarnIfOutOfSync() const +{ + // when median == std::numeric_limits<int64_t>::min(), calling std::chrono::abs is UB + auto median{std::max(Median(), std::chrono::seconds(std::numeric_limits<int64_t>::min() + 1))}; + if (std::chrono::abs(median) <= WARN_THRESHOLD) { + SetMedianTimeOffsetWarning(std::nullopt); + uiInterface.NotifyAlertChanged(); + return false; + } + + bilingual_str msg{strprintf(_( + "Your computer's date and time appear to be more than %d minutes out of sync with the network, " + "this may lead to consensus failure. After you've confirmed your computer's clock, this message " + "should no longer appear when you restart your node. Without a restart, it should stop showing " + "automatically after you've connected to a sufficient number of new outbound peers, which may " + "take some time. You can inspect the `timeoffset` field of the `getpeerinfo` and `getnetworkinfo` " + "RPC methods to get more info." + ), Ticks<std::chrono::minutes>(WARN_THRESHOLD))}; + LogWarning("%s\n", msg.original); + SetMedianTimeOffsetWarning(msg); + uiInterface.NotifyAlertChanged(); + return true; +} diff --git a/src/node/timeoffsets.h b/src/node/timeoffsets.h new file mode 100644 index 0000000000..2b12584e12 --- /dev/null +++ b/src/node/timeoffsets.h @@ -0,0 +1,39 @@ +// Copyright (c) 2024-present 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_TIMEOFFSETS_H +#define BITCOIN_NODE_TIMEOFFSETS_H + +#include <sync.h> + +#include <chrono> +#include <cstddef> +#include <deque> + +class TimeOffsets +{ + //! Maximum number of timeoffsets stored. + static constexpr size_t MAX_SIZE{50}; + //! Minimum difference between system and network time for a warning to be raised. + static constexpr std::chrono::minutes WARN_THRESHOLD{10}; + + mutable Mutex m_mutex; + /** The observed time differences between our local clock and those of our outbound peers. A + * positive offset means our peer's clock is ahead of our local clock. */ + std::deque<std::chrono::seconds> m_offsets GUARDED_BY(m_mutex){}; + +public: + /** Add a new time offset sample. */ + void Add(std::chrono::seconds offset) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + + /** Compute and return the median of the collected time offset samples. The median is returned + * as 0 when there are less than 5 samples. */ + std::chrono::seconds Median() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + + /** Raise warnings if the median time offset exceeds the warnings threshold. Returns true if + * warnings were raised. */ + bool WarnIfOutOfSync() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); +}; + +#endif // BITCOIN_NODE_TIMEOFFSETS_H diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 566e5ec55a..c72d9deacb 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -85,7 +85,7 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, { // Add script to keystore keystore.AddCScript(script); - // Note that scripts over 520 bytes are not yet supported. + // Note that scripts over MAX_SCRIPT_ELEMENT_SIZE bytes are not yet supported. switch (type) { case OutputType::LEGACY: return ScriptHash(script); diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp index 3a63a9fe46..693adcdfd0 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -147,3 +147,24 @@ bool IsChildWithParentsTree(const Package& package) return true; }); } + +uint256 GetPackageHash(const std::vector<CTransactionRef>& transactions) +{ + // Create a vector of the wtxids. + std::vector<Wtxid> wtxids_copy; + std::transform(transactions.cbegin(), transactions.cend(), std::back_inserter(wtxids_copy), + [](const auto& tx){ return tx->GetWitnessHash(); }); + + // Sort in ascending order + std::sort(wtxids_copy.begin(), wtxids_copy.end(), [](const auto& lhs, const auto& rhs) { + return std::lexicographical_compare(std::make_reverse_iterator(lhs.end()), std::make_reverse_iterator(lhs.begin()), + std::make_reverse_iterator(rhs.end()), std::make_reverse_iterator(rhs.begin())); + }); + + // Get sha256 hash of the wtxids concatenated in this order + HashWriter hashwriter; + for (const auto& wtxid : wtxids_copy) { + hashwriter << wtxid; + } + return hashwriter.GetSHA256(); +} diff --git a/src/policy/packages.h b/src/policy/packages.h index 537d8476e2..3050320122 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -88,4 +88,9 @@ bool IsChildWithParents(const Package& package); * other (the package is a "tree"). */ bool IsChildWithParentsTree(const Package& package); + +/** Get the hash of these transactions' wtxids, concatenated in lexicographical order (treating the + * wtxids as little endian encoded uint256, smallest to largest). */ +uint256 GetPackageHash(const std::vector<CTransactionRef>& transactions); + #endif // BITCOIN_POLICY_PACKAGES_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index d08ec4fb7f..d8b4b907e4 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -111,7 +111,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat for (const CTxIn& txin : tx.vin) { // Biggest 'standard' txin involving only keys is a 15-of-15 P2SH - // multisig with compressed keys (remember the 520 byte limit on + // multisig with compressed keys (remember the MAX_SCRIPT_ELEMENT_SIZE byte limit on // redeemScript size). That works out to a (15*(33+1))+3=513 byte // redeemScript, 513+1+15*(73+1)+3=1627 bytes of scriptSig, which // we round off to 1650(MAX_STANDARD_SCRIPTSIG_SIZE) bytes for diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index ccbeb3ec49..976542cfae 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -326,7 +326,7 @@ public: template <typename Stream> inline void Serialize(Stream& s) const { - SerializeTransaction(*this, s, s.GetParams()); + SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>()); } /** This deserializing constructor is provided instead of an Unserialize method. @@ -334,7 +334,7 @@ public: template <typename Stream> CTransaction(deserialize_type, const TransactionSerParams& params, Stream& s) : CTransaction(CMutableTransaction(deserialize, params, s)) {} template <typename Stream> - CTransaction(deserialize_type, ParamsStream<TransactionSerParams,Stream>& s) : CTransaction(CMutableTransaction(deserialize, s)) {} + CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {} bool IsNull() const { return vin.empty() && vout.empty(); @@ -386,12 +386,12 @@ struct CMutableTransaction template <typename Stream> inline void Serialize(Stream& s) const { - SerializeTransaction(*this, s, s.GetParams()); + SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>()); } template <typename Stream> inline void Unserialize(Stream& s) { - UnserializeTransaction(*this, s, s.GetParams()); + UnserializeTransaction(*this, s, s.template GetParams<TransactionSerParams>()); } template <typename Stream> @@ -400,7 +400,7 @@ struct CMutableTransaction } template <typename Stream> - CMutableTransaction(deserialize_type, ParamsStream<TransactionSerParams,Stream>& s) { + CMutableTransaction(deserialize_type, Stream& s) { Unserialize(s); } diff --git a/src/protocol.h b/src/protocol.h index 243cd23e6e..fba08c6f55 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -375,9 +375,10 @@ public: static constexpr SerParams V1_DISK{{CNetAddr::Encoding::V1}, Format::Disk}; static constexpr SerParams V2_DISK{{CNetAddr::Encoding::V2}, Format::Disk}; - SERIALIZE_METHODS_PARAMS(CAddress, obj, SerParams, params) + SERIALIZE_METHODS(CAddress, obj) { bool use_v2; + auto& params = SER_PARAMS(SerParams); if (params.fmt == Format::Disk) { // In the disk serialization format, the encoding (v1 or v2) is determined by a flag version // that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index c52ef7cd67..efdc3966d1 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -369,21 +369,22 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con else if(type == Receive) { // Generate a new address to associate with given label - auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); - if (!op_dest) { + if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) { + strAddress = EncodeDestination(*dest); + } else { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if (!ctx.isValid()) { // Unlock wallet failed or was cancelled editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); - if (!op_dest) { + if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) { + strAddress = EncodeDestination(*dest_retry); + } else { editStatus = KEY_GENERATION_FAILURE; return QString(); } } - strAddress = EncodeDestination(*op_dest); } else { diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index a4771bbb82..70ed40b2a1 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -100,10 +100,14 @@ void AskPassphraseDialog::accept() // Cannot encrypt with empty passphrase break; } - QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm wallet encryption"), - tr("Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!") + "<br><br>" + tr("Are you sure you wish to encrypt your wallet?"), - QMessageBox::Yes|QMessageBox::Cancel, - QMessageBox::Cancel); + QMessageBox msgBoxConfirm(QMessageBox::Question, + tr("Confirm wallet encryption"), + tr("Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!") + "<br><br>" + tr("Are you sure you wish to encrypt your wallet?"), + QMessageBox::Cancel | QMessageBox::Yes, this); + msgBoxConfirm.button(QMessageBox::Yes)->setText(tr("Continue")); + msgBoxConfirm.button(QMessageBox::Cancel)->setText(tr("Back")); + msgBoxConfirm.setDefaultButton(QMessageBox::Cancel); + QMessageBox::StandardButton retval = (QMessageBox::StandardButton)msgBoxConfirm.exec(); if(retval == QMessageBox::Yes) { if(newpass1 == newpass2) @@ -112,10 +116,19 @@ void AskPassphraseDialog::accept() "your bitcoins from being stolen by malware infecting your computer."); if (m_passphrase_out) { m_passphrase_out->assign(newpass1); - QMessageBox::warning(this, tr("Wallet to be encrypted"), - "<qt>" + - tr("Your wallet is about to be encrypted. ") + encryption_reminder + - "</b></qt>"); + QMessageBox msgBoxWarning(QMessageBox::Warning, + tr("Wallet to be encrypted"), + "<qt>" + + tr("Your wallet is about to be encrypted. ") + encryption_reminder + " " + + tr("Are you sure you wish to encrypt your wallet?") + + "</b></qt>", + QMessageBox::Cancel | QMessageBox::Yes, this); + msgBoxWarning.setDefaultButton(QMessageBox::Cancel); + QMessageBox::StandardButton retval = (QMessageBox::StandardButton)msgBoxWarning.exec(); + if (retval == QMessageBox::Cancel) { + QDialog::reject(); + return; + } } else { assert(model != nullptr); if (model->setWalletEncrypted(newpass1)) { @@ -141,11 +154,7 @@ void AskPassphraseDialog::accept() tr("The supplied passphrases do not match.")); } } - else - { - QDialog::reject(); // Cancelled - } - } break; + } break; case Unlock: try { if (!model->setWalletLocked(false, oldpass)) { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index b1a8461d02..44a858c16b 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/bitcoin.h> diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 9622c9d57d..1423a8bbc6 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_QT_BITCOIN_H #define BITCOIN_QT_BITCOIN_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <interfaces/node.h> #include <qt/initexecutor.h> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 5f132b817e..a43009d954 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/bitcoingui.h> diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index ba91eeb1ff..73adbda5a5 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_QT_BITCOINGUI_H #define BITCOIN_QT_BITCOINGUI_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/bitcoinunits.h> #include <qt/clientmodel.h> diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 05172cfbd2..2f3bad37e6 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/clientmodel.h> diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp index 6557280d89..3e8d1461e5 100644 --- a/src/qt/createwalletdialog.cpp +++ b/src/qt/createwalletdialog.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <interfaces/node.h> #include <qt/createwalletdialog.h> diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 60e9bcde33..eeea53864a 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1113,7 +1113,7 @@ <item row="3" column="0"> <widget class="QLabel" name="peerSessionIdLabel"> <property name="toolTip"> - <string>The BIP324 session ID string in hex, if any.</string> + <string>The BIP324 session ID string in hex.</string> </property> <property name="text"> <string>Session ID</string> diff --git a/src/qt/forms/signverifymessagedialog.ui b/src/qt/forms/signverifymessagedialog.ui index f42d19093b..b72360b586 100644 --- a/src/qt/forms/signverifymessagedialog.ui +++ b/src/qt/forms/signverifymessagedialog.ui @@ -30,7 +30,7 @@ <item> <widget class="QLabel" name="infoLabel_SM"> <property name="text"> - <string>You can sign messages/agreements with your addresses to prove you can receive bitcoins sent to them. Be careful not to sign anything vague or random, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to.</string> + <string>You can sign messages/agreements with your legacy (P2PKH) addresses to prove you can receive bitcoins sent to them. Be careful not to sign anything vague or random, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to.</string> </property> <property name="textFormat"> <enum>Qt::PlainText</enum> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 1d1a84e375..ee841ce626 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/guiutil.h> @@ -775,9 +773,9 @@ QString formatPingTime(std::chrono::microseconds ping_time) QObject::tr("%1 ms").arg(QString::number((int)(count_microseconds(ping_time) / 1000), 10)); } -QString formatTimeOffset(int64_t nTimeOffset) +QString formatTimeOffset(int64_t time_offset) { - return QObject::tr("%1 s").arg(QString::number((int)nTimeOffset, 10)); + return QObject::tr("%1 s").arg(QString::number((int)time_offset, 10)); } QString formatNiceTimeOffset(qint64 secs) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 7f06fdfe37..3e28e54557 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -240,8 +240,8 @@ namespace GUIUtil /** Format a CNodeStats.m_last_ping_time into a user-readable string or display N/A, if 0 */ QString formatPingTime(std::chrono::microseconds ping_time); - /** Format a CNodeCombinedStats.nTimeOffset into a user-readable string */ - QString formatTimeOffset(int64_t nTimeOffset); + /** Format a CNodeStateStats.time_offset into a user-readable string */ + QString formatTimeOffset(int64_t time_offset); QString formatNiceTimeOffset(qint64 secs); diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 5371dbaa30..26b42deb64 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparams.h> #include <qt/intro.h> diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp index 667db06574..7bc6ccdc49 100644 --- a/src/qt/modaloverlay.cpp +++ b/src/qt/modaloverlay.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/modaloverlay.h> #include <qt/forms/ui_modaloverlay.h> diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index 551c0ffd13..85bdeee49a 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/notificator.h> diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 1fd8181a22..8808716aa4 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_QT_NOTIFICATOR_H #define BITCOIN_QT_NOTIFICATOR_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <QIcon> #include <QObject> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index dd654a7abe..ee53a59bb5 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/optionsdialog.h> #include <qt/forms/ui_optionsdialog.h> @@ -20,6 +18,7 @@ #include <node/chainstatemanager_args.h> #include <netbase.h> #include <txdb.h> +#include <util/strencodings.h> #include <chrono> @@ -482,7 +481,10 @@ QValidator(parent) QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); - // Validate the proxy + uint16_t port{0}; + std::string hostname; + if (!SplitHostPort(input.toStdString(), port, hostname) || port != 0) return QValidator::Invalid; + CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT)); Proxy addrProxy = Proxy(serv, true); if (addrProxy.IsValid()) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d0f7c64357..0c21c6748d 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/optionsmodel.h> diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index 00f928b355..f6e712a047 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -15,9 +15,7 @@ #include <QMouseEvent> #include <QPainter> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> /* for USE_QRCODE */ -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #ifdef USE_QRCODE #include <qrencode.h> diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 3453857f98..b4322ddc0f 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -14,9 +14,7 @@ #include <QDialog> #include <QString> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> /* for USE_QRCODE */ -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep ReceiveRequestDialog::ReceiveRequestDialog(QWidget* parent) : QDialog(parent, GUIUtil::dialog_flags), diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index d2b184ebdf..702ca44395 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/rpcconsole.h> #include <qt/forms/ui_debugwindow.h> @@ -23,6 +21,7 @@ #include <rpc/server.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/time.h> #include <util/threadnames.h> #include <univalue.h> @@ -1196,7 +1195,6 @@ void RPCConsole::updateDetailWidget() ui->peerBytesRecv->setText(GUIUtil::formatBytes(stats->nodeStats.nRecvBytes)); ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time)); ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time)); - ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); if (stats->nodeStats.nVersion) { ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); } @@ -1228,6 +1226,7 @@ void RPCConsole::updateDetailWidget() // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { + ui->timeoffset->setText(GUIUtil::formatTimeOffset(Ticks<std::chrono::seconds>(stats->nodeStateStats.time_offset))); ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStateStats.their_services)); // Sync height is init to -1 if (stats->nodeStateStats.nSyncHeight > -1) { diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 358f68c3c8..d6a5035c33 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_QT_RPCCONSOLE_H #define BITCOIN_QT_RPCCONSOLE_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/clientmodel.h> #include <qt/guiutil.h> diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 89bd35eb1b..0d8c0f7a63 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/sendcoinsdialog.h> #include <qt/forms/ui_sendcoinsdialog.h> diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 0e725acb33..4392d76328 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -10,6 +10,7 @@ #include <qt/platformstyle.h> #include <qt/walletmodel.h> +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <key_io.h> #include <util/message.h> // For MessageSign(), MessageVerify() #include <wallet/wallet.h> @@ -123,7 +124,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() if (!pkhash) { ui->addressIn_SM->setValid(false); ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_SM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); + ui->statusLabel_SM->setText(tr("The entered address does not refer to a legacy (P2PKH) key. Message signing for SegWit and other non-P2PKH address types is not supported in this version of %1. Please check the address and try again.").arg(PACKAGE_NAME)); return; } @@ -221,10 +222,7 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() return; case MessageVerificationResult::ERR_ADDRESS_NO_KEY: ui->addressIn_VM->setValid(false); - ui->statusLabel_VM->setText( - tr("The entered address does not refer to a key.") + QString(" ") + - tr("Please check the address and try again.") - ); + ui->statusLabel_VM->setText(tr("The entered address does not refer to a legacy (P2PKH) key. Message signing for SegWit and other non-P2PKH address types is not supported in this version of %1. Please check the address and try again.").arg(PACKAGE_NAME)); return; case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: ui->signatureIn_VM->setValid(false); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 8872f8be32..ffd6689910 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/splashscreen.h> diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 5f9f2cb449..0f82f65f3e 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <common/args.h> #include <init.h> diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index c5405cca98..f310d0a02b 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <interfaces/init.h> #include <interfaces/node.h> diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 22fe219def..f261c6409d 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <qt/utilitydialog.h> @@ -117,7 +115,7 @@ HelpMessageDialog::~HelpMessageDialog() void HelpMessageDialog::printToConsole() { // On other operating systems, the expected action is to print the message to the console. - tfm::format(std::cout, "%s\n", qPrintable(text)); + tfm::format(std::cout, "%s", qPrintable(text)); } void HelpMessageDialog::showOrPrint() diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index fe000bcbb8..87ad98a4cc 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -529,12 +529,6 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) return false; } - WalletModel::UnlockContext ctx(requestUnlock()); - if(!ctx.isValid()) - { - return false; - } - // Short-circuit if we are returning a bumped transaction PSBT to clipboard if (retval == QMessageBox::Save) { // "Create Unsigned" clicked @@ -549,10 +543,15 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) DataStream ssTx{}; ssTx << psbtx; GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); - Q_EMIT message(tr("PSBT copied"), tr("Copied to clipboard", "Fee-bump PSBT saved"), CClientUIInterface::MSG_INFORMATION); + Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL); return true; } + WalletModel::UnlockContext ctx(requestUnlock()); + if (!ctx.isValid()) { + return false; + } + assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner()); // sign bumped transaction diff --git a/src/random.cpp b/src/random.cpp index 4fc9099704..239d5bc6fe 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <random.h> diff --git a/src/randomenv.cpp b/src/randomenv.cpp index 123b5cc06c..aeec959c28 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <randomenv.h> diff --git a/src/rest.cpp b/src/rest.cpp index 89c033b8a3..9fc5d4af04 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <rest.h> diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index a1135c27d4..1abaeafb2a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -47,7 +47,6 @@ #include <validation.h> #include <validationinterface.h> #include <versionbits.h> -#include <warnings.h> #include <stdint.h> @@ -189,12 +188,12 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr; UniValue objTx(UniValue::VOBJ); TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, txundo, verbosity); - txs.push_back(objTx); + txs.push_back(std::move(objTx)); } break; } - result.pushKV("tx", txs); + result.pushKV("tx", std::move(txs)); return result; } @@ -1260,7 +1259,14 @@ RPCHelpMan getblockchaininfo() {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::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), }}, RPCExamples{ HelpExampleCli("getblockchaininfo", "") @@ -1298,7 +1304,7 @@ RPCHelpMan getblockchaininfo() } } - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; @@ -2174,7 +2180,8 @@ static RPCHelpMan scantxoutset() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { UniValue result(UniValue::VOBJ); - if (request.params[0].get_str() == "status") { + const auto action{self.Arg<std::string>("action")}; + if (action == "status") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress @@ -2182,7 +2189,7 @@ static RPCHelpMan scantxoutset() } result.pushKV("progress", g_scan_progress.load()); return result; - } else if (request.params[0].get_str() == "abort") { + } else if (action == "abort") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // reserve was possible which means no scan was running @@ -2191,7 +2198,7 @@ static RPCHelpMan scantxoutset() // set the abort flag g_should_abort_scan = true; return true; - } else if (request.params[0].get_str() == "start") { + } else if (action == "start") { CoinsViewScanReserver reserver; if (!reserver.reserve()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); @@ -2260,7 +2267,7 @@ static RPCHelpMan scantxoutset() result.pushKV("unspents", unspents); result.pushKV("total_amount", ValueFromAmount(total_in)); } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str())); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action)); } return result; }, diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 8d06ae4258..27c7394909 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <common/args.h> #include <common/system.h> diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index f3345b4f1c..b933d8c647 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -65,7 +65,7 @@ static RPCHelpMan estimatesmartfee() const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); - CHECK_NONFATAL(mempool.m_signals)->SyncWithValidationInterfaceQueue(); + CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue(); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); bool conservative = true; @@ -83,7 +83,7 @@ static RPCHelpMan estimatesmartfee() CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; if (feeRate != CFeeRate(0)) { CFeeRate min_mempool_feerate{mempool.GetMinFee()}; - CFeeRate min_relay_feerate{mempool.m_min_relay_feerate}; + CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate}; feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); } else { diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 920bb9ea7f..e599c7dc92 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -671,12 +671,12 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee())); - ret.pushKV("maxmempool", pool.m_max_size_bytes); - ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_min_relay_feerate).GetFeePerK())); - ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_min_relay_feerate.GetFeePerK())); - ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK())); + ret.pushKV("maxmempool", pool.m_opts.max_size_bytes); + ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_opts.min_relay_feerate).GetFeePerK())); + ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_opts.min_relay_feerate.GetFeePerK())); + ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_opts.incremental_relay_feerate.GetFeePerK())); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); - ret.pushKV("fullrbf", pool.m_full_rbf); + ret.pushKV("fullrbf", pool.m_opts.full_rbf); return ret; } @@ -813,13 +813,14 @@ static RPCHelpMan submitpackage() { return RPCHelpMan{"submitpackage", "Submit a package of raw transactions (serialized, hex-encoded) to local node.\n" - "The package must consist of a child with its parents, and none of the parents may depend on one another.\n" "The package will be validated according to consensus and mempool policy rules. If any transaction passes, it will be accepted to mempool.\n" "This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n" "Warning: successful submission does not mean the transactions will propagate throughout the network.\n" , { - {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.", + {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.\n" + "The package must solely consist of a child and its parents. None of the parents may depend on each other.\n" + "The package must be topologically sorted, with the child being the last element in the array.", { {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, }, @@ -860,15 +861,15 @@ static RPCHelpMan submitpackage() }, }, RPCExamples{ - HelpExampleCli("testmempoolaccept", "[rawtx1, rawtx2]") + - HelpExampleCli("submitpackage", "[rawtx1, rawtx2]") + HelpExampleRpc("submitpackage", R"(["rawtx1", "rawtx2"])") + + HelpExampleCli("submitpackage", R"('["rawtx1", "rawtx2"]')") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const UniValue raw_transactions = request.params[0].get_array(); - if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { + if (raw_transactions.size() < 2 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, - "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); + "Array must contain between 2 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); } // Fee check needs to be run with chainstate and package context @@ -994,10 +995,8 @@ static RPCHelpMan submitpackage() fees.pushKV("effective-includes", effective_includes_res); } result_inner.pushKV("fees", fees); - if (it->second.m_replaced_transactions.has_value()) { - for (const auto& ptx : it->second.m_replaced_transactions.value()) { - replaced_txids.insert(ptx->GetHash()); - } + for (const auto& ptx : it->second.m_replaced_transactions) { + replaced_txids.insert(ptx->GetHash()); } break; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index f7cdbf52dd..2391392bd7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chain.h> #include <chainparams.h> @@ -33,13 +31,13 @@ #include <script/signingprovider.h> #include <txmempool.h> #include <univalue.h> +#include <util/signalinterrupt.h> #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> -#include <warnings.h> #include <memory> #include <stdint.h> @@ -122,7 +120,7 @@ static RPCHelpMan getnetworkhashps() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetNetworkHashPS(self.Arg<int>(0), self.Arg<int>(1), chainman.ActiveChain()); + return GetNetworkHashPS(self.Arg<int>("nblocks"), self.Arg<int>("height"), chainman.ActiveChain()); }, }; } @@ -229,12 +227,12 @@ static RPCHelpMan generatetodescriptor() "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const auto num_blocks{self.Arg<int>(0)}; - const auto max_tries{self.Arg<uint64_t>(2)}; + const auto num_blocks{self.Arg<int>("num_blocks")}; + const auto max_tries{self.Arg<uint64_t>("maxtries")}; CScript coinbase_script; std::string error; - if (!getScriptFromDescriptor(self.Arg<std::string>(1), coinbase_script, error)) { + if (!getScriptFromDescriptor(self.Arg<std::string>("descriptor"), coinbase_script, error)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } @@ -426,7 +424,14 @@ static RPCHelpMan getmininginfo() {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), }}, RPCExamples{ HelpExampleCli("getmininginfo", "") @@ -448,7 +453,7 @@ static RPCHelpMan getmininginfo() obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", chainman.GetParams().GetChainTypeString()); - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f935a3b08f..59397aa84d 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -23,14 +23,12 @@ #include <rpc/server_util.h> #include <rpc/util.h> #include <sync.h> -#include <timedata.h> #include <util/chaintype.h> #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> #include <util/translation.h> #include <validation.h> -#include <warnings.h> #include <optional> @@ -239,7 +237,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("bytessent", stats.nSendBytes); obj.pushKV("bytesrecv", stats.nRecvBytes); obj.pushKV("conntime", count_seconds(stats.m_connected)); - obj.pushKV("timeoffset", stats.nTimeOffset); + obj.pushKV("timeoffset", Ticks<std::chrono::seconds>(statestats.time_offset)); if (stats.m_last_ping_time > 0us) { obj.pushKV("pingtime", Ticks<SecondsDouble>(stats.m_last_ping_time)); } @@ -322,7 +320,7 @@ static RPCHelpMan addnode() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const std::string command{request.params[1].get_str()}; + const auto command{self.Arg<std::string>("command")}; if (command != "onetry" && command != "add" && command != "remove") { throw std::runtime_error( self.ToString()); @@ -331,9 +329,9 @@ static RPCHelpMan addnode() NodeContext& node = EnsureAnyNodeContext(request.context); CConnman& connman = EnsureConnman(node); - const std::string node_arg{request.params[0].get_str()}; + const auto node_arg{self.Arg<std::string>("node")}; bool node_v2transport = connman.GetLocalServices() & NODE_P2P_V2; - bool use_v2transport = self.MaybeArg<bool>(2).value_or(node_v2transport); + bool use_v2transport = self.MaybeArg<bool>("v2transport").value_or(node_v2transport); if (use_v2transport && !node_v2transport) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: v2transport requested but not enabled (see -v2transport)"); @@ -658,7 +656,14 @@ static RPCHelpMan getnetworkinfo() {RPCResult::Type::NUM, "score", "relative score"}, }}, }}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + (IsDeprecatedRPCEnabled("warnings") ? + RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} : + RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)", + { + {RPCResult::Type::STR, "", "warning"}, + } + } + ), } }, RPCExamples{ @@ -679,9 +684,10 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("localservicesnames", GetServicesNames(services)); } if (node.peerman) { - obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs()); + auto peerman_info{node.peerman->GetInfo()}; + obj.pushKV("localrelay", !peerman_info.ignores_incoming_txs); + obj.pushKV("timeoffset", Ticks<std::chrono::seconds>(peerman_info.median_outbound_time_offset)); } - obj.pushKV("timeoffset", GetTimeOffset()); if (node.connman) { obj.pushKV("networkactive", node.connman->GetNetworkActive()); obj.pushKV("connections", node.connman->GetNodeCount(ConnectionDirection::Both)); @@ -691,8 +697,8 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("networks", GetNetworksInfo()); if (node.mempool) { // Those fields can be deprecated, to be replaced by the getmempoolinfo fields - obj.pushKV("relayfee", ValueFromAmount(node.mempool->m_min_relay_feerate.GetFeePerK())); - obj.pushKV("incrementalfee", ValueFromAmount(node.mempool->m_incremental_relay_feerate.GetFeePerK())); + obj.pushKV("relayfee", ValueFromAmount(node.mempool->m_opts.min_relay_feerate.GetFeePerK())); + obj.pushKV("incrementalfee", ValueFromAmount(node.mempool->m_opts.incremental_relay_feerate.GetFeePerK())); } UniValue localAddresses(UniValue::VARR); { @@ -707,7 +713,7 @@ static RPCHelpMan getnetworkinfo() } } obj.pushKV("localaddresses", localAddresses); - obj.pushKV("warnings", GetWarnings(false).original); + obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings"))); return obj; }, }; diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index ffc2ee5ab0..447be2cf64 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chainparams.h> #include <httpserver.h> diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index f9343f48a8..474d9076be 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -124,11 +124,7 @@ static RPCHelpMan createmultisig() const UniValue& keys = request.params[1].get_array(); std::vector<CPubKey> pubkeys; for (unsigned int i = 0; i < keys.size(); ++i) { - if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { - pubkeys.push_back(HexToPubKey(keys[i].get_str())); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); - } + pubkeys.push_back(HexToPubKey(keys[i].get_str())); } // Get the output type diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 75e42e4c88..83a9010681 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -10,6 +10,7 @@ enum HTTPStatusCode { HTTP_OK = 200, + HTTP_NO_CONTENT = 204, HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, diff --git a/src/rpc/register.h b/src/rpc/register.h index fd23dde75b..65fd29ff08 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_RPC_REGISTER_H #define BITCOIN_RPC_REGISTER_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep /** These are in one header file to avoid creating tons of single-function * headers for everything under src/rpc/ */ diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index b7acd62ee3..d35782189e 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -26,6 +26,17 @@ * * 1.0 spec: http://json-rpc.org/wiki/specification * 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html + * + * If the server receives a request with the JSON-RPC 2.0 marker `{"jsonrpc": "2.0"}` + * then Bitcoin will respond with a strictly specified response. + * It will only return an HTTP error code if an actual HTTP error is encountered + * such as the endpoint is not found (404) or the request is not formatted correctly (500). + * Otherwise the HTTP code is always OK (200) and RPC errors will be included in the + * response body. + * + * 2.0 spec: https://www.jsonrpc.org/specification + * + * Also see http://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0 */ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id) @@ -37,24 +48,25 @@ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, return request; } -UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id) +UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version) { UniValue reply(UniValue::VOBJ); - if (!error.isNull()) - reply.pushKV("result", NullUniValue); - else - reply.pushKV("result", result); - reply.pushKV("error", error); - reply.pushKV("id", id); + // Add JSON-RPC version number field in v2 only. + if (jsonrpc_version == JSONRPCVersion::V2) reply.pushKV("jsonrpc", "2.0"); + + // Add both result and error fields in v1, even though one will be null. + // Omit the null field in v2. + if (error.isNull()) { + reply.pushKV("result", std::move(result)); + if (jsonrpc_version == JSONRPCVersion::V1_LEGACY) reply.pushKV("error", NullUniValue); + } else { + if (jsonrpc_version == JSONRPCVersion::V1_LEGACY) reply.pushKV("result", NullUniValue); + reply.pushKV("error", std::move(error)); + } + if (id.has_value()) reply.pushKV("id", std::move(id.value())); return reply; } -std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id) -{ - UniValue reply = JSONRPCReplyObj(result, error, id); - return reply.write() + "\n"; -} - UniValue JSONRPCError(int code, const std::string& message) { UniValue error(UniValue::VOBJ); @@ -171,7 +183,30 @@ void JSONRPCRequest::parse(const UniValue& valRequest) const UniValue& request = valRequest.get_obj(); // Parse id now so errors from here on will have the id - id = request.find_value("id"); + if (request.exists("id")) { + id = request.find_value("id"); + } else { + id = std::nullopt; + } + + // Check for JSON-RPC 2.0 (default 1.1) + m_json_version = JSONRPCVersion::V1_LEGACY; + const UniValue& jsonrpc_version = request.find_value("jsonrpc"); + if (!jsonrpc_version.isNull()) { + if (!jsonrpc_version.isStr()) { + throw JSONRPCError(RPC_INVALID_REQUEST, "jsonrpc field must be a string"); + } + // The "jsonrpc" key was added in the 2.0 spec, but some older documentation + // incorrectly included {"jsonrpc":"1.0"} in a request object, so we + // maintain that for backwards compatibility. + if (jsonrpc_version.get_str() == "1.0") { + m_json_version = JSONRPCVersion::V1_LEGACY; + } else if (jsonrpc_version.get_str() == "2.0") { + m_json_version = JSONRPCVersion::V2; + } else { + throw JSONRPCError(RPC_INVALID_REQUEST, "JSON-RPC version not supported"); + } + } // Parse method const UniValue& valMethod{request.find_value("method")}; diff --git a/src/rpc/request.h b/src/rpc/request.h index a682c58d96..e47f90af86 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -7,13 +7,18 @@ #define BITCOIN_RPC_REQUEST_H #include <any> +#include <optional> #include <string> #include <univalue.h> +enum class JSONRPCVersion { + V1_LEGACY, + V2 +}; + UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); -UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); -std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); +UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version); UniValue JSONRPCError(int code, const std::string& message); /** Generate a new RPC authentication cookie and write it to disk */ @@ -28,7 +33,7 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in); class JSONRPCRequest { public: - UniValue id; + std::optional<UniValue> id = UniValue::VNULL; std::string strMethod; UniValue params; enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE; @@ -36,8 +41,10 @@ public: std::string authUser; std::string peerAddr; std::any context; + JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; void parse(const UniValue& valRequest); + [[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; }; }; #endif // BITCOIN_RPC_REQUEST_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e7d1e3db4e..1ed406354a 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <rpc/server.h> @@ -362,36 +360,22 @@ bool IsDeprecatedRPCEnabled(const std::string& method) return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end(); } -static UniValue JSONRPCExecOne(JSONRPCRequest jreq, const UniValue& req) +UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors) { - UniValue rpc_result(UniValue::VOBJ); - - try { - jreq.parse(req); - - UniValue result = tableRPC.execute(jreq); - rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); - } - catch (const UniValue& objError) - { - rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); - } - catch (const std::exception& e) - { - rpc_result = JSONRPCReplyObj(NullUniValue, - JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + UniValue result; + if (catch_errors) { + try { + result = tableRPC.execute(jreq); + } catch (UniValue& e) { + return JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version); + } catch (const std::exception& e) { + return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_MISC_ERROR, e.what()), jreq.id, jreq.m_json_version); + } + } else { + result = tableRPC.execute(jreq); } - return rpc_result; -} - -std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq) -{ - UniValue ret(UniValue::VARR); - for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) - ret.push_back(JSONRPCExecOne(jreq, vReq[reqIdx])); - - return ret.write() + "\n"; + return JSONRPCReplyObj(std::move(result), NullUniValue, jreq.id, jreq.m_json_version); } /** diff --git a/src/rpc/server.h b/src/rpc/server.h index b8348e4aa6..5735aff821 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -179,6 +179,6 @@ extern CRPCTable tableRPC; void StartRPC(); void InterruptRPC(); void StopRPC(); -std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq); +UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors); #endif // BITCOIN_RPC_SERVER_H diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp index 8c752ba1fd..9f3c24efcf 100644 --- a/src/rpc/signmessage.cpp +++ b/src/rpc/signmessage.cpp @@ -38,9 +38,9 @@ static RPCHelpMan verifymessage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::string strAddress = request.params[0].get_str(); - std::string strSign = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); + std::string strAddress = self.Arg<std::string>("address"); + std::string strSign = self.Arg<std::string>("signature"); + std::string strMessage = self.Arg<std::string>("message"); switch (MessageVerify(strAddress, strSign, strMessage)) { case MessageVerificationResult::ERR_INVALID_ADDRESS: diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 6e332e3855..f5a2e9eb63 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <clientversion.h> #include <core_io.h> @@ -18,14 +16,19 @@ #include <script/signingprovider.h> #include <script/solver.h> #include <tinyformat.h> +#include <univalue.h> #include <util/check.h> #include <util/result.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> +#include <warnings.h> +#include <algorithm> +#include <iterator> #include <string_view> #include <tuple> +#include <utility> const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; @@ -172,7 +175,7 @@ std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& std::string HelpExampleRpc(const std::string& methodname, const std::string& args) { - return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", " + return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", " "\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } @@ -183,7 +186,7 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& params.pushKV(param.first, param.second); } - return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", " + return "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", " "\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } @@ -191,11 +194,14 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& CPubKey HexToPubKey(const std::string& hex_in) { if (!IsHex(hex_in)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be a hex string"); + } + if (hex_in.length() != 66 && hex_in.length() != 130) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must have a length of either 33 or 65 bytes"); } CPubKey vchPubKey(ParseHex(hex_in)); if (!vchPubKey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be cryptographically valid."); } return vchPubKey; } @@ -729,6 +735,16 @@ std::vector<std::pair<std::string, bool>> RPCHelpMan::GetArgNames() const return ret; } +size_t RPCHelpMan::GetParamIndex(std::string_view key) const +{ + auto it{std::find_if( + m_args.begin(), m_args.end(), [&key](const auto& arg) { return arg.GetName() == key;} + )}; + + CHECK_NONFATAL(it != m_args.end()); // TODO: ideally this is checked at compile time + return std::distance(m_args.begin(), it); +} + std::string RPCHelpMan::ToString() const { std::string ret; @@ -1345,3 +1361,17 @@ void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj) if (warnings.empty()) return; obj.pushKV("warnings", BilingualStringsToUniValue(warnings)); } + +UniValue GetNodeWarnings(bool use_deprecated) +{ + if (use_deprecated) { + const auto all_warnings{GetWarnings()}; + return all_warnings.empty() ? "" : all_warnings.back().original; + } + + UniValue warnings{UniValue::VARR}; + for (auto&& warning : GetWarnings()) { + warnings.push_back(std::move(warning.original)); + } + return warnings; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index f6ee6a317a..0e4dcc27b5 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -404,18 +404,25 @@ public: UniValue HandleRequest(const JSONRPCRequest& request) const; /** - * Helper to get a request argument. - * This function only works during m_fun(), i.e. it should only be used in - * RPC method implementations. The helper internally checks whether the - * user-passed argument isNull() and parses (from JSON) and returns the - * user-passed argument, or the default value derived from the RPCArg - * documentation, or a falsy value if no default was given. + * @brief Helper to get a required or default-valued request argument. * - * Use Arg<Type>(i) to get the argument or its default value. Otherwise, - * use MaybeArg<Type>(i) to get the optional argument or a falsy value. + * Use this function when the argument is required or when it has a default value. If the + * argument is optional and may not be provided, use MaybeArg instead. * - * The Type passed to this helper must match the corresponding - * RPCArg::Type. + * This function only works during m_fun(), i.e., it should only be used in + * RPC method implementations. It internally checks whether the user-passed + * argument isNull() and parses (from JSON) and returns the user-passed argument, + * or the default value derived from the RPCArg documentation. + * + * There are two overloads of this function: + * - Use Arg<Type>(size_t i) to get the argument (or the default value) by index. + * - Use Arg<Type>(const std::string& key) to get the argument (or the default value) by key. + * + * The Type passed to this helper must match the corresponding RPCArg::Type. + * + * @return The value of the RPC argument (or the default value) cast to type Type. + * + * @see MaybeArg for handling optional arguments without default values. */ template <typename R> auto Arg(size_t i) const @@ -429,6 +436,34 @@ public: return ArgValue<const R&>(i); } } + template<typename R> + auto Arg(std::string_view key) const + { + return Arg<R>(GetParamIndex(key)); + } + /** + * @brief Helper to get an optional request argument. + * + * Use this function when the argument is optional and does not have a default value. If the + * argument is required or has a default value, use Arg instead. + * + * This function only works during m_fun(), i.e., it should only be used in + * RPC method implementations. It internally checks whether the user-passed + * argument isNull() and parses (from JSON) and returns the user-passed argument, + * or a falsy value if no argument was passed. + * + * There are two overloads of this function: + * - Use MaybeArg<Type>(size_t i) to get the optional argument by index. + * - Use MaybeArg<Type>(const std::string& key) to get the optional argument by key. + * + * The Type passed to this helper must match the corresponding RPCArg::Type. + * + * @return For integral and floating-point types, a std::optional<Type> is returned. + * For other types, a Type* pointer to the argument is returned. If the + * argument is not provided, std::nullopt or a null pointer is returned. + * + * @see Arg for handling arguments that are required or have a default value. + */ template <typename R> auto MaybeArg(size_t i) const { @@ -441,6 +476,11 @@ public: return ArgValue<const R*>(i); } } + template<typename R> + auto MaybeArg(std::string_view key) const + { + return MaybeArg<R>(GetParamIndex(key)); + } std::string ToString() const; /** Return the named args that need to be converted from string to another JSON type */ UniValue GetArgMap() const; @@ -460,6 +500,8 @@ private: mutable const JSONRPCRequest* m_req{nullptr}; // A pointer to the request for the duration of m_fun() template <typename R> R ArgValue(size_t i) const; + //! Return positional index of a parameter using its name as key. + size_t GetParamIndex(std::string_view key) const; }; /** @@ -471,4 +513,6 @@ private: void PushWarnings(const UniValue& warnings, UniValue& obj); void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj); +UniValue GetNodeWarnings(bool use_deprecated); + #endif // BITCOIN_RPC_UTIL_H diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index 344a81bdf0..455bd56283 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -231,7 +231,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty Type acc_tl = "k"_mst; for (size_t i = 0; i < sub_types.size(); ++i) { Type t = sub_types[i]; - if (!(t << (i ? "Wdu"_mst : "Bdu"_mst))) return ""_mst; // Require Bdu, Wdu, Wdu, ... + static constexpr auto WDU{"Wdu"_mst}, BDU{"Bdu"_mst}; + if (!(t << (i ? WDU : BDU))) return ""_mst; // Require Bdu, Wdu, Wdu, ... if (!(t << "e"_mst)) all_e = false; if (!(t << "m"_mst)) all_m = false; if (t << "s"_mst) num_s += 1; diff --git a/src/script/miniscript.h b/src/script/miniscript.h index f635fa7340..4880f32410 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -123,12 +123,12 @@ class Type { //! Internal bitmap of properties (see ""_mst operator for details). uint32_t m_flags; - //! Internal constructor used by the ""_mst operator. - explicit constexpr Type(uint32_t flags) : m_flags(flags) {} + //! Internal constructor. + explicit constexpr Type(uint32_t flags) noexcept : m_flags(flags) {} public: - //! The only way to publicly construct a Type is using this literal operator. - friend constexpr Type operator"" _mst(const char* c, size_t l); + //! Construction function used by the ""_mst operator. + static consteval Type Make(uint32_t flags) noexcept { return Type(flags); } //! Compute the type with the union of properties. constexpr Type operator|(Type x) const { return Type(m_flags | x.m_flags); } @@ -150,11 +150,11 @@ public: }; //! Literal operator to construct Type objects. -inline constexpr Type operator"" _mst(const char* c, size_t l) { - Type typ{0}; +inline consteval Type operator"" _mst(const char* c, size_t l) { + Type typ{Type::Make(0)}; for (const char *p = c; p < c + l; p++) { - typ = typ | Type( + typ = typ | Type::Make( *p == 'B' ? 1 << 0 : // Base type *p == 'V' ? 1 << 1 : // Verify type *p == 'K' ? 1 << 2 : // Key type @@ -548,7 +548,8 @@ private: for (const auto& sub : subs) { subsize += sub->ScriptSize(); } - Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst; + static constexpr auto NONE_MST{""_mst}; + Type sub0type = subs.size() > 0 ? subs[0]->GetType() : NONE_MST; return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size(), m_script_ctx); } @@ -712,9 +713,10 @@ private: 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. - Type x = subs.size() > 0 ? subs[0]->GetType() : ""_mst; - Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst; - Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst; + static constexpr auto NONE_MST{""_mst}; + Type x = subs.size() > 0 ? subs[0]->GetType() : NONE_MST; + Type y = subs.size() > 1 ? subs[1]->GetType() : NONE_MST; + Type z = subs.size() > 2 ? subs[2]->GetType() : NONE_MST; return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size(), m_script_ctx)); } diff --git a/src/serialize.h b/src/serialize.h index 2f13fba582..35519056a5 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -182,9 +182,8 @@ const Out& AsBase(const In& x) static void SerializationOps(Type& obj, Stream& s, Operation ser_action) /** - * Variant of FORMATTER_METHODS that supports a declared parameter type. - * - * If a formatter has a declared parameter type, it must be invoked directly or + * Formatter methods can retrieve parameters attached to a stream using the + * SER_PARAMS(type) macro as long as the stream is created directly or * indirectly with a parameter of that type. This permits making serialization * depend on run-time context in a type-safe way. * @@ -192,7 +191,8 @@ const Out& AsBase(const In& x) * struct BarParameter { bool fancy; ... }; * struct Bar { ... }; * struct FooFormatter { - * FORMATTER_METHODS(Bar, obj, BarParameter, param) { + * FORMATTER_METHODS(Bar, obj) { + * auto& param = SER_PARAMS(BarParameter); * if (param.fancy) { * READWRITE(VARINT(obj.value)); * } else { @@ -214,13 +214,7 @@ const Out& AsBase(const In& x) * Compilation will fail in any context where serialization is invoked but * no parameter of a type convertible to BarParameter is provided. */ -#define FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) \ - template <typename Stream> \ - static void Ser(Stream& s, const cls& obj) { SerializationOps(obj, s, ActionSerialize{}, s.GetParams()); } \ - template <typename Stream> \ - static void Unser(Stream& s, cls& obj) { SerializationOps(obj, s, ActionUnserialize{}, s.GetParams()); } \ - template <typename Stream, typename Type, typename Operation> \ - static void SerializationOps(Type& obj, Stream& s, Operation ser_action, const paramcls& paramobj) +#define SER_PARAMS(type) (s.template GetParams<type>()) #define BASE_SERIALIZE_METHODS(cls) \ template <typename Stream> \ @@ -247,15 +241,6 @@ const Out& AsBase(const In& x) BASE_SERIALIZE_METHODS(cls) \ FORMATTER_METHODS(cls, obj) -/** - * Variant of SERIALIZE_METHODS that supports a declared parameter type. - * - * See FORMATTER_METHODS_PARAMS for more information on parameters. - */ -#define SERIALIZE_METHODS_PARAMS(cls, obj, paramcls, paramobj) \ - BASE_SERIALIZE_METHODS(cls) \ - FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) - // Templates for serializing to anything that looks like a stream, // i.e. anything that supports .read(Span<std::byte>) and .write(Span<const std::byte>) // @@ -1118,27 +1103,85 @@ size_t GetSerializeSize(const T& t) return (SizeComputer() << t).size(); } -/** Wrapper that overrides the GetParams() function of a stream (and hides GetVersion/GetType). */ -template <typename Params, typename SubStream> +//! Check if type contains a stream by seeing if has a GetStream() method. +template<typename T> +concept ContainsStream = requires(T t) { t.GetStream(); }; + +/** Wrapper that overrides the GetParams() function of a stream. */ +template <typename SubStream, typename Params> class ParamsStream { const Params& m_params; - SubStream& m_substream; // private to avoid leaking version/type into serialization code that shouldn't see it + // If ParamsStream constructor is passed an lvalue argument, Substream will + // be a reference type, and m_substream will reference that argument. + // Otherwise m_substream will be a substream instance and move from the + // argument. Letting ParamsStream contain a substream instance instead of + // just a reference is useful to make the ParamsStream object self contained + // and let it do cleanup when destroyed, for example by closing files if + // SubStream is a file stream. + SubStream m_substream; public: - ParamsStream(const Params& params LIFETIMEBOUND, SubStream& substream LIFETIMEBOUND) : m_params{params}, m_substream{substream} {} + ParamsStream(SubStream&& substream, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{std::forward<SubStream>(substream)} {} + + template <typename NestedSubstream, typename Params1, typename Params2, typename... NestedParams> + ParamsStream(NestedSubstream&& s, const Params1& params1 LIFETIMEBOUND, const Params2& params2 LIFETIMEBOUND, const NestedParams&... params LIFETIMEBOUND) + : ParamsStream{::ParamsStream{std::forward<NestedSubstream>(s), params2, params...}, params1} {} + template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } - void write(Span<const std::byte> src) { m_substream.write(src); } - void read(Span<std::byte> dst) { m_substream.read(dst); } - void ignore(size_t num) { m_substream.ignore(num); } - bool eof() const { return m_substream.eof(); } - size_t size() const { return m_substream.size(); } - const Params& GetParams() const { return m_params; } - int GetVersion() = delete; // Deprecated with Params usage - int GetType() = delete; // Deprecated with Params usage + void write(Span<const std::byte> src) { GetStream().write(src); } + void read(Span<std::byte> dst) { GetStream().read(dst); } + void ignore(size_t num) { GetStream().ignore(num); } + bool eof() const { return GetStream().eof(); } + size_t size() const { return GetStream().size(); } + + //! Get reference to stream parameters. + template <typename P> + const auto& GetParams() const + { + if constexpr (std::is_convertible_v<Params, P>) { + return m_params; + } else { + return m_substream.template GetParams<P>(); + } + } + + //! Get reference to underlying stream. + auto& GetStream() + { + if constexpr (ContainsStream<SubStream>) { + return m_substream.GetStream(); + } else { + return m_substream; + } + } + const auto& GetStream() const + { + if constexpr (ContainsStream<SubStream>) { + return m_substream.GetStream(); + } else { + return m_substream; + } + } }; +/** + * Explicit template deduction guide is required for single-parameter + * constructor so Substream&& is treated as a forwarding reference, and + * SubStream is deduced as reference type for lvalue arguments. + */ +template <typename Substream, typename Params> +ParamsStream(Substream&&, const Params&) -> ParamsStream<Substream, Params>; + +/** + * Template deduction guide for multiple params arguments that creates a nested + * ParamsStream. + */ +template <typename Substream, typename Params1, typename Params2, typename... Params> +ParamsStream(Substream&& s, const Params1& params1, const Params2& params2, const Params&... params) -> + ParamsStream<decltype(ParamsStream{std::forward<Substream>(s), params2, params...}), Params1>; + /** Wrapper that serializes objects with the specified parameters. */ template <typename Params, typename T> class ParamsWrapper @@ -1152,13 +1195,13 @@ public: template <typename Stream> void Serialize(Stream& s) const { - ParamsStream ss{m_params, s}; + ParamsStream ss{s, m_params}; ::Serialize(ss, m_object); } template <typename Stream> void Unserialize(Stream& s) { - ParamsStream ss{m_params, s}; + ParamsStream ss{s, m_params}; ::Unserialize(ss, m_object); } }; @@ -1176,7 +1219,7 @@ public: /** \ * Return a wrapper around t that (de)serializes it with specified parameter params. \ * \ - * See FORMATTER_METHODS_PARAMS for more information on serialization parameters. \ + * See SER_PARAMS for more information on serialization parameters. \ */ \ template <typename T> \ auto operator()(T&& t) const \ diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 0fef8c5906..5e9ae78681 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -16,7 +16,6 @@ #include <test/util/net.h> #include <test/util/random.h> #include <test/util/setup_common.h> -#include <timedata.h> #include <util/string.h> #include <util/time.h> #include <validation.h> @@ -72,7 +71,6 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), /*version=*/PROTOCOL_VERSION, /*relay_txs=*/true); - TestOnlyResetTimeData(); // This test requires that we have a chain with non-zero work. { diff --git a/src/test/fuzz/addition_overflow.cpp b/src/test/fuzz/addition_overflow.cpp index 5100b6f438..071e5fb029 100644 --- a/src/test/fuzz/addition_overflow.cpp +++ b/src/test/fuzz/addition_overflow.cpp @@ -24,12 +24,14 @@ void TestAdditionOverflow(FuzzedDataProvider& fuzzed_data_provider) assert(is_addition_overflow_custom == AdditionOverflow(j, i)); assert(maybe_add == CheckedAdd(j, i)); assert(sat_add == SaturatingAdd(j, i)); +#ifndef _MSC_VER T result_builtin; const bool is_addition_overflow_builtin = __builtin_add_overflow(i, j, &result_builtin); assert(is_addition_overflow_custom == is_addition_overflow_builtin); if (!is_addition_overflow_custom) { assert(i + j == result_builtin); } +#endif if (is_addition_overflow_custom) { assert(sat_add == std::numeric_limits<T>::min() || sat_add == std::numeric_limits<T>::max()); } else { diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index 37c41f3895..8210e75cee 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -17,7 +17,7 @@ namespace { void Initialize() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index 6ea315d4e2..b9a5560ffb 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -55,7 +55,7 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov void initialize_descriptor_parse() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index ebc5673e71..c9a3bc86ac 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -33,7 +33,6 @@ #include <optional> #include <stdexcept> #include <stdint.h> -#include <unistd.h> using node::SnapshotMetadata; diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index a8e490b459..f9915187bd 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -25,7 +25,6 @@ #include <memory> #include <string> #include <tuple> -#include <unistd.h> #include <utility> #include <vector> @@ -135,9 +134,9 @@ void initialize() #if defined(PROVIDE_FUZZ_MAIN_FUNCTION) static bool read_stdin(std::vector<uint8_t>& data) { - uint8_t buffer[1024]; - ssize_t length = 0; - while ((length = read(STDIN_FILENO, buffer, 1024)) > 0) { + std::istream::char_type buffer[1024]; + std::streamsize length; + while ((std::cin.read(buffer, 1024), length = std::cin.gcount()) > 0) { data.insert(data.end(), buffer, buffer + length); } return length == 0; diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index 9e1e318e02..d389a29575 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -32,7 +32,7 @@ void initialize_key() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/key_io.cpp b/src/test/fuzz/key_io.cpp index 5f98f2b7f1..aefdefe233 100644 --- a/src/test/fuzz/key_io.cpp +++ b/src/test/fuzz/key_io.cpp @@ -14,7 +14,7 @@ void initialize_key_io() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::MAIN); } diff --git a/src/test/fuzz/message.cpp b/src/test/fuzz/message.cpp index b5c95441f8..75baaa2754 100644 --- a/src/test/fuzz/message.cpp +++ b/src/test/fuzz/message.cpp @@ -19,7 +19,7 @@ void initialize_message() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp index 0d0ee59ab3..f10007222c 100644 --- a/src/test/fuzz/miniscript.cpp +++ b/src/test/fuzz/miniscript.cpp @@ -391,6 +391,7 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider& bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst); bool allow_V = (type_needed == ""_mst) || (type_needed << "V"_mst); bool allow_W = (type_needed == ""_mst) || (type_needed << "W"_mst); + static constexpr auto B{"B"_mst}, K{"K"_mst}, V{"V"_mst}, W{"W"_mst}; switch (provider.ConsumeIntegral<uint8_t>()) { case 0: @@ -440,22 +441,22 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider& } case 11: if (!(allow_B || allow_K || allow_V)) return {}; - return {{{"B"_mst, type_needed, type_needed}, Fragment::ANDOR}}; + return {{{B, type_needed, type_needed}, Fragment::ANDOR}}; case 12: if (!(allow_B || allow_K || allow_V)) return {}; - return {{{"V"_mst, type_needed}, Fragment::AND_V}}; + return {{{V, type_needed}, Fragment::AND_V}}; case 13: if (!allow_B) return {}; - return {{{"B"_mst, "W"_mst}, Fragment::AND_B}}; + return {{{B, W}, Fragment::AND_B}}; case 15: if (!allow_B) return {}; - return {{{"B"_mst, "W"_mst}, Fragment::OR_B}}; + return {{{B, W}, Fragment::OR_B}}; case 16: if (!allow_V) return {}; - return {{{"B"_mst, "V"_mst}, Fragment::OR_C}}; + return {{{B, V}, Fragment::OR_C}}; case 17: if (!allow_B) return {}; - return {{{"B"_mst, "B"_mst}, Fragment::OR_D}}; + return {{{B, B}, Fragment::OR_D}}; case 18: if (!(allow_B || allow_K || allow_V)) return {}; return {{{type_needed, type_needed}, Fragment::OR_I}}; @@ -472,25 +473,25 @@ std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider& } case 20: if (!allow_W) return {}; - return {{{"B"_mst}, Fragment::WRAP_A}}; + return {{{B}, Fragment::WRAP_A}}; case 21: if (!allow_W) return {}; - return {{{"B"_mst}, Fragment::WRAP_S}}; + return {{{B}, Fragment::WRAP_S}}; case 22: if (!allow_B) return {}; - return {{{"K"_mst}, Fragment::WRAP_C}}; + return {{{K}, Fragment::WRAP_C}}; case 23: if (!allow_B) return {}; - return {{{"V"_mst}, Fragment::WRAP_D}}; + return {{{V}, Fragment::WRAP_D}}; case 24: if (!allow_V) return {}; - return {{{"B"_mst}, Fragment::WRAP_V}}; + return {{{B}, Fragment::WRAP_V}}; case 25: if (!allow_B) return {}; - return {{{"B"_mst}, Fragment::WRAP_J}}; + return {{{B}, Fragment::WRAP_J}}; case 26: if (!allow_B) return {}; - return {{{"B"_mst}, Fragment::WRAP_N}}; + return {{{B}, Fragment::WRAP_N}}; case 27: { if (!allow_B || !IsTapscript(script_ctx)) return {}; const auto k = provider.ConsumeIntegral<uint16_t>(); @@ -528,20 +529,23 @@ struct SmartInfo { /* Construct a set of interesting type requirements to reason with (sections of BKVWzondu). */ std::vector<Type> types; + static constexpr auto B_mst{"B"_mst}, K_mst{"K"_mst}, V_mst{"V"_mst}, W_mst{"W"_mst}; + static constexpr auto d_mst{"d"_mst}, n_mst{"n"_mst}, o_mst{"o"_mst}, u_mst{"u"_mst}, z_mst{"z"_mst}; + static constexpr auto NONE_mst{""_mst}; for (int base = 0; base < 4; ++base) { /* select from B,K,V,W */ - Type type_base = base == 0 ? "B"_mst : base == 1 ? "K"_mst : base == 2 ? "V"_mst : "W"_mst; + Type type_base = base == 0 ? B_mst : base == 1 ? K_mst : base == 2 ? V_mst : W_mst; for (int zo = 0; zo < 3; ++zo) { /* select from z,o,(none) */ - Type type_zo = zo == 0 ? "z"_mst : zo == 1 ? "o"_mst : ""_mst; + Type type_zo = zo == 0 ? z_mst : zo == 1 ? o_mst : NONE_mst; for (int n = 0; n < 2; ++n) { /* select from (none),n */ if (zo == 0 && n == 1) continue; /* z conflicts with n */ if (base == 3 && n == 1) continue; /* W conflicts with n */ - Type type_n = n == 0 ? ""_mst : "n"_mst; + Type type_n = n == 0 ? NONE_mst : n_mst; for (int d = 0; d < 2; ++d) { /* select from (none),d */ if (base == 2 && d == 1) continue; /* V conflicts with d */ - Type type_d = d == 0 ? ""_mst : "d"_mst; + Type type_d = d == 0 ? NONE_mst : d_mst; for (int u = 0; u < 2; ++u) { /* select from (none),u */ if (base == 2 && u == 1) continue; /* V conflicts with u */ - Type type_u = u == 0 ? ""_mst : "u"_mst; + Type type_u = u == 0 ? NONE_mst : u_mst; Type type = type_base | type_zo | type_n | type_d | type_u; types.push_back(type); } @@ -683,7 +687,7 @@ struct SmartInfo /* Find which types are useful. The fuzzer logic only cares about constructing * B,V,K,W nodes, so any type that isn't needed in any recipe (directly or * indirectly) for the construction of those is uninteresting. */ - std::set<Type> useful_types{"B"_mst, "V"_mst, "K"_mst, "W"_mst}; + std::set<Type> useful_types{B_mst, V_mst, K_mst, W_mst}; // Find the transitive closure by adding types until the set of types does not change. while (true) { size_t set_size = useful_types.size(); @@ -1197,7 +1201,7 @@ void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& p void FuzzInit() { - ECC_Start(); + static ECC_Context ecc_context{}; TEST_DATA.Init(); } diff --git a/src/test/fuzz/multiplication_overflow.cpp b/src/test/fuzz/multiplication_overflow.cpp index aeef4f24b7..a762a4dfe3 100644 --- a/src/test/fuzz/multiplication_overflow.cpp +++ b/src/test/fuzz/multiplication_overflow.cpp @@ -17,12 +17,18 @@ void TestMultiplicationOverflow(FuzzedDataProvider& fuzzed_data_provider) const T i = fuzzed_data_provider.ConsumeIntegral<T>(); const T j = fuzzed_data_provider.ConsumeIntegral<T>(); const bool is_multiplication_overflow_custom = MultiplicationOverflow(i, j); +#ifndef _MSC_VER T result_builtin; const bool is_multiplication_overflow_builtin = __builtin_mul_overflow(i, j, &result_builtin); assert(is_multiplication_overflow_custom == is_multiplication_overflow_builtin); if (!is_multiplication_overflow_custom) { assert(i * j == result_builtin); } +#else + if (!is_multiplication_overflow_custom) { + (void)(i * j); + } +#endif } } // namespace diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 1b7a732260..f6d82c3001 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -25,7 +25,7 @@ std::vector<std::string> g_all_messages; void initialize_p2p_transport_serialization() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); g_all_messages = getAllNetMessageTypes(); std::sort(g_all_messages.begin(), g_all_messages.end()); diff --git a/src/test/fuzz/partially_downloaded_block.cpp b/src/test/fuzz/partially_downloaded_block.cpp index ab75afe066..2bf47930f4 100644 --- a/src/test/fuzz/partially_downloaded_block.cpp +++ b/src/test/fuzz/partially_downloaded_block.cpp @@ -72,7 +72,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb) available.insert(i); } - if (add_to_mempool) { + if (add_to_mempool && !pool.exists(GenTxid::Txid(tx->GetHash()))) { LOCK2(cs_main, pool.cs); pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx)); available.insert(i); diff --git a/src/test/fuzz/poolresource.cpp b/src/test/fuzz/poolresource.cpp index ce64ef6472..f764d9f8db 100644 --- a/src/test/fuzz/poolresource.cpp +++ b/src/test/fuzz/poolresource.cpp @@ -63,9 +63,9 @@ public: { if (m_total_allocated > 0x1000000) return; size_t alignment_bits = m_provider.ConsumeIntegralInRange<size_t>(0, 7); - size_t alignment = 1 << alignment_bits; + size_t alignment = size_t{1} << alignment_bits; size_t size_bits = m_provider.ConsumeIntegralInRange<size_t>(0, 16 - alignment_bits); - size_t size = m_provider.ConsumeIntegralInRange<size_t>(1U << size_bits, (1U << (size_bits + 1)) - 1U) << alignment_bits; + size_t size = m_provider.ConsumeIntegralInRange<size_t>(size_t{1} << size_bits, (size_t{1} << (size_bits + 1)) - 1U) << alignment_bits; Allocate(size, alignment); } diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 9ae150e553..4695bc611b 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -26,7 +26,7 @@ void initialize_script_sign() { - ECC_Start(); + static ECC_Context ecc_context{}; SelectParams(ChainType::REGTEST); } diff --git a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp index 74ef6bfd4e..ae0c8479cb 100644 --- a/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp +++ b/src/test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp @@ -24,8 +24,7 @@ FUZZ_TARGET(secp256k1_ecdsa_signature_parse_der_lax) secp256k1_ecdsa_signature sig_der_lax; const bool parsed_der_lax = ecdsa_signature_parse_der_lax(&sig_der_lax, signature_bytes.data(), signature_bytes.size()) == 1; if (parsed_der_lax) { - ECC_Start(); + ECC_Context ecc_context{}; (void)SigHasLowR(&sig_der_lax); - ECC_Stop(); } } diff --git a/src/test/fuzz/timedata.cpp b/src/test/fuzz/timedata.cpp deleted file mode 100644 index f5d005296b..0000000000 --- a/src/test/fuzz/timedata.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2020-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 <test/fuzz/FuzzedDataProvider.h> -#include <test/fuzz/fuzz.h> -#include <test/fuzz/util.h> -#include <timedata.h> - -#include <cstdint> -#include <string> -#include <vector> - -FUZZ_TARGET(timedata) -{ - FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - const unsigned int max_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000); - // A max_size of 0 implies no limit, so cap the max number of insertions to avoid timeouts - auto max_to_insert = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 4000); - // Divide by 2 to avoid signed integer overflow in .median() - const int64_t initial_value = fuzzed_data_provider.ConsumeIntegral<int64_t>() / 2; - CMedianFilter<int64_t> median_filter{max_size, initial_value}; - while (fuzzed_data_provider.remaining_bytes() > 0 && --max_to_insert >= 0) { - (void)median_filter.median(); - assert(median_filter.size() > 0); - assert(static_cast<size_t>(median_filter.size()) == median_filter.sorted().size()); - assert(static_cast<unsigned int>(median_filter.size()) <= max_size || max_size == 0); - // Divide by 2 to avoid signed integer overflow in .median() - median_filter.input(fuzzed_data_provider.ConsumeIntegral<int64_t>() / 2); - } -} diff --git a/src/test/fuzz/timeoffsets.cpp b/src/test/fuzz/timeoffsets.cpp new file mode 100644 index 0000000000..019337a94a --- /dev/null +++ b/src/test/fuzz/timeoffsets.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2024-present 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/timeoffsets.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/util/setup_common.h> + +#include <chrono> +#include <cstdint> +#include <functional> + +void initialize_timeoffsets() +{ + static const auto testing_setup = MakeNoLogFileContext<>(ChainType::MAIN); +} + +FUZZ_TARGET(timeoffsets, .init = initialize_timeoffsets) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + TimeOffsets offsets{}; + LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 4'000) { + (void)offsets.Median(); + offsets.Add(std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<std::chrono::seconds::rep>()}); + offsets.WarnIfOutOfSync(); + } +} diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 0b4019d5eb..9f0aedf29b 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -139,7 +139,6 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b Assert(wtxid_in_mempool); Assert(res.m_state.IsValid()); Assert(!res.m_state.IsInvalid()); - Assert(res.m_replaced_transactions); Assert(res.m_vsize); Assert(res.m_base_fees); Assert(res.m_effective_feerate); @@ -154,7 +153,6 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b Assert(res.m_state.IsInvalid()); const bool is_reconsiderable{res.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE}; - Assert(!res.m_replaced_transactions); Assert(!res.m_vsize); Assert(!res.m_base_fees); // Fee information is provided if the failure is TX_RECONSIDERABLE. diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index 5423ba8920..7bebb987b1 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -37,13 +37,14 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) SetMockTime(ConsumeTime(fuzzed_data_provider)); TxOrphanage orphanage; - std::vector<COutPoint> outpoints; + std::set<COutPoint> outpoints; + // initial outpoints used to construct transactions later for (uint8_t i = 0; i < 4; i++) { - outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); + outpoints.emplace(Txid::FromUint256(uint256{i}), 0); } - // if true, allow duplicate input when constructing tx - const bool duplicate_input = fuzzed_data_provider.ConsumeBool(); + + CTransactionRef ptx_potential_parent = nullptr; LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) { @@ -51,33 +52,47 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) const CTransactionRef tx = [&] { CMutableTransaction tx_mut; const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); - const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); - // pick unique outpoints from outpoints as input + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256); + // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not + // running any transaction validation logic before adding transactions to the orphanage for (uint32_t i = 0; i < num_in; i++) { auto& prevout = PickValue(fuzzed_data_provider, outpoints); - tx_mut.vin.emplace_back(prevout); - // pop the picked outpoint if duplicate input is not allowed - if (!duplicate_input) { - std::swap(prevout, outpoints.back()); - outpoints.pop_back(); - } + // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen + tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); } // output amount will not affect txorphanage for (uint32_t i = 0; i < num_out; i++) { tx_mut.vout.emplace_back(CAmount{0}, CScript{}); } - // restore previously popped outpoints - for (auto& in : tx_mut.vin) { - outpoints.push_back(in.prevout); - } auto new_tx = MakeTransactionRef(tx_mut); - // add newly constructed transaction to outpoints + // add newly constructed outpoints to the coin pool for (uint32_t i = 0; i < num_out; i++) { - outpoints.emplace_back(new_tx->GetHash(), i); + outpoints.emplace(new_tx->GetHash(), i); } return new_tx; }(); + // Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a + // previous loop and potentially the parent of this tx. + if (ptx_potential_parent) { + // Set up future GetTxToReconsider call. + orphanage.AddChildrenToWorkSet(*ptx_potential_parent); + + // Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx. + NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); + for (const auto& child : orphanage.GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) { + assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) { + return input.prevout.hash == ptx_potential_parent->GetHash(); + })); + } + for (const auto& [child, peer] : orphanage.GetChildrenFromDifferentPeer(ptx_potential_parent, peer_id)) { + assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) { + return input.prevout.hash == ptx_potential_parent->GetHash(); + })); + assert(peer != peer_id); + } + } + // trigger orphanage functions LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) { @@ -86,19 +101,15 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) CallOneOf( fuzzed_data_provider, [&] { - orphanage.AddChildrenToWorkSet(*tx); - }, - [&] { { CTransactionRef ref = orphanage.GetTxToReconsider(peer_id); if (ref) { - bool have_tx = orphanage.HaveTx(GenTxid::Txid(ref->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(ref->GetWitnessHash())); - Assert(have_tx); + Assert(orphanage.HaveTx(ref->GetWitnessHash())); } } }, [&] { - bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); + bool have_tx = orphanage.HaveTx(tx->GetWitnessHash()); // AddTx should return false if tx is too big or already have it // tx weight is unknown, we only check when tx is already in orphanage { @@ -106,7 +117,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) // have_tx == true -> add_tx == false Assert(!have_tx || !add_tx); } - have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); + have_tx = orphanage.HaveTx(tx->GetWitnessHash()); { bool add_tx = orphanage.AddTx(tx, peer_id); // if have_tx is still false, it must be too big @@ -115,15 +126,15 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) } }, [&] { - bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); + bool have_tx = orphanage.HaveTx(tx->GetWitnessHash()); // EraseTx should return 0 if m_orphans doesn't have the tx { - Assert(have_tx == orphanage.EraseTx(tx->GetHash())); + Assert(have_tx == orphanage.EraseTx(tx->GetWitnessHash())); } - have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); + have_tx = orphanage.HaveTx(tx->GetWitnessHash()); // have_tx should be false and EraseTx should fail { - Assert(!have_tx && !orphanage.EraseTx(tx->GetHash())); + Assert(!have_tx && !orphanage.EraseTx(tx->GetWitnessHash())); } }, [&] { @@ -136,6 +147,12 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) orphanage.LimitOrphans(limit, limit_orphans_rng); Assert(orphanage.Size() <= limit); }); + + } + // Set tx as potential parent to be used for future GetChildren() calls. + if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) { + ptx_potential_parent = tx; } + } } diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 217e4a6d22..9f8d434213 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -202,9 +202,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[1].nValue = 1 * COIN; - auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())}; - BOOST_REQUIRE(ancestors_calculated.has_value()); - BOOST_CHECK(*ancestors_calculated == setAncestors); + { + auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())}; + BOOST_REQUIRE(ancestors_calculated.has_value()); + BOOST_CHECK(*ancestors_calculated == setAncestors); + } pool.addUnchecked(entry.FromTx(tx7), setAncestors); BOOST_CHECK_EQUAL(pool.size(), 7U); @@ -260,9 +262,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx10.vout[0].nValue = 10 * COIN; - ancestors_calculated = pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits()); - BOOST_REQUIRE(ancestors_calculated); - BOOST_CHECK(*ancestors_calculated == setAncestors); + { + auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits())}; + BOOST_REQUIRE(ancestors_calculated); + BOOST_CHECK(*ancestors_calculated == setAncestors); + } pool.addUnchecked(entry.FromTx(tx10), setAncestors); diff --git a/src/test/net_peer_connection_tests.cpp b/src/test/net_peer_connection_tests.cpp index 58cbe9eb72..00bc1fdb6a 100644 --- a/src/test/net_peer_connection_tests.cpp +++ b/src/test/net_peer_connection_tests.cpp @@ -108,6 +108,12 @@ BOOST_AUTO_TEST_CASE(test_addnode_getaddednodeinfo_and_connection_detection) AddPeer(id, nodes, *peerman, *connman, ConnectionType::BLOCK_RELAY, /*onion_peer=*/true); AddPeer(id, nodes, *peerman, *connman, ConnectionType::INBOUND); + // Add a CJDNS peer connection. + AddPeer(id, nodes, *peerman, *connman, ConnectionType::INBOUND, /*onion_peer=*/false, + /*address=*/"[fc00:3344:5566:7788:9900:aabb:ccdd:eeff]:1234"); + BOOST_CHECK(nodes.back()->IsInboundConn()); + BOOST_CHECK_EQUAL(nodes.back()->ConnectedThroughNetwork(), Network::NET_CJDNS); + BOOST_TEST_MESSAGE("Call AddNode() for all the peers"); for (auto node : connman->TestNodes()) { BOOST_CHECK(connman->AddNode({/*m_added_node=*/node->addr.ToStringAddrPort(), /*m_use_v2transport=*/true})); diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 70a8279f04..b9dff96610 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -19,7 +19,6 @@ #include <test/util/random.h> #include <test/util/setup_common.h> #include <test/util/validation.h> -#include <timedata.h> #include <util/strencodings.h> #include <util/string.h> #include <validation.h> @@ -902,10 +901,6 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) chainman.ResetIbd(); m_node.args->ForceSetArg("-capturemessages", "0"); m_node.args->ForceSetArg("-bind", ""); - // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state - // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset - // that state as it was before our test was run. - TestOnlyResetTimeData(); } diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index 4231fcc909..450bf6a4fc 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -30,22 +30,74 @@ public: CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); - std::map<Txid, OrphanTx>::iterator it; - it = m_orphans.lower_bound(Txid::FromUint256(InsecureRand256())); + std::map<Wtxid, OrphanTx>::iterator it; + it = m_orphans.lower_bound(Wtxid::FromUint256(InsecureRand256())); if (it == m_orphans.end()) it = m_orphans.begin(); return it->second.tx; } }; -static void MakeNewKeyWithFastRandomContext(CKey& key) +static void MakeNewKeyWithFastRandomContext(CKey& key, FastRandomContext& rand_ctx = g_insecure_rand_ctx) { std::vector<unsigned char> keydata; - keydata = g_insecure_rand_ctx.randbytes(32); + keydata = rand_ctx.randbytes(32); key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); assert(key.IsValid()); } +// Creates a transaction with 2 outputs. Spends all outpoints. If outpoints is empty, spends a random one. +static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, FastRandomContext& det_rand) +{ + CKey key; + MakeNewKeyWithFastRandomContext(key, det_rand); + CMutableTransaction tx; + // If no outpoints are given, create a random one. + if (outpoints.empty()) { + tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0); + } else { + for (const auto& outpoint : outpoints) { + tx.vin.emplace_back(outpoint); + } + } + // Ensure txid != wtxid + tx.vin[0].scriptWitness.stack.push_back({1}); + tx.vout.resize(2); + tx.vout[0].nValue = CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + tx.vout[1].nValue = 3 * CENT; + tx.vout[1].scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(key.GetPubKey())); + return MakeTransactionRef(tx); +} + +// Make another (not necessarily valid) tx with the same txid but different wtxid. +static CTransactionRef MakeMutation(const CTransactionRef& ptx) +{ + CMutableTransaction tx(*ptx); + tx.vin[0].scriptWitness.stack.push_back({5}); + auto mutated_tx = MakeTransactionRef(tx); + assert(ptx->GetHash() == mutated_tx->GetHash()); + return mutated_tx; +} + +static bool EqualTxns(const std::set<CTransactionRef>& set_txns, const std::vector<CTransactionRef>& vec_txns) +{ + if (vec_txns.size() != set_txns.size()) return false; + for (const auto& tx : vec_txns) { + if (!set_txns.contains(tx)) return false; + } + return true; +} +static bool EqualTxns(const std::set<CTransactionRef>& set_txns, + const std::vector<std::pair<CTransactionRef, NodeId>>& vec_txns) +{ + if (vec_txns.size() != set_txns.size()) return false; + for (const auto& [tx, nodeid] : vec_txns) { + if (!set_txns.contains(tx)) return false; + } + return true; +} + BOOST_AUTO_TEST_CASE(DoS_mapOrphans) { // This test had non-deterministic coverage due to @@ -138,4 +190,148 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) BOOST_CHECK(orphanage.CountOrphans() == 0); } +BOOST_AUTO_TEST_CASE(same_txid_diff_witness) +{ + FastRandomContext det_rand{true}; + TxOrphanage orphanage; + NodeId peer{0}; + + std::vector<COutPoint> empty_outpoints; + auto parent = MakeTransactionSpending(empty_outpoints, det_rand); + + // Create children to go into orphanage. + auto child_normal = MakeTransactionSpending({{parent->GetHash(), 0}}, det_rand); + auto child_mutated = MakeMutation(child_normal); + + const auto& normal_wtxid = child_normal->GetWitnessHash(); + const auto& mutated_wtxid = child_mutated->GetWitnessHash(); + BOOST_CHECK(normal_wtxid != mutated_wtxid); + + BOOST_CHECK(orphanage.AddTx(child_normal, peer)); + // EraseTx fails as transaction by this wtxid doesn't exist. + BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 0); + BOOST_CHECK(orphanage.HaveTx(normal_wtxid)); + BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid)); + + // Must succeed. Both transactions should be present in orphanage. + BOOST_CHECK(orphanage.AddTx(child_mutated, peer)); + BOOST_CHECK(orphanage.HaveTx(normal_wtxid)); + BOOST_CHECK(orphanage.HaveTx(mutated_wtxid)); + + // Outpoints map should track all entries: check that both are returned as children of the parent. + std::set<CTransactionRef> expected_children{child_normal, child_mutated}; + BOOST_CHECK(EqualTxns(expected_children, orphanage.GetChildrenFromSamePeer(parent, peer))); + + // Erase by wtxid: mutated first + BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 1); + BOOST_CHECK(orphanage.HaveTx(normal_wtxid)); + BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid)); + + BOOST_CHECK_EQUAL(orphanage.EraseTx(normal_wtxid), 1); + BOOST_CHECK(!orphanage.HaveTx(normal_wtxid)); + BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid)); +} + + +BOOST_AUTO_TEST_CASE(get_children) +{ + FastRandomContext det_rand{true}; + std::vector<COutPoint> empty_outpoints; + + auto parent1 = MakeTransactionSpending(empty_outpoints, det_rand); + auto parent2 = MakeTransactionSpending(empty_outpoints, det_rand); + + // Make sure these parents have different txids otherwise this test won't make sense. + while (parent1->GetHash() == parent2->GetHash()) { + parent2 = MakeTransactionSpending(empty_outpoints, det_rand); + } + + // Create children to go into orphanage. + auto child_p1n0 = MakeTransactionSpending({{parent1->GetHash(), 0}}, det_rand); + auto child_p2n1 = MakeTransactionSpending({{parent2->GetHash(), 1}}, det_rand); + // Spends the same tx twice. Should not cause duplicates. + auto child_p1n0_p1n1 = MakeTransactionSpending({{parent1->GetHash(), 0}, {parent1->GetHash(), 1}}, det_rand); + // Spends the same outpoint as previous tx. Should still be returned; don't assume outpoints are unique. + auto child_p1n0_p2n0 = MakeTransactionSpending({{parent1->GetHash(), 0}, {parent2->GetHash(), 0}}, det_rand); + + const NodeId node1{1}; + const NodeId node2{2}; + + // All orphans provided by node1 + { + TxOrphanage orphanage; + BOOST_CHECK(orphanage.AddTx(child_p1n0, node1)); + BOOST_CHECK(orphanage.AddTx(child_p2n1, node1)); + BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node1)); + BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node1)); + + std::set<CTransactionRef> expected_parent1_children{child_p1n0, child_p1n0_p2n0, child_p1n0_p1n1}; + std::set<CTransactionRef> expected_parent2_children{child_p2n1, child_p1n0_p2n0}; + + BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage.GetChildrenFromSamePeer(parent1, node1))); + BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage.GetChildrenFromSamePeer(parent2, node1))); + + BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage.GetChildrenFromDifferentPeer(parent1, node2))); + BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage.GetChildrenFromDifferentPeer(parent2, node2))); + + // The peer must match + BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent1, node2).empty()); + BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent2, node2).empty()); + + // There shouldn't be any children of this tx in the orphanage + BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node1).empty()); + BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node2).empty()); + BOOST_CHECK(orphanage.GetChildrenFromDifferentPeer(child_p1n0_p2n0, node1).empty()); + BOOST_CHECK(orphanage.GetChildrenFromDifferentPeer(child_p1n0_p2n0, node2).empty()); + } + + // Orphans provided by node1 and node2 + { + TxOrphanage orphanage; + BOOST_CHECK(orphanage.AddTx(child_p1n0, node1)); + BOOST_CHECK(orphanage.AddTx(child_p2n1, node1)); + BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node2)); + BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node2)); + + // +----------------+---------------+----------------------------------+ + // | | sender=node1 | sender=node2 | + // +----------------+---------------+----------------------------------+ + // | spends parent1 | child_p1n0 | child_p1n0_p1n1, child_p1n0_p2n0 | + // | spends parent2 | child_p2n1 | child_p1n0_p2n0 | + // +----------------+---------------+----------------------------------+ + + // Children of parent1 from node1: + { + std::set<CTransactionRef> expected_parent1_node1{child_p1n0}; + + BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage.GetChildrenFromSamePeer(parent1, node1))); + BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage.GetChildrenFromDifferentPeer(parent1, node2))); + } + + // Children of parent2 from node1: + { + std::set<CTransactionRef> expected_parent2_node1{child_p2n1}; + + BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage.GetChildrenFromSamePeer(parent2, node1))); + BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage.GetChildrenFromDifferentPeer(parent2, node2))); + } + + // Children of parent1 from node2: + { + std::set<CTransactionRef> expected_parent1_node2{child_p1n0_p1n1, child_p1n0_p2n0}; + + BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage.GetChildrenFromSamePeer(parent1, node2))); + BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage.GetChildrenFromDifferentPeer(parent1, node1))); + } + + // Children of parent2 from node2: + { + std::set<CTransactionRef> expected_parent2_node2{child_p1n0_p2n0}; + + BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage.GetChildrenFromSamePeer(parent2, node2))); + BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage.GetChildrenFromDifferentPeer(parent2, node1))); + } + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 3a1cb45e8d..1c7d11d8a4 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -552,7 +552,7 @@ BOOST_AUTO_TEST_CASE(help_example) // test different argument types const RPCArgList& args = {{"foo", "bar"}, {"b", true}, {"n", 1}}; BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", args), "> bitcoin-cli -named test foo=bar b=true n=1\n"); - BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); // test shell escape BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b'ar"}}), "> bitcoin-cli -named test foo='b'''ar'\n"); @@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(help_example) obj_value.pushKV("b", false); obj_value.pushKV("n", 1); BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", obj_value}}), "> bitcoin-cli -named test name='{\"foo\":\"bar\",\"b\":false,\"n\":1}'\n"); - BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); // test array params UniValue arr_value(UniValue::VARR); @@ -573,7 +573,7 @@ BOOST_AUTO_TEST_CASE(help_example) arr_value.push_back(false); arr_value.push_back(1); BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", arr_value}}), "> bitcoin-cli -named test name='[\"bar\",false,1]'\n"); - BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"2.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); // test types don't matter for shell BOOST_CHECK_EQUAL(HelpExampleCliNamed("foo", {{"arg", true}}), HelpExampleCliNamed("foo", {{"arg", "true"}})); @@ -582,4 +582,72 @@ BOOST_AUTO_TEST_CASE(help_example) BOOST_CHECK_NE(HelpExampleRpcNamed("foo", {{"arg", true}}), HelpExampleRpcNamed("foo", {{"arg", "true"}})); } +static void CheckRpc(const std::vector<RPCArg>& params, const UniValue& args, RPCHelpMan::RPCMethodImpl test_impl) +{ + auto null_result{RPCResult{RPCResult::Type::NONE, "", "None"}}; + const RPCHelpMan rpc{"dummy", "dummy description", params, null_result, RPCExamples{""}, test_impl}; + JSONRPCRequest req; + req.params = args; + + rpc.HandleRequest(req); +} + +BOOST_AUTO_TEST_CASE(rpc_arg_helper) +{ + constexpr bool DEFAULT_BOOL = true; + constexpr auto DEFAULT_STRING = "default"; + constexpr uint64_t DEFAULT_UINT64_T = 3; + + //! Parameters with which the RPCHelpMan is instantiated + const std::vector<RPCArg> params{ + // Required arg + {"req_int", RPCArg::Type::NUM, RPCArg::Optional::NO, ""}, + {"req_str", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, + // Default arg + {"def_uint64_t", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_UINT64_T}, ""}, + {"def_string", RPCArg::Type::STR, RPCArg::Default{DEFAULT_STRING}, ""}, + {"def_bool", RPCArg::Type::BOOL, RPCArg::Default{DEFAULT_BOOL}, ""}, + // Optional arg without default + {"opt_double", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, ""}, + {"opt_string", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""} + }; + + //! Check that `self.Arg` returns the same value as the `request.params` accessors + RPCHelpMan::RPCMethodImpl check_positional = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + BOOST_CHECK_EQUAL(self.Arg<int>(0), request.params[0].getInt<int>()); + BOOST_CHECK_EQUAL(self.Arg<std::string>(1), request.params[1].get_str()); + BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), request.params[2].isNull() ? DEFAULT_UINT64_T : request.params[2].getInt<uint64_t>()); + BOOST_CHECK_EQUAL(self.Arg<std::string>(3), request.params[3].isNull() ? DEFAULT_STRING : request.params[3].get_str()); + BOOST_CHECK_EQUAL(self.Arg<bool>(4), request.params[4].isNull() ? DEFAULT_BOOL : request.params[4].get_bool()); + if (!request.params[5].isNull()) { + BOOST_CHECK_EQUAL(self.MaybeArg<double>(5).value(), request.params[5].get_real()); + } else { + BOOST_CHECK(!self.MaybeArg<double>(5)); + } + if (!request.params[6].isNull()) { + BOOST_CHECK(self.MaybeArg<std::string>(6)); + BOOST_CHECK_EQUAL(*self.MaybeArg<std::string>(6), request.params[6].get_str()); + } else { + BOOST_CHECK(!self.MaybeArg<std::string>(6)); + } + return UniValue{}; + }; + CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_positional); + CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_positional); + + //! Check that `self.Arg` returns the same value when using index and key + RPCHelpMan::RPCMethodImpl check_named = [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + BOOST_CHECK_EQUAL(self.Arg<int>(0), self.Arg<int>("req_int")); + BOOST_CHECK_EQUAL(self.Arg<std::string>(1), self.Arg<std::string>("req_str")); + BOOST_CHECK_EQUAL(self.Arg<uint64_t>(2), self.Arg<uint64_t>("def_uint64_t")); + BOOST_CHECK_EQUAL(self.Arg<std::string>(3), self.Arg<std::string>("def_string")); + BOOST_CHECK_EQUAL(self.Arg<bool>(4), self.Arg<bool>("def_bool")); + BOOST_CHECK(self.MaybeArg<double>(5) == self.MaybeArg<double>("opt_double")); + BOOST_CHECK(self.MaybeArg<std::string>(6) == self.MaybeArg<std::string>("opt_string")); + return UniValue{}; + }; + CheckRpc(params, UniValue{JSON(R"([5, "hello", null, null, null, null, null])")}, check_named); + CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_named); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index d75eb499b4..b28e1b4196 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -15,6 +15,18 @@ BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup) +// For testing move-semantics, declare a version of datastream that can be moved +// but is not copyable. +class UncopyableStream : public DataStream +{ +public: + using DataStream::DataStream; + UncopyableStream(const UncopyableStream&) = delete; + UncopyableStream& operator=(const UncopyableStream&) = delete; + UncopyableStream(UncopyableStream&&) noexcept = default; + UncopyableStream& operator=(UncopyableStream&&) noexcept = default; +}; + class CSerializeMethodsTestSingle { protected: @@ -289,7 +301,7 @@ public: template <typename Stream> void Serialize(Stream& s) const { - if (s.GetParams().m_base_format == BaseFormat::RAW) { + if (s.template GetParams<BaseFormat>().m_base_format == BaseFormat::RAW) { s << m_base_data; } else { s << Span{HexStr(Span{&m_base_data, 1})}; @@ -299,7 +311,7 @@ public: template <typename Stream> void Unserialize(Stream& s) { - if (s.GetParams().m_base_format == BaseFormat::RAW) { + if (s.template GetParams<BaseFormat>().m_base_format == BaseFormat::RAW) { s >> m_base_data; } else { std::string hex{"aa"}; @@ -327,8 +339,9 @@ class Derived : public Base public: std::string m_derived_data; - SERIALIZE_METHODS_PARAMS(Derived, obj, DerivedAndBaseFormat, fmt) + SERIALIZE_METHODS(Derived, obj) { + auto& fmt = SER_PARAMS(DerivedAndBaseFormat); READWRITE(fmt.m_base_format(AsBase<Base>(obj))); if (ser_action.ForRead()) { @@ -343,6 +356,76 @@ public: } }; +struct OtherParam { + uint8_t param; + SER_PARAMS_OPFUNC +}; + +//! Checker for value of OtherParam. When being serialized, serializes the +//! param to the stream. When being unserialized, verifies the value in the +//! stream matches the param. +class OtherParamChecker +{ +public: + template <typename Stream> + void Serialize(Stream& s) const + { + const uint8_t param = s.template GetParams<OtherParam>().param; + s << param; + } + + template <typename Stream> + void Unserialize(Stream& s) const + { + const uint8_t param = s.template GetParams<OtherParam>().param; + uint8_t value; + s >> value; + BOOST_CHECK_EQUAL(value, param); + } +}; + +//! Test creating a stream with multiple parameters and making sure +//! serialization code requiring different parameters can retrieve them. Also +//! test that earlier parameters take precedence if the same parameter type is +//! specified twice. (Choice of whether earlier or later values take precedence +//! or multiple values of the same type are allowed was arbitrary, and just +//! decided based on what would require smallest amount of ugly C++ template +//! code. Intent of the test is to just ensure there is no unexpected behavior.) +BOOST_AUTO_TEST_CASE(with_params_multi) +{ + const OtherParam other_param_used{.param = 0x10}; + const OtherParam other_param_ignored{.param = 0x11}; + const OtherParam other_param_override{.param = 0x12}; + const OtherParamChecker check; + DataStream stream; + ParamsStream pstream{stream, RAW, other_param_used, other_param_ignored}; + + Base base1{0x20}; + pstream << base1 << check << other_param_override(check); + BOOST_CHECK_EQUAL(stream.str(), "\x20\x10\x12"); + + Base base2; + pstream >> base2 >> check >> other_param_override(check); + BOOST_CHECK_EQUAL(base2.m_base_data, 0x20); +} + +//! Test creating a ParamsStream that moves from a stream argument. +BOOST_AUTO_TEST_CASE(with_params_move) +{ + UncopyableStream stream{MakeByteSpan(std::string_view{"abc"})}; + ParamsStream pstream{std::move(stream), RAW, HEX, RAW}; + BOOST_CHECK_EQUAL(pstream.GetStream().str(), "abc"); + pstream.GetStream().clear(); + + Base base1{0x20}; + pstream << base1; + BOOST_CHECK_EQUAL(pstream.GetStream().str(), "\x20"); + + Base base2; + pstream >> base2; + BOOST_CHECK_EQUAL(base2.m_base_data, 0x20); +} + BOOST_AUTO_TEST_CASE(with_params_base) { Base b{0x0F}; diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 0903f987f6..e666e11758 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -143,8 +143,8 @@ BOOST_AUTO_TEST_CASE(streams_vector_reader) BOOST_CHECK_EQUAL(reader.size(), 5U); BOOST_CHECK(!reader.empty()); - // Read a single byte as a signed char. - signed char b; + // Read a single byte as a int8_t. + int8_t b; reader >> b; BOOST_CHECK_EQUAL(b, -1); BOOST_CHECK_EQUAL(reader.size(), 4U); diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index 2de147deea..baa759e42c 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -3,9 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <test/util/setup_common.h> #include <common/run_command.h> #include <univalue.h> diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp deleted file mode 100644 index 2814dbf4c0..0000000000 --- a/src/test/timedata_tests.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2011-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 <netaddress.h> -#include <noui.h> -#include <test/util/logging.h> -#include <test/util/setup_common.h> -#include <timedata.h> -#include <util/string.h> -#include <util/translation.h> -#include <warnings.h> - -#include <string> - -#include <boost/test/unit_test.hpp> - -BOOST_FIXTURE_TEST_SUITE(timedata_tests, BasicTestingSetup) - -BOOST_AUTO_TEST_CASE(util_MedianFilter) -{ - CMedianFilter<int> filter(5, 15); - - BOOST_CHECK_EQUAL(filter.median(), 15); - - filter.input(20); // [15 20] - BOOST_CHECK_EQUAL(filter.median(), 17); - - filter.input(30); // [15 20 30] - BOOST_CHECK_EQUAL(filter.median(), 20); - - filter.input(3); // [3 15 20 30] - BOOST_CHECK_EQUAL(filter.median(), 17); - - filter.input(7); // [3 7 15 20 30] - BOOST_CHECK_EQUAL(filter.median(), 15); - - filter.input(18); // [3 7 18 20 30] - BOOST_CHECK_EQUAL(filter.median(), 18); - - filter.input(0); // [0 3 7 18 30] - BOOST_CHECK_EQUAL(filter.median(), 7); -} - -static void MultiAddTimeData(int n, int64_t offset) -{ - static int cnt = 0; - for (int i = 0; i < n; ++i) { - CNetAddr addr; - addr.SetInternal(ToString(++cnt)); - AddTimeData(addr, offset); - } -} - - -BOOST_AUTO_TEST_CASE(addtimedata) -{ - BOOST_CHECK_EQUAL(GetTimeOffset(), 0); - - //Part 1: Add large offsets to test a warning message that our clock may be wrong. - MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1); - // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0) - - { - ASSERT_DEBUG_LOG("Please check that your computer's date and time are correct!"); - MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 - } - - BOOST_CHECK(GetWarnings(true).original.find("clock is wrong") != std::string::npos); - - // nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT - BOOST_CHECK_EQUAL(GetTimeOffset(), 0); - - // Part 2: Test positive and negative medians by adding more offsets - MultiAddTimeData(4, 100); // filter size 9 - BOOST_CHECK_EQUAL(GetTimeOffset(), 100); - MultiAddTimeData(10, -100); //filter size 19 - BOOST_CHECK_EQUAL(GetTimeOffset(), -100); - - // Part 3: Test behaviour when filter has reached maximum number of offsets - const int MAX_SAMPLES = 200; - int nfill = (MAX_SAMPLES - 3 - 19) / 2; //89 - MultiAddTimeData(nfill, 100); - MultiAddTimeData(nfill, -100); //filter size MAX_SAMPLES - 3 - BOOST_CHECK_EQUAL(GetTimeOffset(), -100); - - MultiAddTimeData(2, 100); - //filter size MAX_SAMPLES -1, median is the initial 0 offset - //since we added same number of positive/negative offsets - - BOOST_CHECK_EQUAL(GetTimeOffset(), 0); - - // After the number of offsets has reached MAX_SAMPLES -1 (=199), nTimeOffset will never change - // because it is only updated when the number of elements in the filter becomes odd. It was decided - // not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521 - // for a more detailed explanation. - MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change - // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail. - BOOST_CHECK_EQUAL(GetTimeOffset(), 0); - - TestOnlyResetTimeData(); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/timeoffsets_tests.cpp b/src/test/timeoffsets_tests.cpp new file mode 100644 index 0000000000..008f1a9db9 --- /dev/null +++ b/src/test/timeoffsets_tests.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2024-present 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/timeoffsets.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +#include <chrono> +#include <vector> + +using namespace std::chrono_literals; + +static void AddMulti(TimeOffsets& offsets, const std::vector<std::chrono::seconds>& to_add) +{ + for (auto offset : to_add) { + offsets.Add(offset); + } +} + +BOOST_FIXTURE_TEST_SUITE(timeoffsets_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(timeoffsets) +{ + TimeOffsets offsets{}; + BOOST_CHECK(offsets.Median() == 0s); + + AddMulti(offsets, {{0s, -1s, -2s, -3s}}); + // median should be zero for < 5 offsets + BOOST_CHECK(offsets.Median() == 0s); + + offsets.Add(-4s); + // we now have 5 offsets: [-4, -3, -2, -1, 0] + BOOST_CHECK(offsets.Median() == -2s); + + AddMulti(offsets, {4, 5s}); + // we now have 9 offsets: [-4, -3, -2, -1, 0, 5, 5, 5, 5] + BOOST_CHECK(offsets.Median() == 0s); + + AddMulti(offsets, {41, 10s}); + // the TimeOffsets is now at capacity with 50 offsets, oldest offsets is discarded for any additional offset + BOOST_CHECK(offsets.Median() == 10s); + + AddMulti(offsets, {25, 15s}); + // we now have 25 offsets of 10s followed by 25 offsets of 15s + BOOST_CHECK(offsets.Median() == 15s); +} + +static bool IsWarningRaised(const std::vector<std::chrono::seconds>& check_offsets) +{ + TimeOffsets offsets{}; + AddMulti(offsets, check_offsets); + return offsets.WarnIfOutOfSync(); +} + + +BOOST_AUTO_TEST_CASE(timeoffsets_warning) +{ + BOOST_CHECK(IsWarningRaised({{-60min, -40min, -30min, 0min, 10min}})); + BOOST_CHECK(IsWarningRaised({5, 11min})); + + BOOST_CHECK(!IsWarningRaised({4, 60min})); + BOOST_CHECK(!IsWarningRaised({100, 3min})); +} + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index b948ea8acb..55e0c5f285 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -8,9 +8,12 @@ #include <policy/policy.h> #include <primitives/transaction.h> #include <script/script.h> +#include <serialize.h> +#include <streams.h> #include <test/util/random.h> #include <test/util/script.h> #include <test/util/setup_common.h> +#include <util/strencodings.h> #include <test/util/txmempool.h> #include <validation.h> @@ -40,6 +43,93 @@ inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outpu return MakeTransactionRef(mtx); } +// Create a Wtxid from a hex string +inline Wtxid WtxidFromString(std::string_view str) +{ + return Wtxid::FromUint256(uint256S(str.data())); +} + +BOOST_FIXTURE_TEST_CASE(package_hash_tests, TestChain100Setup) +{ + // Random real segwit transaction + DataStream stream_1{ + ParseHex("02000000000101964b8aa63509579ca6086e6012eeaa4c2f4dd1e283da29b67c8eea38b3c6fd220000000000fdffffff0294c618000000000017a9145afbbb42f4e83312666d0697f9e66259912ecde38768fa2c0000000000160014897388a0889390fd0e153a22bb2cf9d8f019faf50247304402200547406380719f84d68cf4e96cc3e4a1688309ef475b150be2b471c70ea562aa02206d255f5acc40fd95981874d77201d2eb07883657ce1c796513f32b6079545cdf0121023ae77335cefcb5ab4c1dc1fb0d2acfece184e593727d7d5906c78e564c7c11d125cf0c00"), + }; + CTransaction tx_1(deserialize, TX_WITH_WITNESS, stream_1); + CTransactionRef ptx_1{MakeTransactionRef(tx_1)}; + + // Random real nonsegwit transaction + DataStream stream_2{ + ParseHex("01000000010b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e88607d722c190000000008b4830450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a0141046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339ffffffff021bff3d11000000001976a91404943fdd508053c75000106d3bc6e2754dbcff1988ac2f15de00000000001976a914a266436d2965547608b9e15d9032a7b9d64fa43188ac00000000"), + }; + CTransaction tx_2(deserialize, TX_WITH_WITNESS, stream_2); + CTransactionRef ptx_2{MakeTransactionRef(tx_2)}; + + // Random real segwit transaction + DataStream stream_3{ + ParseHex("0200000000010177862801f77c2c068a70372b4c435ef8dd621291c36a64eb4dd491f02218f5324600000000fdffffff014a0100000000000022512035ea312034cfac01e956a269f3bf147f569c2fbb00180677421262da042290d803402be713325ff285e66b0380f53f2fae0d0fb4e16f378a440fed51ce835061437566729d4883bc917632f3cff474d6384bc8b989961a1d730d4a87ed38ad28bd337b20f1d658c6c138b1c312e072b4446f50f01ae0da03a42e6274f8788aae53416a7fac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800357b2270223a226272632d3230222c226f70223a226d696e74222c227469636b223a224342414c222c22616d74223a2236393639227d6821c1f1d658c6c138b1c312e072b4446f50f01ae0da03a42e6274f8788aae53416a7f00000000"), + }; + CTransaction tx_3(deserialize, TX_WITH_WITNESS, stream_3); + CTransactionRef ptx_3{MakeTransactionRef(tx_3)}; + + // It's easy to see that wtxids are sorted in lexicographical order: + Wtxid wtxid_1{WtxidFromString("0x85cd1a31eb38f74ed5742ec9cb546712ab5aaf747de28a9168b53e846cbda17f")}; + Wtxid wtxid_2{WtxidFromString("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b")}; + Wtxid wtxid_3{WtxidFromString("0xe065bac15f62bb4e761d761db928ddee65a47296b2b776785abb912cdec474e3")}; + BOOST_CHECK_EQUAL(tx_1.GetWitnessHash(), wtxid_1); + BOOST_CHECK_EQUAL(tx_2.GetWitnessHash(), wtxid_2); + BOOST_CHECK_EQUAL(tx_3.GetWitnessHash(), wtxid_3); + + BOOST_CHECK(wtxid_1.GetHex() < wtxid_2.GetHex()); + BOOST_CHECK(wtxid_2.GetHex() < wtxid_3.GetHex()); + + // The txids are not (we want to test that sorting and hashing use wtxid, not txid): + Txid txid_1{TxidFromString("0xbd0f71c1d5e50589063e134fad22053cdae5ab2320db5bf5e540198b0b5a4e69")}; + Txid txid_2{TxidFromString("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b")}; + Txid txid_3{TxidFromString("0xee707be5201160e32c4fc715bec227d1aeea5940fb4295605e7373edce3b1a93")}; + BOOST_CHECK_EQUAL(tx_1.GetHash(), txid_1); + BOOST_CHECK_EQUAL(tx_2.GetHash(), txid_2); + BOOST_CHECK_EQUAL(tx_3.GetHash(), txid_3); + + BOOST_CHECK(txid_2.GetHex() < txid_1.GetHex()); + + BOOST_CHECK(txid_1.ToUint256() != wtxid_1.ToUint256()); + BOOST_CHECK(txid_2.ToUint256() == wtxid_2.ToUint256()); + BOOST_CHECK(txid_3.ToUint256() != wtxid_3.ToUint256()); + + // We are testing that both functions compare using GetHex() and not uint256. + // (in this pair of wtxids, hex string order != uint256 order) + BOOST_CHECK(wtxid_2 < wtxid_1); + // (in this pair of wtxids, hex string order == uint256 order) + BOOST_CHECK(wtxid_2 < wtxid_3); + + // All permutations of the package containing ptx_1, ptx_2, ptx_3 have the same package hash + std::vector<CTransactionRef> package_123{ptx_1, ptx_2, ptx_3}; + std::vector<CTransactionRef> package_132{ptx_1, ptx_3, ptx_2}; + std::vector<CTransactionRef> package_231{ptx_2, ptx_3, ptx_1}; + std::vector<CTransactionRef> package_213{ptx_2, ptx_1, ptx_3}; + std::vector<CTransactionRef> package_312{ptx_3, ptx_1, ptx_2}; + std::vector<CTransactionRef> package_321{ptx_3, ptx_2, ptx_1}; + + uint256 calculated_hash_123 = (HashWriter() << wtxid_1 << wtxid_2 << wtxid_3).GetSHA256(); + + uint256 hash_if_by_txid = (HashWriter() << wtxid_2 << wtxid_1 << wtxid_3).GetSHA256(); + BOOST_CHECK(hash_if_by_txid != calculated_hash_123); + + uint256 hash_if_use_txid = (HashWriter() << txid_2 << txid_1 << txid_3).GetSHA256(); + BOOST_CHECK(hash_if_use_txid != calculated_hash_123); + + uint256 hash_if_use_int_order = (HashWriter() << wtxid_2 << wtxid_1 << wtxid_3).GetSHA256(); + BOOST_CHECK(hash_if_use_int_order != calculated_hash_123); + + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_123)); + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_132)); + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_231)); + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_213)); + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_312)); + BOOST_CHECK_EQUAL(calculated_hash_123, GetPackageHash(package_321)); +} + BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100Setup) { // Packages can't have more than 25 transactions. @@ -190,6 +280,9 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup) BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted"); BOOST_CHECK(IsChildWithParents({tx_parent, tx_child})); BOOST_CHECK(IsChildWithParentsTree({tx_parent, tx_child})); + BOOST_CHECK(GetPackageHash({tx_parent}) != GetPackageHash({tx_child})); + BOOST_CHECK(GetPackageHash({tx_child, tx_child}) != GetPackageHash({tx_child})); + BOOST_CHECK(GetPackageHash({tx_child, tx_parent}) != GetPackageHash({tx_child, tx_child})); } // 24 Parents and 1 Child @@ -450,6 +543,8 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) BOOST_CHECK_EQUAL(ptx_child1->GetHash(), ptx_child2->GetHash()); // child1 and child2 have different wtxids BOOST_CHECK(ptx_child1->GetWitnessHash() != ptx_child2->GetWitnessHash()); + // Check that they have different package hashes + BOOST_CHECK(GetPackageHash({ptx_parent, ptx_child1}) != GetPackageHash({ptx_parent, ptx_child2})); // Try submitting Package1{parent, child1} and Package2{parent, child2} where the children are // same-txid-different-witness. @@ -503,7 +598,8 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) /*output_destination=*/grandchild_locking_script, /*output_amount=*/CAmount(47 * COIN), /*submit=*/false); CTransactionRef ptx_grandchild = MakeTransactionRef(mtx_grandchild); - + // Check that they have different package hashes + BOOST_CHECK(GetPackageHash({ptx_child1, ptx_grandchild}) != GetPackageHash({ptx_child2, ptx_grandchild})); // We already submitted child1 above. { Package package_child2_grandchild{ptx_child2, ptx_grandchild}; @@ -583,7 +679,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) CTransactionRef ptx_parent3 = MakeTransactionRef(mtx_parent3); package_mixed.push_back(ptx_parent3); BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*ptx_parent3)) > low_fee_amt); - BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*ptx_parent3)) <= low_fee_amt); + BOOST_CHECK(m_node.mempool->m_opts.min_relay_feerate.GetFee(GetVirtualTransactionSize(*ptx_parent3)) <= low_fee_amt); // child spends parent1, parent2, and parent3 CKey mixed_grandchild_key = GenerateRandomKey(); @@ -729,7 +825,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup) CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap); package_still_too_low.push_back(tx_parent_cheap); BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) > parent_fee); - BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee); + BOOST_CHECK(m_node.mempool->m_opts.min_relay_feerate.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee); auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0, /*input_height=*/101, /*input_signing_key=*/child_key, diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 38350b33cc..e9566cf50b 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <test/util/setup_common.h> @@ -185,6 +183,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto AppInitParameterInteraction(*m_node.args); LogInstance().StartLogging(); m_node.kernel = std::make_unique<kernel::Context>(); + m_node.ecc_context = std::make_unique<ECC_Context>(); SetupEnvironment(); ValidationCacheSizes validation_cache_sizes{}; @@ -202,6 +201,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto BasicTestingSetup::~BasicTestingSetup() { + m_node.ecc_context.reset(); m_node.kernel.reset(); SetMockTime(0s); // Reset mocktime for following tests LogInstance().DisconnectTestLogger(); @@ -552,9 +552,9 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate) assert(m_node.mempool->size() == 0); // The target feerate cannot be too low... // ...otherwise the transaction's feerate will need to be negative. - assert(target_feerate > m_node.mempool->m_incremental_relay_feerate); + assert(target_feerate > m_node.mempool->m_opts.incremental_relay_feerate); // ...otherwise this is not meaningful. The feerate policy uses the maximum of both feerates. - assert(target_feerate > m_node.mempool->m_min_relay_feerate); + assert(target_feerate > m_node.mempool->m_opts.min_relay_feerate); // Manually create an invalid transaction. Manually set the fee in the CTxMemPoolEntry to // achieve the exact target feerate. @@ -565,7 +565,7 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate) LockPoints lp; // The new mempool min feerate is equal to the removed package's feerate + incremental feerate. const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) - - m_node.mempool->m_incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx)); + m_node.mempool->m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx)); m_node.mempool->addUnchecked(CTxMemPoolEntry(tx, /*fee=*/tx_fee, /*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0, /*spends_coinbase=*/true, /*sigops_cost=*/1, lp)); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 8ccf9b571c..dbd66e3585 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -6,6 +6,7 @@ #define BITCOIN_TEST_UTIL_SETUP_COMMON_H #include <common/args.h> // IWYU pragma: export +#include <kernel/context.h> #include <key.h> #include <node/caches.h> #include <node/context.h> // IWYU pragma: export @@ -15,6 +16,7 @@ #include <util/chaintype.h> // IWYU pragma: export #include <util/check.h> #include <util/fs.h> +#include <util/signalinterrupt.h> #include <util/string.h> #include <util/vector.h> diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp index 71cf8aca60..870cdd0b32 100644 --- a/src/test/util/txmempool.cpp +++ b/src/test/util/txmempool.cpp @@ -68,12 +68,6 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns, return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString()); } - //m_replaced_transactions should exist iff the result was VALID - if (atmp_result.m_replaced_transactions.has_value() != valid) { - return strprintf("tx %s result should %shave m_replaced_transactions", - wtxid.ToString(), valid ? "" : "not "); - } - // m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY}; if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) { diff --git a/src/test/util_threadnames_tests.cpp b/src/test/util_threadnames_tests.cpp index 45d3a58fd3..df5b1a4461 100644 --- a/src/test/util_threadnames_tests.cpp +++ b/src/test/util_threadnames_tests.cpp @@ -11,9 +11,7 @@ #include <thread> #include <vector> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <boost/test/unit_test.hpp> diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index fe2d2ba592..1c02066047 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -12,6 +12,7 @@ #include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> +#include <util/check.h> #include <validation.h> #include <vector> @@ -102,14 +103,14 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2); - Chainstate& background_cs{*[&] { + Chainstate& background_cs{*Assert([&]() -> Chainstate* { for (Chainstate* cs : chainman.GetAll()) { if (cs != &chainman.ActiveChainstate()) { return cs; } } - assert(false); - }()}; + return nullptr; + }())}; // Append the first block to the background chain. BlockValidationState state; diff --git a/src/timedata.cpp b/src/timedata.cpp deleted file mode 100644 index d948de976f..0000000000 --- a/src/timedata.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2014-2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <timedata.h> - -#include <common/args.h> -#include <logging.h> -#include <netaddress.h> -#include <node/interface_ui.h> -#include <sync.h> -#include <tinyformat.h> -#include <util/translation.h> -#include <warnings.h> - -static GlobalMutex g_timeoffset_mutex; -static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0; - -/** - * "Never go to sea with two chronometers; take one or three." - * Our three time sources are: - * - System clock - * - Median of other nodes clocks - * - The user (asking the user to fix the system clock if the first two disagree) - */ -int64_t GetTimeOffset() -{ - LOCK(g_timeoffset_mutex); - return nTimeOffset; -} - -#define BITCOIN_TIMEDATA_MAX_SAMPLES 200 - -static std::set<CNetAddr> g_sources; -static CMedianFilter<int64_t> g_time_offsets{BITCOIN_TIMEDATA_MAX_SAMPLES, 0}; -static bool g_warning_emitted; - -void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) -{ - LOCK(g_timeoffset_mutex); - // Ignore duplicates - if (g_sources.size() == BITCOIN_TIMEDATA_MAX_SAMPLES) - return; - if (!g_sources.insert(ip).second) - return; - - // Add data - g_time_offsets.input(nOffsetSample); - LogPrint(BCLog::NET, "added time data, samples %d, offset %+d (%+d minutes)\n", g_time_offsets.size(), nOffsetSample, nOffsetSample / 60); - - // There is a known issue here (see issue #4521): - // - // - The structure g_time_offsets contains up to 200 elements, after which - // any new element added to it will not increase its size, replacing the - // oldest element. - // - // - The condition to update nTimeOffset includes checking whether the - // number of elements in g_time_offsets is odd, which will never happen after - // there are 200 elements. - // - // But in this case the 'bug' is protective against some attacks, and may - // actually explain why we've never seen attacks which manipulate the - // clock offset. - // - // So we should hold off on fixing this and clean it up as part of - // a timing cleanup that strengthens it in a number of other ways. - // - if (g_time_offsets.size() >= 5 && g_time_offsets.size() % 2 == 1) { - int64_t nMedian = g_time_offsets.median(); - std::vector<int64_t> vSorted = g_time_offsets.sorted(); - // Only let other nodes change our time by so much - int64_t max_adjustment = std::max<int64_t>(0, gArgs.GetIntArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT)); - if (nMedian >= -max_adjustment && nMedian <= max_adjustment) { - nTimeOffset = nMedian; - } else { - nTimeOffset = 0; - - if (!g_warning_emitted) { - // If nobody has a time different than ours but within 5 minutes of ours, give a warning - bool fMatch = false; - for (const int64_t nOffset : vSorted) { - if (nOffset != 0 && nOffset > -5 * 60 && nOffset < 5 * 60) fMatch = true; - } - - if (!fMatch) { - g_warning_emitted = true; - bilingual_str strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly."), PACKAGE_NAME); - SetMiscWarning(strMessage); - uiInterface.ThreadSafeMessageBox(strMessage, "", CClientUIInterface::MSG_WARNING); - } - } - } - - 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); - } - log_message += strprintf("| median offset = %+d (%+d minutes)", nTimeOffset, nTimeOffset / 60); - LogPrint(BCLog::NET, "%s\n", log_message); - } - } -} - -void TestOnlyResetTimeData() -{ - LOCK(g_timeoffset_mutex); - nTimeOffset = 0; - g_sources.clear(); - g_time_offsets = CMedianFilter<int64_t>{BITCOIN_TIMEDATA_MAX_SAMPLES, 0}; - g_warning_emitted = false; -} diff --git a/src/timedata.h b/src/timedata.h deleted file mode 100644 index 3e76f80452..0000000000 --- a/src/timedata.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2014-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_TIMEDATA_H -#define BITCOIN_TIMEDATA_H - -#include <algorithm> -#include <cassert> -#include <cstdint> -#include <vector> - -static const int64_t DEFAULT_MAX_TIME_ADJUSTMENT = 70 * 60; - -class CNetAddr; - -/** - * Median filter over a stream of values. - * Returns the median of the last N numbers - */ -template <typename T> -class CMedianFilter -{ -private: - std::vector<T> vValues; - std::vector<T> vSorted; - unsigned int nSize; - -public: - CMedianFilter(unsigned int _size, T initial_value) : nSize(_size) - { - vValues.reserve(_size); - vValues.push_back(initial_value); - vSorted = vValues; - } - - void input(T value) - { - if (vValues.size() == nSize) { - vValues.erase(vValues.begin()); - } - vValues.push_back(value); - - vSorted.resize(vValues.size()); - std::copy(vValues.begin(), vValues.end(), vSorted.begin()); - std::sort(vSorted.begin(), vSorted.end()); - } - - T median() const - { - int vSortedSize = vSorted.size(); - assert(vSortedSize > 0); - if (vSortedSize & 1) // Odd number of elements - { - return vSorted[vSortedSize / 2]; - } else // Even number of elements - { - return (vSorted[vSortedSize / 2 - 1] + vSorted[vSortedSize / 2]) / 2; - } - } - - int size() const - { - return vValues.size(); - } - - std::vector<T> sorted() const - { - return vSorted; - } -}; - -/** Functions to keep track of adjusted P2P time */ -int64_t GetTimeOffset(); -void AddTimeData(const CNetAddr& ip, int64_t nTime); - -/** - * Reset the internal state of GetTimeOffset() and AddTimeData(). - */ -void TestOnlyResetTimeData(); - -#endif // BITCOIN_TIMEDATA_H diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 06066e38b2..c845944d32 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -92,7 +92,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan // Don't directly remove the transaction here -- doing so would // invalidate iterators in cachedDescendants. Mark it for removal // by inserting into descendants_to_remove. - if (descendant.GetCountWithAncestors() > uint64_t(m_limits.ancestor_count) || descendant.GetSizeWithAncestors() > m_limits.ancestor_size_vbytes) { + if (descendant.GetCountWithAncestors() > uint64_t(m_opts.limits.ancestor_count) || descendant.GetSizeWithAncestors() > m_opts.limits.ancestor_size_vbytes) { descendants_to_remove.insert(descendant.GetTx().GetHash()); } } @@ -203,14 +203,14 @@ util::Result<void> CTxMemPool::CheckPackageLimits(const Package& package, size_t pack_count = package.size(); // Package itself is busting mempool limits; should be rejected even if no staged_ancestors exist - if (pack_count > static_cast<uint64_t>(m_limits.ancestor_count)) { - return util::Error{Untranslated(strprintf("package count %u exceeds ancestor count limit [limit: %u]", pack_count, m_limits.ancestor_count))}; - } else if (pack_count > static_cast<uint64_t>(m_limits.descendant_count)) { - return util::Error{Untranslated(strprintf("package count %u exceeds descendant count limit [limit: %u]", pack_count, m_limits.descendant_count))}; - } else if (total_vsize > m_limits.ancestor_size_vbytes) { - return util::Error{Untranslated(strprintf("package size %u exceeds ancestor size limit [limit: %u]", total_vsize, m_limits.ancestor_size_vbytes))}; - } else if (total_vsize > m_limits.descendant_size_vbytes) { - return util::Error{Untranslated(strprintf("package size %u exceeds descendant size limit [limit: %u]", total_vsize, m_limits.descendant_size_vbytes))}; + if (pack_count > static_cast<uint64_t>(m_opts.limits.ancestor_count)) { + return util::Error{Untranslated(strprintf("package count %u exceeds ancestor count limit [limit: %u]", pack_count, m_opts.limits.ancestor_count))}; + } else if (pack_count > static_cast<uint64_t>(m_opts.limits.descendant_count)) { + return util::Error{Untranslated(strprintf("package count %u exceeds descendant count limit [limit: %u]", pack_count, m_opts.limits.descendant_count))}; + } else if (total_vsize > m_opts.limits.ancestor_size_vbytes) { + return util::Error{Untranslated(strprintf("package size %u exceeds ancestor size limit [limit: %u]", total_vsize, m_opts.limits.ancestor_size_vbytes))}; + } else if (total_vsize > m_opts.limits.descendant_size_vbytes) { + return util::Error{Untranslated(strprintf("package size %u exceeds descendant size limit [limit: %u]", total_vsize, m_opts.limits.descendant_size_vbytes))}; } CTxMemPoolEntry::Parents staged_ancestors; @@ -219,8 +219,8 @@ util::Result<void> CTxMemPool::CheckPackageLimits(const Package& package, std::optional<txiter> piter = GetIter(input.prevout.hash); if (piter) { staged_ancestors.insert(**piter); - if (staged_ancestors.size() + package.size() > static_cast<uint64_t>(m_limits.ancestor_count)) { - return util::Error{Untranslated(strprintf("too many unconfirmed parents [limit: %u]", m_limits.ancestor_count))}; + if (staged_ancestors.size() + package.size() > static_cast<uint64_t>(m_opts.limits.ancestor_count)) { + return util::Error{Untranslated(strprintf("too many unconfirmed parents [limit: %u]", m_opts.limits.ancestor_count))}; } } } @@ -229,7 +229,7 @@ util::Result<void> CTxMemPool::CheckPackageLimits(const Package& package, // considered together must be within limits even if they are not interdependent. This may be // stricter than the limits for each individual transaction. const auto ancestors{CalculateAncestorsAndCheckLimits(total_vsize, package.size(), - staged_ancestors, m_limits)}; + staged_ancestors, m_opts.limits)}; // It's possible to overestimate the ancestor/descendant totals. if (!ancestors.has_value()) return util::Error{Untranslated("possibly " + util::ErrorString(ancestors).original)}; return {}; @@ -396,19 +396,7 @@ void CTxMemPoolEntry::UpdateAncestorState(int32_t modifySize, CAmount modifyFee, } CTxMemPool::CTxMemPool(const Options& opts) - : m_check_ratio{opts.check_ratio}, - m_max_size_bytes{opts.max_size_bytes}, - m_expiry{opts.expiry}, - m_incremental_relay_feerate{opts.incremental_relay_feerate}, - m_min_relay_feerate{opts.min_relay_feerate}, - m_dust_relay_feerate{opts.dust_relay_feerate}, - m_permit_bare_multisig{opts.permit_bare_multisig}, - m_max_datacarrier_bytes{opts.max_datacarrier_bytes}, - m_require_standard{opts.require_standard}, - m_full_rbf{opts.full_rbf}, - m_persist_v1_dat{opts.persist_v1_dat}, - m_limits{opts.limits}, - m_signals{opts.signals} + : m_opts{opts} { } @@ -489,12 +477,12 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) // even if not directly reported below. uint64_t mempool_sequence = GetAndIncrementSequence(); - if (reason != MemPoolRemovalReason::BLOCK && m_signals) { + if (reason != MemPoolRemovalReason::BLOCK && m_opts.signals) { // Notify clients that a transaction has been removed from the mempool // for any reason except being included in a block. Clients interested // in transactions included in blocks can subscribe to the BlockConnected // notification. - m_signals->TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence); + m_opts.signals->TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence); } TRACE5(mempool, removed, it->GetTx().GetHash().data(), @@ -645,8 +633,8 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne removeConflicts(*tx); ClearPrioritisation(tx->GetHash()); } - if (m_signals) { - m_signals->MempoolTransactionsRemovedForBlock(txs_removed_for_block, nBlockHeight); + if (m_opts.signals) { + m_opts.signals->MempoolTransactionsRemovedForBlock(txs_removed_for_block, nBlockHeight); } lastRollingFeeUpdate = GetTime(); blockSinceLastRollingFeeBump = true; @@ -654,9 +642,9 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const { - if (m_check_ratio == 0) return; + if (m_opts.check_ratio == 0) return; - if (GetRand(m_check_ratio) >= 1) return; + if (GetRand(m_opts.check_ratio) >= 1) return; AssertLockHeld(::cs_main); LOCK(cs); @@ -1108,12 +1096,12 @@ CFeeRate CTxMemPool::GetMinFee(size_t sizelimit) const { rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife); lastRollingFeeUpdate = time; - if (rollingMinimumFeeRate < (double)m_incremental_relay_feerate.GetFeePerK() / 2) { + if (rollingMinimumFeeRate < (double)m_opts.incremental_relay_feerate.GetFeePerK() / 2) { rollingMinimumFeeRate = 0; return CFeeRate(0); } } - return std::max(CFeeRate(llround(rollingMinimumFeeRate)), m_incremental_relay_feerate); + return std::max(CFeeRate(llround(rollingMinimumFeeRate)), m_opts.incremental_relay_feerate); } void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) { @@ -1137,7 +1125,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpends // to have 0 fee). This way, we don't allow txn to enter mempool with feerate // equal to txn which were removed with no block in between. CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants()); - removed += m_incremental_relay_feerate; + removed += m_opts.incremental_relay_feerate; trackPackageRemoved(removed); maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed); diff --git a/src/txmempool.h b/src/txmempool.h index 52a3dc2d7d..c9f6cdbfac 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -301,7 +301,6 @@ struct TxMempoolInfo class CTxMemPool { protected: - const int m_check_ratio; //!< Value n means that 1 times in n we check. std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation uint64_t totalTxSize GUARDED_BY(cs){0}; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141. @@ -436,20 +435,7 @@ public: using Options = kernel::MemPoolOptions; - const int64_t m_max_size_bytes; - const std::chrono::seconds m_expiry; - const CFeeRate m_incremental_relay_feerate; - const CFeeRate m_min_relay_feerate; - const CFeeRate m_dust_relay_feerate; - const bool m_permit_bare_multisig; - const std::optional<unsigned> m_max_datacarrier_bytes; - const bool m_require_standard; - const bool m_full_rbf; - const bool m_persist_v1_dat; - - const Limits m_limits; - - ValidationSignals* const m_signals; + const Options m_opts; /** Create a new CTxMemPool. * Sanity checks will be off by default for performance, because otherwise @@ -625,7 +611,7 @@ public: * would otherwise be half of this, it is set to 0 instead. */ CFeeRate GetMinFee() const { - return GetMinFee(m_max_size_bytes); + return GetMinFee(m_opts.max_size_bytes); } /** Remove transactions from the mempool until its dynamic size is <= sizelimit. diff --git a/src/txorphanage.cpp b/src/txorphanage.cpp index e907fffd4f..13ef96f3be 100644 --- a/src/txorphanage.cpp +++ b/src/txorphanage.cpp @@ -23,7 +23,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) const Txid& hash = tx->GetHash(); const Wtxid& wtxid = tx->GetWitnessHash(); - if (m_orphans.count(hash)) + if (m_orphans.count(wtxid)) return false; // Ignore big transactions, to avoid a @@ -40,30 +40,28 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) return false; } - auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()}); + auto ret = m_orphans.emplace(wtxid, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()}); assert(ret.second); m_orphan_list.push_back(ret.first); - // Allow for lookups in the orphan pool by wtxid, as well as txid - m_wtxid_to_orphan_it.emplace(tx->GetWitnessHash(), ret.first); for (const CTxIn& txin : tx->vin) { m_outpoint_to_orphan_it[txin.prevout].insert(ret.first); } - LogPrint(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s) (mapsz %u outsz %u)\n", hash.ToString(), wtxid.ToString(), + LogPrint(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n", hash.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_it.size()); return true; } -int TxOrphanage::EraseTx(const Txid& txid) +int TxOrphanage::EraseTx(const Wtxid& wtxid) { LOCK(m_mutex); - return EraseTxNoLock(txid); + return EraseTxNoLock(wtxid); } -int TxOrphanage::EraseTxNoLock(const Txid& txid) +int TxOrphanage::EraseTxNoLock(const Wtxid& wtxid) { AssertLockHeld(m_mutex); - std::map<Txid, OrphanTx>::iterator it = m_orphans.find(txid); + std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid); if (it == m_orphans.end()) return 0; for (const CTxIn& txin : it->second.tx->vin) @@ -85,10 +83,12 @@ int TxOrphanage::EraseTxNoLock(const Txid& txid) m_orphan_list[old_pos] = it_last; it_last->second.list_pos = old_pos; } - const auto& wtxid = it->second.tx->GetWitnessHash(); - LogPrint(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s)\n", txid.ToString(), wtxid.ToString()); + const auto& txid = it->second.tx->GetHash(); + // Time spent in orphanage = difference between current and entry time. + // Entry time is equal to ORPHAN_TX_EXPIRE_TIME earlier than entry's expiry. + LogPrint(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s) after %ds\n", txid.ToString(), wtxid.ToString(), + GetTime() + ORPHAN_TX_EXPIRE_TIME - it->second.nTimeExpire); m_orphan_list.pop_back(); - m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash()); m_orphans.erase(it); return 1; @@ -101,16 +101,16 @@ void TxOrphanage::EraseForPeer(NodeId peer) m_peer_work_set.erase(peer); int nErased = 0; - std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin(); + std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin(); while (iter != m_orphans.end()) { - std::map<Txid, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid - if (maybeErase->second.fromPeer == peer) - { - nErased += EraseTxNoLock(maybeErase->second.tx->GetHash()); + // increment to avoid iterator becoming invalid after erasure + const auto& [wtxid, orphan] = *iter++; + if (orphan.fromPeer == peer) { + nErased += EraseTxNoLock(wtxid); } } - if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx from peer=%d\n", nErased, peer); + if (nErased > 0) LogPrint(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", nErased, peer); } void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng) @@ -124,12 +124,12 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng) // Sweep out expired orphan pool entries: int nErased = 0; int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; - std::map<Txid, OrphanTx>::iterator iter = m_orphans.begin(); + std::map<Wtxid, OrphanTx>::iterator iter = m_orphans.begin(); while (iter != m_orphans.end()) { - std::map<Txid, OrphanTx>::iterator maybeErase = iter++; + std::map<Wtxid, OrphanTx>::iterator maybeErase = iter++; if (maybeErase->second.nTimeExpire <= nNow) { - nErased += EraseTxNoLock(maybeErase->second.tx->GetHash()); + nErased += EraseTxNoLock(maybeErase->second.tx->GetWitnessHash()); } else { nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); } @@ -142,7 +142,7 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng) { // Evict a random orphan: size_t randompos = rng.randrange(m_orphan_list.size()); - EraseTxNoLock(m_orphan_list[randompos]->first); + EraseTxNoLock(m_orphan_list[randompos]->second.tx->GetWitnessHash()); ++nEvicted; } if (nEvicted > 0) LogPrint(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted); @@ -159,7 +159,7 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx) for (const auto& elem : it_by_prev->second) { // Get this source peer's work set, emplacing an empty set if it didn't exist // (note: if this peer wasn't still connected, we would have removed the orphan tx already) - std::set<Txid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second; + std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(elem->second.fromPeer).first->second; // Add this tx to the work set orphan_work_set.insert(elem->first); LogPrint(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n", @@ -169,14 +169,10 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx) } } -bool TxOrphanage::HaveTx(const GenTxid& gtxid) const +bool TxOrphanage::HaveTx(const Wtxid& wtxid) const { LOCK(m_mutex); - if (gtxid.IsWtxid()) { - return m_wtxid_to_orphan_it.count(Wtxid::FromUint256(gtxid.GetHash())); - } else { - return m_orphans.count(Txid::FromUint256(gtxid.GetHash())); - } + return m_orphans.count(wtxid); } CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer) @@ -187,10 +183,10 @@ CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer) if (work_set_it != m_peer_work_set.end()) { auto& work_set = work_set_it->second; while (!work_set.empty()) { - Txid txid = *work_set.begin(); + Wtxid wtxid = *work_set.begin(); work_set.erase(work_set.begin()); - const auto orphan_it = m_orphans.find(txid); + const auto orphan_it = m_orphans.find(wtxid); if (orphan_it != m_orphans.end()) { return orphan_it->second.tx; } @@ -215,7 +211,7 @@ void TxOrphanage::EraseForBlock(const CBlock& block) { LOCK(m_mutex); - std::vector<Txid> vOrphanErase; + std::vector<Wtxid> vOrphanErase; for (const CTransactionRef& ptx : block.vtx) { const CTransaction& tx = *ptx; @@ -226,8 +222,7 @@ void TxOrphanage::EraseForBlock(const CBlock& block) if (itByPrev == m_outpoint_to_orphan_it.end()) continue; for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { const CTransaction& orphanTx = *(*mi)->second.tx; - const auto& orphanHash = orphanTx.GetHash(); - vOrphanErase.push_back(orphanHash); + vOrphanErase.push_back(orphanTx.GetWitnessHash()); } } } @@ -238,6 +233,80 @@ void TxOrphanage::EraseForBlock(const CBlock& block) for (const auto& orphanHash : vOrphanErase) { nErased += EraseTxNoLock(orphanHash); } - LogPrint(BCLog::TXPACKAGES, "Erased %d orphan tx included or conflicted by block\n", nErased); + LogPrint(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", nErased); + } +} + +std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const +{ + LOCK(m_mutex); + + // First construct a vector of iterators to ensure we do not return duplicates of the same tx + // and so we can sort by nTimeExpire. + std::vector<OrphanMap::iterator> iters; + + // For each output, get all entries spending this prevout, filtering for ones from the specified peer. + for (unsigned int i = 0; i < parent->vout.size(); i++) { + const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(parent->GetHash(), i)); + if (it_by_prev != m_outpoint_to_orphan_it.end()) { + for (const auto& elem : it_by_prev->second) { + if (elem->second.fromPeer == nodeid) { + iters.emplace_back(elem); + } + } + } + } + + // Sort by address so that duplicates can be deleted. At the same time, sort so that more recent + // orphans (which expire later) come first. Break ties based on address, as nTimeExpire is + // quantified in seconds and it is possible for orphans to have the same expiry. + std::sort(iters.begin(), iters.end(), [](const auto& lhs, const auto& rhs) { + if (lhs->second.nTimeExpire == rhs->second.nTimeExpire) { + return &(*lhs) < &(*rhs); + } else { + return lhs->second.nTimeExpire > rhs->second.nTimeExpire; + } + }); + // Erase duplicates + iters.erase(std::unique(iters.begin(), iters.end()), iters.end()); + + // Convert to a vector of CTransactionRef + std::vector<CTransactionRef> children_found; + children_found.reserve(iters.size()); + for (const auto& child_iter : iters) { + children_found.emplace_back(child_iter->second.tx); + } + return children_found; +} + +std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDifferentPeer(const CTransactionRef& parent, NodeId nodeid) const +{ + LOCK(m_mutex); + + // First construct vector of iterators to ensure we do not return duplicates of the same tx. + std::vector<OrphanMap::iterator> iters; + + // For each output, get all entries spending this prevout, filtering for ones not from the specified peer. + for (unsigned int i = 0; i < parent->vout.size(); i++) { + const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(parent->GetHash(), i)); + if (it_by_prev != m_outpoint_to_orphan_it.end()) { + for (const auto& elem : it_by_prev->second) { + if (elem->second.fromPeer != nodeid) { + iters.emplace_back(elem); + } + } + } + } + + // Erase duplicates + std::sort(iters.begin(), iters.end(), IteratorComparator()); + iters.erase(std::unique(iters.begin(), iters.end()), iters.end()); + + // Convert iterators to pair<CTransactionRef, NodeId> + std::vector<std::pair<CTransactionRef, NodeId>> children_found; + children_found.reserve(iters.size()); + for (const auto& child_iter : iters) { + children_found.emplace_back(child_iter->second.tx, child_iter->second.fromPeer); } + return children_found; } diff --git a/src/txorphanage.h b/src/txorphanage.h index 2fd14e6fd2..5f9888c969 100644 --- a/src/txorphanage.h +++ b/src/txorphanage.h @@ -23,8 +23,8 @@ public: /** Add a new orphan transaction */ bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - /** Check if we already have an orphan transaction (by txid or wtxid) */ - bool HaveTx(const GenTxid& gtxid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Check if we already have an orphan transaction (by wtxid only) */ + bool HaveTx(const Wtxid& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Extract a transaction from a peer's work set * Returns nullptr if there are no transactions to work on. @@ -33,8 +33,8 @@ public: */ CTransactionRef GetTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - /** Erase an orphan by txid */ - int EraseTx(const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Erase an orphan by wtxid */ + int EraseTx(const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Erase all orphans announced by a peer (eg, after that peer disconnects) */ void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); @@ -51,6 +51,14 @@ public: /** Does this peer have any work to do? */ bool HaveTxToReconsider(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);; + /** Get all children that spend from this tx and were received from nodeid. Sorted from most + * recent to least recent. */ + std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + + /** Get all children that spend from this tx but were not received from nodeid. Also return + * which peer provided each tx. */ + std::vector<std::pair<CTransactionRef, NodeId>> GetChildrenFromDifferentPeer(const CTransactionRef& parent, NodeId nodeid) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** Return how many entries exist in the orphange */ size_t Size() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { @@ -69,12 +77,12 @@ protected: size_t list_pos; }; - /** Map from txid to orphan transaction record. Limited by + /** Map from wtxid to orphan transaction record. Limited by * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ - std::map<Txid, OrphanTx> m_orphans GUARDED_BY(m_mutex); + std::map<Wtxid, OrphanTx> m_orphans GUARDED_BY(m_mutex); /** Which peer provided the orphans that need to be reconsidered */ - std::map<NodeId, std::set<Txid>> m_peer_work_set GUARDED_BY(m_mutex); + std::map<NodeId, std::set<Wtxid>> m_peer_work_set GUARDED_BY(m_mutex); using OrphanMap = decltype(m_orphans); @@ -94,12 +102,8 @@ protected: /** Orphan transactions in vector for quick random eviction */ std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(m_mutex); - /** Index from wtxid into the m_orphans to lookup orphan - * transactions using their witness ids. */ - std::map<Wtxid, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(m_mutex); - - /** Erase an orphan by txid */ - int EraseTxNoLock(const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + /** Erase an orphan by wtxid */ + int EraseTxNoLock(const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); }; #endif // BITCOIN_TXORPHANAGE_H diff --git a/src/util/bitdeque.h b/src/util/bitdeque.h index 1e34b72475..ac9d302420 100644 --- a/src/util/bitdeque.h +++ b/src/util/bitdeque.h @@ -14,18 +14,17 @@ /** Class that mimics std::deque<bool>, but with std::vector<bool>'s bit packing. * - * BlobSize selects the (minimum) number of bits that are allocated at once. + * BITS_PER_WORD selects the (minimum) number of bits that are allocated at once. * Larger values reduce the asymptotic memory usage overhead, at the cost of * needing larger up-front allocations. The default is 4096 bytes. */ -template<int BlobSize = 4096 * 8> +template<int BITS_PER_WORD = 4096 * 8> class bitdeque { // Internal definitions - using word_type = std::bitset<BlobSize>; + using word_type = std::bitset<BITS_PER_WORD>; using deque_type = std::deque<word_type>; - static_assert(BlobSize > 0); - static constexpr int BITS_PER_WORD = BlobSize; + static_assert(BITS_PER_WORD > 0); // Forward and friend declarations of iterator types. template<bool Const> class Iterator; diff --git a/src/util/check.cpp b/src/util/check.cpp index c4d4b0cc28..eb3885832b 100644 --- a/src/util/check.cpp +++ b/src/util/check.cpp @@ -4,9 +4,7 @@ #include <util/check.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <clientversion.h> #include <tinyformat.h> diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp index bce5602462..8952f20f79 100644 --- a/src/util/fs_helpers.cpp +++ b/src/util/fs_helpers.cpp @@ -5,9 +5,7 @@ #include <util/fs_helpers.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <logging.h> #include <sync.h> diff --git a/src/util/result.h b/src/util/result.h index b99995c7e5..122a7638fa 100644 --- a/src/util/result.h +++ b/src/util/result.h @@ -39,6 +39,16 @@ private: std::variant<bilingual_str, T> m_variant; + //! Disallow copy constructor, require Result to be moved for efficiency. + Result(const Result&) = delete; + + //! Disallow operator= to avoid confusion in the future when the Result + //! class gains support for richer error reporting, and callers should have + //! ability to set a new result value without clearing existing error + //! messages. + Result& operator=(const Result&) = delete; + Result& operator=(Result&&) = delete; + template <typename FT> friend bilingual_str ErrorString(const Result<FT>& result); @@ -46,6 +56,8 @@ public: Result() : m_variant{std::in_place_index_t<1>{}, std::monostate{}} {} // constructor for void Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {} Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {} + Result(Result&&) = default; + ~Result() = default; //! std::optional methods, so functions returning optional<T> can change to //! return Result<T> with minimal changes to existing code, and vice versa. diff --git a/src/util/subprocess.h b/src/util/subprocess.h index 4acfa8ff83..e76ced687c 100644 --- a/src/util/subprocess.h +++ b/src/util/subprocess.h @@ -159,12 +159,6 @@ public: //-------------------------------------------------------------------- namespace util { - template <typename R> - inline bool is_ready(std::shared_future<R> const &f) - { - return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; - } - inline void quote_argument(const std::wstring &argument, std::wstring &command_line, bool force) { @@ -676,8 +670,8 @@ struct error * This is basically used to determine the length of the actual * data stored inside the dynamically resized vector. * - * This is what is returned as the output to communicate and check_output - * functions, so, users must know about this class. + * This is what is returned as the output to the communicate + * function, so, users must know about this class. * * OutBuffer and ErrBuffer are just different typedefs to this class. */ @@ -688,22 +682,6 @@ public: explicit Buffer(size_t cap) { buf.resize(cap); } void add_cap(size_t cap) { buf.resize(cap); } -#if 0 - Buffer(const Buffer& other): - buf(other.buf), - length(other.length) - { - std::cout << "COPY" << std::endl; - } - - Buffer(Buffer&& other): - buf(std::move(other.buf)), - length(other.length) - { - std::cout << "MOVE" << std::endl; - } -#endif - public: std::vector<char> buf; size_t length = 0; @@ -724,39 +702,9 @@ class Popen; */ namespace detail { - -// Metaprogram for searching a type within -// a variadic parameter pack -// This is particularly required to do a compile time -// checking of the arguments provided to 'check_output' function -// wherein the user is not expected to provide an 'output' option. - -template <typename... T> struct param_pack{}; - -template <typename F, typename T> struct has_type; - -template <typename F> -struct has_type<F, param_pack<>> { - static constexpr bool value = false; -}; - -template <typename F, typename... T> -struct has_type<F, param_pack<F, T...>> { - static constexpr bool value = true; -}; - -template <typename F, typename H, typename... T> -struct has_type<F, param_pack<H,T...>> { - static constexpr bool value = - std::is_same<F, typename std::decay<H>::type>::value ? true : has_type<F, param_pack<T...>>::value; -}; - -//---- - /*! * A helper class to Popen class for setting - * options as provided in the Popen constructor - * or in check_output arguments. + * options as provided in the Popen constructor. * This design allows us to _not_ have any fixed position * to any arguments and specify them in a way similar to what * can be done in python. @@ -948,24 +896,15 @@ private: * interface to the client. * * API's provided by the class: - * 1. Popen({"cmd"}, output{..}, error{..}, ....) + * Popen({"cmd"}, output{..}, error{..}, ....) * Command provided as a sequence. - * 2. Popen("cmd arg1"m output{..}, error{..}, ....) + * Popen("cmd arg1", output{..}, error{..}, ....) * Command provided in a single string. - * 3. wait() - Wait for the child to exit. - * 4. retcode() - The return code of the exited child. - * 5. pid() - PID of the spawned child. - * 6. poll() - Check the status of the running child. - * 7. kill(sig_num) - Kill the child. SIGTERM used by default. - * 8. send(...) - Send input to the input channel of the child. - * 9. communicate(...) - Get the output/error from the child and close the channels - * from the parent side. - *10. input() - Get the input channel/File pointer. Can be used for - * customizing the way of sending input to child. - *11. output() - Get the output channel/File pointer. Usually used - in case of redirection. See piping examples. - *12. error() - Get the error channel/File pointer. Usually used - in case of redirection. + * wait() - Wait for the child to exit. + * retcode() - The return code of the exited child. + * send(...) - Send input to the input channel of the child. + * communicate(...) - Get the output/error from the child and close the channels + * from the parent side. */ class Popen { @@ -1009,27 +948,10 @@ public: execute_process(); } -/* - ~Popen() - { -#ifdef __USING_WINDOWS__ - CloseHandle(this->process_handle_); -#endif - } -*/ - - int pid() const noexcept { return child_pid_; } - int retcode() const noexcept { return retcode_; } int wait() noexcept(false); - int poll() noexcept(false); - - // Does not fail, Caller is expected to recheck the - // status with a call to poll() - void kill(int sig_num = 9); - void set_out_buf_cap(size_t cap) { stream_.set_out_buf_cap(cap); } void set_err_buf_cap(size_t cap) { stream_.set_err_buf_cap(cap); } @@ -1067,15 +989,6 @@ public: return communicate(nullptr, 0); } - FILE* input() { return stream_.input(); } - FILE* output() { return stream_.output();} - FILE* error() { return stream_.error(); } - - /// Stream close APIs - void close_input() { stream_.input_.reset(); } - void close_output() { stream_.output_.reset(); } - void close_error() { stream_.error_.reset(); } - private: template <typename F, typename... Args> void init_args(F&& farg, Args&&... args); @@ -1099,7 +1012,6 @@ private: std::vector<std::string> vargs_; std::vector<char*> cargv_; - bool child_created_ = false; // Pid of the child process int child_pid_ = -1; @@ -1134,7 +1046,7 @@ inline int Popen::wait() noexcept(false) return 0; #else int ret, status; - std::tie(ret, status) = util::wait_for_child_exit(pid()); + std::tie(ret, status) = util::wait_for_child_exit(child_pid_); if (ret == -1) { if (errno != ECHILD) throw OSError("waitpid failed", errno); return 0; @@ -1147,68 +1059,6 @@ inline int Popen::wait() noexcept(false) #endif } -inline int Popen::poll() noexcept(false) -{ -#ifdef __USING_WINDOWS__ - int ret = WaitForSingleObject(process_handle_, 0); - if (ret != WAIT_OBJECT_0) return -1; - - DWORD dretcode_; - if (FALSE == GetExitCodeProcess(process_handle_, &dretcode_)) - throw OSError("GetExitCodeProcess", 0); - - retcode_ = (int)dretcode_; - CloseHandle(process_handle_); - - return retcode_; -#else - if (!child_created_) return -1; // TODO: ?? - - int status; - - // Returns zero if child is still running - int ret = waitpid(child_pid_, &status, WNOHANG); - if (ret == 0) return -1; - - if (ret == child_pid_) { - if (WIFSIGNALED(status)) { - retcode_ = WTERMSIG(status); - } else if (WIFEXITED(status)) { - retcode_ = WEXITSTATUS(status); - } else { - retcode_ = 255; - } - return retcode_; - } - - if (ret == -1) { - // From subprocess.py - // This happens if SIGCHLD is set to be ignored - // or waiting for child process has otherwise been disabled - // for our process. This child is dead, we cannot get the - // status. - if (errno == ECHILD) retcode_ = 0; - else throw OSError("waitpid failed", errno); - } else { - retcode_ = ret; - } - - return retcode_; -#endif -} - -inline void Popen::kill(int sig_num) -{ -#ifdef __USING_WINDOWS__ - if (!TerminateProcess(this->process_handle_, (UINT)sig_num)) { - throw OSError("TerminateProcess", 0); - } -#else - ::kill(child_pid_, sig_num); -#endif -} - - inline void Popen::execute_process() noexcept(false) { #ifdef __USING_WINDOWS__ @@ -1311,8 +1161,6 @@ inline void Popen::execute_process() noexcept(false) throw OSError("fork failed", errno); } - child_created_ = true; - if (child_pid_ == 0) { // Close descriptors belonging to parent diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp index bac498a23d..6f3a724483 100644 --- a/src/util/syserror.cpp +++ b/src/util/syserror.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <tinyformat.h> #include <util/syserror.h> diff --git a/src/util/threadnames.cpp b/src/util/threadnames.cpp index 91883fe4ff..ea597dd05a 100644 --- a/src/util/threadnames.cpp +++ b/src/util/threadnames.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <string> #include <thread> diff --git a/src/util/time.cpp b/src/util/time.cpp index 456662bd84..f08eb5300a 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -16,11 +16,11 @@ void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread::sleep_for(n); } -static std::atomic<int64_t> nMockTime(0); //!< For testing +static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing NodeClock::time_point NodeClock::now() noexcept { - const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)}; + const auto mocktime{g_mock_time.load(std::memory_order_relaxed)}; const auto ret{ mocktime.count() ? mocktime : @@ -29,20 +29,16 @@ NodeClock::time_point NodeClock::now() noexcept return time_point{ret}; }; -void SetMockTime(int64_t nMockTimeIn) -{ - Assert(nMockTimeIn >= 0); - nMockTime.store(nMockTimeIn, std::memory_order_relaxed); -} - +void SetMockTime(int64_t nMockTimeIn) { SetMockTime(std::chrono::seconds{nMockTimeIn}); } void SetMockTime(std::chrono::seconds mock_time_in) { - nMockTime.store(mock_time_in.count(), std::memory_order_relaxed); + Assert(mock_time_in >= 0s); + g_mock_time.store(mock_time_in, std::memory_order_relaxed); } std::chrono::seconds GetMockTime() { - return std::chrono::seconds(nMockTime.load(std::memory_order_relaxed)); + return g_mock_time.load(std::memory_order_relaxed); } int64_t GetTime() { return GetTime<std::chrono::seconds>().count(); } diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp index 3c27d5e523..16fbb664ea 100644 --- a/src/util/tokenpipe.cpp +++ b/src/util/tokenpipe.cpp @@ -3,9 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/tokenpipe.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #ifndef WIN32 diff --git a/src/util/trace.h b/src/util/trace.h index b7c275f19b..d9ed65e3aa 100644 --- a/src/util/trace.h +++ b/src/util/trace.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_UTIL_TRACE_H #define BITCOIN_UTIL_TRACE_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #ifdef ENABLE_TRACING diff --git a/src/validation.cpp b/src/validation.cpp index 2bdffdb1b0..bd9c8fe194 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <validation.h> @@ -268,13 +266,13 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache) { AssertLockHeld(::cs_main); AssertLockHeld(pool.cs); - int expired = pool.Expire(GetTime<std::chrono::seconds>() - pool.m_expiry); + int expired = pool.Expire(GetTime<std::chrono::seconds>() - pool.m_opts.expiry); if (expired != 0) { LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); } std::vector<COutPoint> vNoSpendsRemaining; - pool.TrimToSize(pool.m_max_size_bytes, &vNoSpendsRemaining); + pool.TrimToSize(pool.m_opts.max_size_bytes, &vNoSpendsRemaining); for (const COutPoint& removed : vNoSpendsRemaining) coins_cache.Uncache(removed); } @@ -695,9 +693,9 @@ private: return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); } - if (package_fee < m_pool.m_min_relay_feerate.GetFee(package_size)) { + if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size)) { return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "min relay fee not met", - strprintf("%d < %d", package_fee, m_pool.m_min_relay_feerate.GetFee(package_size))); + strprintf("%d < %d", package_fee, m_pool.m_opts.min_relay_feerate.GetFee(package_size))); } return true; } @@ -742,7 +740,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; - if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason)) { + if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, m_pool.m_opts.permit_bare_multisig, m_pool.m_opts.dust_relay_feerate, reason)) { return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason); } @@ -789,7 +787,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // // Replaceability signaling of the original transactions may be // ignored due to node setting. - const bool allow_rbf{m_pool.m_full_rbf || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->nVersion == 3}; + const bool allow_rbf{m_pool.m_opts.full_rbf || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->nVersion == 3}; if (!allow_rbf) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } @@ -850,12 +848,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return false; // state filled in by CheckTxInputs } - if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view)) { + if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); } // Check for non-standard witnesses. - if (tx.HasWitness() && m_pool.m_require_standard && !IsWitnessStandard(tx, m_view)) { + if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); } @@ -893,11 +891,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear // due to a replacement. // The only exception is v3 transactions. - if (!bypass_limits && ws.m_ptx->nVersion != 3 && ws.m_modified_fees < m_pool.m_min_relay_feerate.GetFee(ws.m_vsize)) { + if (!bypass_limits && ws.m_ptx->nVersion != 3 && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)) { // Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not // TX_RECONSIDERABLE, because it cannot be bypassed using package validation. return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", - strprintf("%d < %d", ws.m_modified_fees, m_pool.m_min_relay_feerate.GetFee(ws.m_vsize))); + strprintf("%d < %d", ws.m_modified_fees, m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize))); } // No individual transactions are allowed below the mempool min feerate except from disconnected // blocks and transactions in a package. Package transactions will be checked using package @@ -908,7 +906,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Note that these modifications are only applicable to single transaction scenarios; // carve-outs and package RBF are disabled for multi-transaction evaluations. - CTxMemPool::Limits maybe_rbf_limits = m_pool.m_limits; + CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts.limits; // Calculate in-mempool ancestors, up to a limit. if (ws.m_conflicts.size() == 1) { @@ -946,8 +944,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants(); } - auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}; - if (!ancestors) { + if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) { + ws.m_ancestors = std::move(*ancestors); + } else { // If CalculateMemPoolAncestors fails second time, we want the original error string. // Contracting/payment channels CPFP carve-out: // If the new transaction is relatively small (up to 40k weight) @@ -970,11 +969,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); } - ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits); - if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); + if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) { + ws.m_ancestors = std::move(*ancestors_retry); + } else { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); + } } - ws.m_ancestors = *ancestors; // Even though just checking direct mempool parents for inheritance would be sufficient, we // check using the full ancestor set here because it's more convenient to use what we have // already calculated. @@ -1058,7 +1059,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws) ws.m_conflicting_size += it->GetTxSize(); } if (const auto err_string{PaysForRBF(ws.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize, - m_pool.m_incremental_relay_feerate, hash)}) { + m_pool.m_opts.incremental_relay_feerate, hash)}) { // Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not // TX_RECONSIDERABLE, because it cannot be bypassed using package validation. // This must be changed if package RBF is enabled. @@ -1227,7 +1228,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& // Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the // last calculation done in PreChecks, since package ancestors have already been submitted. { - auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_limits)}; + auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_opts.limits)}; if(!ancestors) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail. @@ -1268,14 +1269,14 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_vsize, ws.m_base_fees, effective_feerate, effective_feerate_wtxids)); - if (!m_pool.m_signals) continue; + if (!m_pool.m_opts.signals) continue; const CTransaction& tx = *ws.m_ptx; const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, ws.m_vsize, ws.m_entry->GetHeight(), args.m_bypass_limits, args.m_package_submission, IsCurrentForFeeEstimation(m_active_chainstate), m_pool.HasNoInputsOf(tx)); - m_pool.m_signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); + m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); } return all_submitted; } @@ -1283,7 +1284,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) { AssertLockHeld(cs_main); - LOCK(m_pool.cs); // mempool "read lock" (held through m_pool.m_signals->TransactionAddedToMempool()) + LOCK(m_pool.cs); // mempool "read lock" (held through m_pool.m_opts.signals->TransactionAddedToMempool()) Workspace ws(ptx); const std::vector<Wtxid> single_wtxid{ws.m_ptx->GetWitnessHash()}; @@ -1324,14 +1325,14 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()}); } - if (m_pool.m_signals) { + if (m_pool.m_opts.signals) { const CTransaction& tx = *ws.m_ptx; const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, ws.m_vsize, ws.m_entry->GetHeight(), args.m_bypass_limits, args.m_package_submission, IsCurrentForFeeEstimation(m_active_chainstate), m_pool.HasNoInputsOf(tx)); - m_pool.m_signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); + m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); } return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_vsize, ws.m_base_fees, @@ -2594,7 +2595,7 @@ CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState() AssertLockHeld(::cs_main); return this->GetCoinsCacheSizeState( m_coinstip_cache_size_bytes, - m_mempool ? m_mempool->m_max_size_bytes : 0); + m_mempool ? m_mempool->m_opts.max_size_bytes : 0); } CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState( @@ -2747,8 +2748,10 @@ bool Chainstate::FlushStateToDisk( return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!")); } // Flush the chainstate (which may refer to block index entries). - if (!CoinsTip().Flush()) + const auto empty_cache{(mode == FlushStateMode::ALWAYS) || fCacheLarge || fCacheCritical || fFlushForPrune}; + if (empty_cache ? !CoinsTip().Flush() : !CoinsTip().Sync()) { return FatalError(m_chainman.GetNotifications(), state, _("Failed to write to coin database.")); + } m_last_flush = nNow; full_flush_completed = true; TRACE5(utxocache, flush, diff --git a/src/validation.h b/src/validation.h index e3b2a2d59b..28b045fe80 100644 --- a/src/validation.h +++ b/src/validation.h @@ -113,7 +113,6 @@ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight *| txid in mempool? | yes | no | no* | yes | yes | *| wtxid in mempool? | yes | no | no* | yes | no | *| m_state | yes, IsValid() | yes, IsInvalid() | yes, IsInvalid() | yes, IsValid() | yes, IsValid() | -*| m_replaced_transactions | yes | no | no | no | no | *| m_vsize | yes | no | no | yes | no | *| m_base_fees | yes | no | no | yes | no | *| m_effective_feerate | yes | yes | no | no | no | @@ -139,7 +138,7 @@ struct MempoolAcceptResult { const TxValidationState m_state; /** Mempool transactions replaced by the tx. */ - const std::optional<std::list<CTransactionRef>> m_replaced_transactions; + const std::list<CTransactionRef> m_replaced_transactions; /** Virtual size as used by the mempool, calculated using serialized size and sigops. */ const std::optional<int64_t> m_vsize; /** Raw base fees in satoshis. */ diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index f151fad740..14e988ec1a 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <common/args.h> #include <init.h> diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index bed9ec029a..65587f0b18 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <core_io.h> #include <key_io.h> diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index ae2dfe5795..8d3eea59ee 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <chain.h> #include <clientversion.h> @@ -458,12 +456,7 @@ RPCHelpMan importpubkey() throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - if (!IsHex(request.params[0].get_str())) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); - std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); - CPubKey pubKey(data); - if (!pubKey.IsFullyValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); + CPubKey pubKey = HexToPubKey(request.params[0].get_str()); { LOCK(pwallet->cs_wallet); @@ -985,15 +978,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); } for (size_t i = 0; i < pubKeys.size(); ++i) { - const auto& str = pubKeys[i].get_str(); - if (!IsHex(str)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); - } - auto parsed_pubkey = ParseHex(str); - CPubKey pubkey(parsed_pubkey); - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); - } + CPubKey pubkey = HexToPubKey(pubKeys[i].get_str()); pubkey_map.emplace(pubkey.GetID(), pubkey); ordered_pubkeys.push_back(pubkey.GetID()); } diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 0cb0891141..b6c7396f4b 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -194,15 +194,12 @@ RPCHelpMan getbalance() LOCK(pwallet->cs_wallet); - const auto dummy_value{self.MaybeArg<std::string>(0)}; + const auto dummy_value{self.MaybeArg<std::string>("dummy")}; if (dummy_value && *dummy_value != "*") { throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); } - int min_depth = 0; - if (!request.params[1].isNull()) { - min_depth = request.params[1].getInt<int>(); - } + const auto min_depth{self.Arg<int>("minconf")}; bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 6060f017ce..1a364a75ed 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -627,15 +627,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact const UniValue solving_data = options["solving_data"].get_obj(); if (solving_data.exists("pubkeys")) { for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) { - const std::string& pk_str = pk_univ.get_str(); - if (!IsHex(pk_str)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str)); - } - const std::vector<unsigned char> data(ParseHex(pk_str)); - const CPubKey pubkey(data.begin(), data.end()); - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str)); - } + const CPubKey pubkey = HexToPubKey(pk_univ.get_str()); coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey); // Add witness script for pubkeys const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey)); diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index a684d4e191..f1cb595271 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <core_io.h> #include <key_io.h> diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 5d23ebd35a..9a7e166e68 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -683,11 +683,11 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co // Vector of results. We will choose the best one based on waste. std::vector<SelectionResult> results; std::vector<util::Result<SelectionResult>> errors; - auto append_error = [&] (const util::Result<SelectionResult>& result) { + auto append_error = [&] (util::Result<SelectionResult>&& result) { // If any specific error message appears here, then something different from a simple "no selection found" happened. // Let's save it, so it can be retrieved to the user if no other selection algorithm succeeded. if (HasErrorMsg(result)) { - errors.emplace_back(result); + errors.emplace_back(std::move(result)); } }; @@ -698,7 +698,7 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co if (!coin_selection_params.m_subtract_fee_outputs) { if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_inputs_weight)}) { results.push_back(*bnb_result); - } else append_error(bnb_result); + } else append_error(std::move(bnb_result)); } // As Knapsack and SRD can create change, also deduce change weight. @@ -707,25 +707,25 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) { results.push_back(*knapsack_result); - } else append_error(knapsack_result); + } else append_error(std::move(knapsack_result)); if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+) if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) { cg_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); results.push_back(*cg_result); } else { - append_error(cg_result); + append_error(std::move(cg_result)); } } if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) { results.push_back(*srd_result); - } else append_error(srd_result); + } else append_error(std::move(srd_result)); if (results.empty()) { // No solution found, retrieve the first explicit error (if any). // future: add 'severity level' to errors so the worst one can be retrieved instead of the first one. - return errors.empty() ? util::Error() : errors.front(); + return errors.empty() ? util::Error() : std::move(errors.front()); } // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry @@ -818,7 +818,7 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the // transaction at a target feerate. If an attempt fails, more attempts may be made using a more // permissive CoinEligibilityFilter. - util::Result<SelectionResult> res = [&] { + { // Place coins eligibility filters on a scope increasing order. std::vector<SelectionFilter> ordered_filters{ // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six @@ -866,9 +866,9 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin if (CAmount total_amount = available_coins.GetTotalAmount() - total_discarded < value_to_select) { // Special case, too-long-mempool cluster. if (total_amount + total_unconf_long_chain > value_to_select) { - return util::Result<SelectionResult>({_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")}); + return util::Error{_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")}; } - return util::Result<SelectionResult>(util::Error()); // General "Insufficient Funds" + return util::Error{}; // General "Insufficient Funds" } // Walk-through the filters until the solution gets found. @@ -885,19 +885,17 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin // If any specific error message appears here, then something particularly wrong might have happened. // Save the error and continue the selection process. So if no solutions gets found, we can return // the detailed error to the upper layers. - if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res); + if (HasErrorMsg(res)) res_detailed_errors.emplace_back(std::move(res)); } } // Return right away if we have a detailed error - if (!res_detailed_errors.empty()) return res_detailed_errors.front(); + if (!res_detailed_errors.empty()) return std::move(res_detailed_errors.front()); // General "Insufficient Funds" - return util::Result<SelectionResult>(util::Error()); - }(); - - return res; + return util::Error{}; + } } static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash) diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index 34f18bf0b1..5e3a8179a2 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <wallet/sqlite.h> diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index f783424df8..438dfceb7f 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <boost/test/unit_test.hpp> diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp index 297432de9e..331590df7f 100644 --- a/src/wallet/test/fuzz/coinselection.cpp +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -291,7 +291,10 @@ FUZZ_TARGET(coinselection) } std::vector<COutput> utxos; - std::vector<util::Result<SelectionResult>> results{result_srd, result_knapsack, result_bnb}; + std::vector<util::Result<SelectionResult>> results; + results.emplace_back(std::move(result_srd)); + results.emplace_back(std::move(result_knapsack)); + results.emplace_back(std::move(result_bnb)); CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)}; if (new_total_balance > 0) { std::set<std::shared_ptr<COutput>> new_utxo_pool; diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index 9a515828fe..792079e6c6 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -106,13 +106,11 @@ struct FuzzedWallet { CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) { auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; - util::Result<CTxDestination> op_dest{util::Error{}}; if (fuzzed_data_provider.ConsumeBool()) { - op_dest = wallet->GetNewDestination(type, ""); + return *Assert(wallet->GetNewDestination(type, "")); } else { - op_dest = wallet->GetNewChangeDestination(type); + return *Assert(wallet->GetNewChangeDestination(type)); } - return *Assert(op_dest); } CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index 3509bc116f..963c0f838b 100644 --- a/src/wallet/test/spend_tests.cpp +++ b/src/wallet/test/spend_tests.cpp @@ -97,13 +97,11 @@ BOOST_FIXTURE_TEST_CASE(wallet_duplicated_preset_inputs_test, TestChain100Setup) // so that the recipient's amount is no longer equal to the user's selected target of 299 BTC. // First case, use 'subtract_fee_from_outputs=true' - util::Result<CreatedTransactionResult> res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control); - BOOST_CHECK(!res_tx.has_value()); + BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control)); // Second case, don't use 'subtract_fee_from_outputs'. recipients[0].fSubtractFeeFromAmount = false; - res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control); - BOOST_CHECK(!res_tx.has_value()); + BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index 9f2974ece6..a3e6ede81e 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -5,9 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_UTIL_H #define BITCOIN_WALLET_TEST_UTIL_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <addresstype.h> #include <wallet/db.h> diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8f4171eb15..45f69f52d1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,9 +5,7 @@ #include <wallet/wallet.h> -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <addresstype.h> #include <blockfilter.h> #include <chain.h> diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index b1ce7ee4e7..3ba43cdb73 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <wallet/walletdb.h> diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index cda344ab19..7a1930fd31 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <wallet/wallettool.h> diff --git a/src/warnings.cpp b/src/warnings.cpp index 84b021dad5..38c0554cf2 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -3,22 +3,21 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif +#include <config/bitcoin-config.h> // IWYU pragma: keep #include <warnings.h> #include <common/system.h> #include <sync.h> -#include <util/string.h> #include <util/translation.h> +#include <optional> #include <vector> static GlobalMutex g_warnings_mutex; static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex); static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false; +static std::optional<bilingual_str> g_timeoffset_warning GUARDED_BY(g_warnings_mutex){}; void SetMiscWarning(const bilingual_str& warning) { @@ -32,33 +31,35 @@ void SetfLargeWorkInvalidChainFound(bool flag) fLargeWorkInvalidChainFound = flag; } -bilingual_str GetWarnings(bool verbose) +void SetMedianTimeOffsetWarning(std::optional<bilingual_str> warning) { - bilingual_str warnings_concise; - std::vector<bilingual_str> warnings_verbose; + LOCK(g_warnings_mutex); + g_timeoffset_warning = warning; +} + +std::vector<bilingual_str> GetWarnings() +{ + std::vector<bilingual_str> warnings; LOCK(g_warnings_mutex); // Pre-release build warning if (!CLIENT_VERSION_IS_RELEASE) { - warnings_concise = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"); - warnings_verbose.emplace_back(warnings_concise); + warnings.emplace_back(_("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications")); } // Misc warnings like out of disk space and clock is wrong if (!g_misc_warnings.empty()) { - warnings_concise = g_misc_warnings; - warnings_verbose.emplace_back(warnings_concise); + warnings.emplace_back(g_misc_warnings); } if (fLargeWorkInvalidChainFound) { - warnings_concise = _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."); - warnings_verbose.emplace_back(warnings_concise); + warnings.emplace_back(_("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.")); } - if (verbose) { - return Join(warnings_verbose, Untranslated("<hr />")); + if (g_timeoffset_warning) { + warnings.emplace_back(g_timeoffset_warning.value()); } - return warnings_concise; + return warnings; } diff --git a/src/warnings.h b/src/warnings.h index b21e2ea2b8..79dc2ffabf 100644 --- a/src/warnings.h +++ b/src/warnings.h @@ -6,18 +6,17 @@ #ifndef BITCOIN_WARNINGS_H #define BITCOIN_WARNINGS_H +#include <optional> #include <string> +#include <vector> struct bilingual_str; void SetMiscWarning(const bilingual_str& warning); void SetfLargeWorkInvalidChainFound(bool flag); -/** Format a string that describes several potential problems detected by the core. - * @param[in] verbose bool - * - if true, get all warnings separated by <hr /> - * - if false, get the most important warning - * @returns the warning string - */ -bilingual_str GetWarnings(bool verbose); +/** Pass std::nullopt to disable the warning */ +void SetMedianTimeOffsetWarning(std::optional<bilingual_str> warning); +/** Return potential problems detected by the node. */ +std::vector<bilingual_str> GetWarnings(); #endif // BITCOIN_WARNINGS_H |