diff options
Diffstat (limited to 'src')
136 files changed, 3449 insertions, 1757 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6a7a49f012..e2b930a2fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -148,6 +148,7 @@ BITCOIN_CORE_H = \ fs.h \ httprpc.h \ httpserver.h \ + i2p.h \ index/base.h \ index/blockfilterindex.h \ index/disktxpos.h \ @@ -224,13 +225,15 @@ BITCOIN_CORE_H = \ timedata.h \ torcontrol.h \ txdb.h \ - txrequest.h \ txmempool.h \ + txorphanage.h \ + txrequest.h \ undo.h \ util/asmap.h \ util/bip32.h \ util/bytevectorhash.h \ util/check.h \ + util/epochguard.h \ util/error.h \ util/fees.h \ util/getuniquepath.h \ @@ -241,6 +244,7 @@ BITCOIN_CORE_H = \ util/message.h \ util/moneystr.h \ util/rbf.h \ + util/readwritefile.h \ util/ref.h \ util/settings.h \ util/sock.h \ @@ -300,7 +304,7 @@ libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # Contains code accessing mempool and chain state that is meant to be separated # from wallet and gui code (see node/README.md). Shared code should go in # libbitcoin_common or libbitcoin_util libraries, instead. -libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ @@ -314,6 +318,7 @@ libbitcoin_server_a_SOURCES = \ flatfile.cpp \ httprpc.cpp \ httpserver.cpp \ + i2p.cpp \ index/base.cpp \ index/blockfilterindex.cpp \ index/txindex.cpp \ @@ -347,8 +352,9 @@ libbitcoin_server_a_SOURCES = \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ - txrequest.cpp \ txmempool.cpp \ + txorphanage.cpp \ + txrequest.cpp \ validation.cpp \ validationinterface.cpp \ versionbits.cpp \ @@ -572,6 +578,7 @@ libbitcoin_util_a_SOURCES = \ util/message.cpp \ util/moneystr.cpp \ util/rbf.cpp \ + util/readwritefile.cpp \ util/settings.cpp \ util/threadnames.cpp \ util/spanparsing.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e817bb2ee2..133277baa6 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -285,12 +285,14 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp \ test/fuzz/signature_checker.cpp \ test/fuzz/signet.cpp \ + test/fuzz/socks5.cpp \ test/fuzz/span.cpp \ test/fuzz/spanparsing.cpp \ test/fuzz/string.cpp \ test/fuzz/strprintf.cpp \ test/fuzz/system.cpp \ test/fuzz/timedata.cpp \ + test/fuzz/torcontrol.cpp \ test/fuzz/transaction.cpp \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index 75fe68fcd1..2d772f2fca 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -12,7 +12,7 @@ TEST_FUZZ_H = \ test/fuzz/FuzzedDataProvider.h \ test/fuzz/util.h -libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 1abfb667a0..f7f393ccac 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -19,7 +19,7 @@ TEST_UTIL_H = \ test/util/validation.h \ test/util/wallet.h -libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_util_a_SOURCES = \ test/util/blockfilter.cpp \ diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 8f656c44d9..67ab02a5b3 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -16,13 +16,7 @@ static void AssembleBlock(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); const std::vector<unsigned char> op_true{OP_TRUE}; CScriptWitness witness; @@ -38,7 +32,7 @@ static void AssembleBlock(benchmark::Bench& bench) std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs; for (size_t b{0}; b < NUM_BLOCKS; ++b) { CMutableTransaction tx; - tx.vin.push_back(MineBlock(test_setup.m_node, SCRIPT_PUB)); + tx.vin.push_back(MineBlock(test_setup->m_node, SCRIPT_PUB)); tx.vin.back().scriptWitness = witness; tx.vout.emplace_back(1337, SCRIPT_PUB); if (NUM_BLOCKS - b >= COINBASE_MATURITY) @@ -48,13 +42,13 @@ static void AssembleBlock(benchmark::Bench& bench) LOCK(::cs_main); // Required for ::AcceptToMemoryPool. for (const auto& txr : txs) { - const MempoolAcceptResult res = ::AcceptToMemoryPool(::ChainstateActive(), *test_setup.m_node.mempool, txr, false /* bypass_limits */); + const MempoolAcceptResult res = ::AcceptToMemoryPool(::ChainstateActive(), *test_setup->m_node.mempool, txr, false /* bypass_limits */); assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID); } } bench.run([&] { - PrepareBlock(test_setup.m_node, SCRIPT_PUB); + PrepareBlock(test_setup->m_node, SCRIPT_PUB); }); } diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 5745e4276c..25d1a2b56c 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -14,13 +14,7 @@ static void DuplicateInputs(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); const CScript SCRIPT_PUB{CScript(OP_TRUE)}; diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index db9a5661fd..4f49fba7b7 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -25,13 +25,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& po // unique transactions for a more meaningful performance measurement. static void MempoolEviction(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); CMutableTransaction tx1 = CMutableTransaction(); tx1.vin.resize(1); diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index 9b862b735c..f28768efc8 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -79,7 +79,7 @@ static void ComplexMemPool(benchmark::Bench& bench) ordered_coins.emplace_back(MakeTransactionRef(tx)); available_coins.emplace_back(ordered_coins.back(), tx_counter++); } - TestingSetup test_setup; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN); CTxMemPool pool; LOCK2(cs_main, pool.cs); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 45ed9f60dc..c8886a4c23 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -12,25 +12,49 @@ #include <univalue.h> -static void BlockToJsonVerbose(benchmark::Bench& bench) -{ - TestingSetup test_setup{}; +namespace { + +struct TestBlockAndIndex { + const std::unique_ptr<const TestingSetup> testing_setup{MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN)}; + CBlock block{}; + uint256 blockHash{}; + CBlockIndex blockindex{}; - CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); - char a = '\0'; - stream.write(&a, 1); // Prevent compaction + TestBlockAndIndex() + { + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); + char a = '\0'; + stream.write(&a, 1); // Prevent compaction - CBlock block; - stream >> block; + stream >> block; - CBlockIndex blockindex; - const uint256 blockHash = block.GetHash(); - blockindex.phashBlock = &blockHash; - blockindex.nBits = 403014710; + blockHash = block.GetHash(); + blockindex.phashBlock = &blockHash; + blockindex.nBits = 403014710; + } +}; +} // namespace + +static void BlockToJsonVerbose(benchmark::Bench& bench) +{ + TestBlockAndIndex data; bench.run([&] { - (void)blockToJSON(block, &blockindex, &blockindex, /*verbose*/ true); + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true); + ankerl::nanobench::doNotOptimizeAway(univalue); }); } BENCHMARK(BlockToJsonVerbose); + +static void BlockToJsonVerboseWrite(benchmark::Bench& bench) +{ + TestBlockAndIndex data; + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true); + bench.run([&] { + auto str = univalue.write(); + ankerl::nanobench::doNotOptimizeAway(str); + }); +} + +BENCHMARK(BlockToJsonVerboseWrite); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index b385cec085..c96ef209e3 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -14,30 +14,24 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const bool add_watchonly, const bool add_mine) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE; - CWallet wallet{test_setup.m_node.chain.get(), "", CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockWalletDatabase()}; { wallet.SetupLegacyScriptPubKeyMan(); bool first_run; if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false); } - auto handler = test_setup.m_node.chain->handleNotifications({&wallet, [](CWallet*) {}}); + auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}}); const Optional<std::string> address_mine{add_mine ? Optional<std::string>{getnewaddress(wallet)} : nullopt}; if (add_watchonly) importaddress(wallet, ADDRESS_WATCHONLY); for (int i = 0; i < 100; ++i) { - generatetoaddress(test_setup.m_node, address_mine.value_or(ADDRESS_WATCHONLY)); - generatetoaddress(test_setup.m_node, ADDRESS_WATCHONLY); + generatetoaddress(test_setup->m_node, address_mine.value_or(ADDRESS_WATCHONLY)); + generatetoaddress(test_setup->m_node, ADDRESS_WATCHONLY); } SyncWithValidationInterfaceQueue(); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 0830cb54cb..dc4b142f83 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -300,7 +300,6 @@ class NetinfoRequestHandler : public BaseRequestHandler { private: static constexpr int8_t UNKNOWN_NETWORK{-1}; - static constexpr int8_t NET_I2P{3}; // pos of "i2p" in m_networks static constexpr uint8_t m_networks_size{4}; static constexpr uint8_t MAX_DETAIL_LEVEL{4}; const std::array<std::string, m_networks_size> m_networks{{"ipv4", "ipv6", "onion", "i2p"}}; @@ -314,13 +313,11 @@ private: } return UNKNOWN_NETWORK; } - uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level - bool m_is_help_requested{false}; //!< Optional user-supplied arg to print help documentation + uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; } bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; } bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; } bool m_is_asmap_on{false}; - bool m_is_i2p_on{false}; size_t m_max_addr_length{0}; size_t m_max_age_length{3}; size_t m_max_id_length{2}; @@ -367,68 +364,6 @@ private: if (conn_type == "addr-fetch") return "addr"; return ""; } - const UniValue NetinfoHelp() - { - return std::string{ - "-netinfo level|\"help\" \n\n" - "Returns a network peer connections dashboard with information from the remote server.\n" - "Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n" - "An optional integer argument from 0 to 4 can be passed for different peers listings.\n" - "Pass \"help\" to see this detailed help documentation.\n" - "If more than one argument is passed, only the first one is read and parsed.\n" - "Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n" - "Arguments:\n" - "1. level (integer 0-4, optional) Specify the info level of the peers dashboard (default 0):\n" - " 0 - Connection counts and local addresses\n" - " 1 - Like 0 but with a peers listing (without address or version columns)\n" - " 2 - Like 1 but with an address column\n" - " 3 - Like 1 but with a version column\n" - " 4 - Like 1 but with both address and version columns\n" - "2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n" - "Result:\n\n" - "* The peers listing in levels 1-4 displays all of the peers sorted by direction and minimum ping time:\n\n" - " Column Description\n" - " ------ -----------\n" - " <-> Direction\n" - " \"in\" - inbound connections are those initiated by the peer\n" - " \"out\" - outbound connections are those initiated by us\n" - " type Type of peer connection\n" - " \"full\" - full relay, the default\n" - " \"block\" - block relay; like full relay but does not relay transactions or addresses\n" - " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" - " \"feeler\" - short-lived connection for testing addresses\n" - " \"addr\" - address fetch; short-lived connection for requesting addresses\n" - " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" - " mping Minimum observed ping time, in milliseconds (ms)\n" - " ping Last observed ping time, in milliseconds (ms)\n" - " send Time since last message sent to the peer, in seconds\n" - " recv Time since last message received from the peer, in seconds\n" - " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" - " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" - " hb High-bandwidth BIP152 compact block relay\n" - " \".\" (to) - we selected the peer as a high-bandwidth peer\n" - " \"*\" (from) - the peer selected us as a high-bandwidth peer\n" - " age Duration of connection to the peer, in minutes\n" - " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" - " peer selection (only displayed if the -asmap config option is set)\n" - " id Peer index, in increasing order of peer connections since node startup\n" - " address IP address and port of the peer\n" - " version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n" - "* The connection counts table displays the number of peers by direction, network, and the totals\n" - " for each, as well as two special outbound columns for block relay peers and manual peers.\n\n" - "* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n" - "Examples:\n\n" - "Connection counts and local addresses only\n" - "> bitcoin-cli -netinfo\n\n" - "Compact peers listing\n" - "> bitcoin-cli -netinfo 1\n\n" - "Full dashboard\n" - "> bitcoin-cli -netinfo 4\n\n" - "Full live dashboard, adjust --interval or --no-title as needed (Linux)\n" - "> watch --interval 1 --no-title bitcoin-cli -netinfo 4\n\n" - "See this help\n" - "> bitcoin-cli -netinfo help\n"}; - } const int64_t m_time_now{GetSystemTimeInSeconds()}; public: @@ -441,10 +376,8 @@ public: uint8_t n{0}; if (ParseUInt8(args.at(0), &n)) { m_details_level = std::min(n, MAX_DETAIL_LEVEL); - } else if (args.at(0) == "help") { - m_is_help_requested = true; } else { - throw std::runtime_error(strprintf("invalid -netinfo argument: %s", args.at(0))); + throw std::runtime_error(strprintf("invalid -netinfo argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0))); } } UniValue result(UniValue::VARR); @@ -455,9 +388,6 @@ public: UniValue ProcessReply(const UniValue& batch_in) override { - if (m_is_help_requested) { - return JSONRPCReplyObj(NetinfoHelp(), NullUniValue, 1); - } const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)}; if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO]; if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO]; @@ -472,7 +402,6 @@ public: const std::string network{peer["network"].get_str()}; const int8_t network_id{NetworkStringToId(network)}; if (network_id == UNKNOWN_NETWORK) continue; - m_is_i2p_on |= (network_id == NET_I2P); const bool is_outbound{!peer["inbound"].get_bool()}; const bool is_block_relay{!peer["relaytxes"].get_bool()}; const std::string conn_type{peer["connection_type"].get_str()}; @@ -545,13 +474,14 @@ public: // Report peer connection totals by type. result += " ipv4 ipv6 onion"; - if (m_is_i2p_on) result += " i2p"; + const bool any_i2p_peers = m_counts.at(2).at(3); // false if total i2p peers count is 0, otherwise true + if (any_i2p_peers) result += " i2p"; result += " total block"; if (m_manual_peers_count) result += " manual"; const std::array<std::string, 3> rows{{"in", "out", "total"}}; for (uint8_t i = 0; i < 3; ++i) { result += strprintf("\n%-5s %5i %5i %5i", rows.at(i), m_counts.at(i).at(0), m_counts.at(i).at(1), m_counts.at(i).at(2)); // ipv4/ipv6/onion peers counts - if (m_is_i2p_on) result += strprintf(" %5i", m_counts.at(i).at(3)); // i2p peers count + if (any_i2p_peers) result += strprintf(" %5i", m_counts.at(i).at(3)); // i2p peers count result += strprintf(" %5i", m_counts.at(i).at(m_networks_size)); // total peers count if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts result += strprintf(" %5i", m_block_relay_peers_count); @@ -576,6 +506,67 @@ public: return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1); } + + const std::string m_help_doc{ + "-netinfo level|\"help\" \n\n" + "Returns a network peer connections dashboard with information from the remote server.\n" + "This human-readable interface will change regularly and is not intended to be a stable API.\n" + "Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n" + + strprintf("An optional integer argument from 0 to %d can be passed for different peers listings; %d to 255 are parsed as %d.\n", MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL) + + "Pass \"help\" to see this detailed help documentation.\n" + "If more than one argument is passed, only the first one is read and parsed.\n" + "Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n" + "Arguments:\n" + + strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", MAX_DETAIL_LEVEL) + + " 0 - Connection counts and local addresses\n" + " 1 - Like 0 but with a peers listing (without address or version columns)\n" + " 2 - Like 1 but with an address column\n" + " 3 - Like 1 but with a version column\n" + " 4 - Like 1 but with both address and version columns\n" + "2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n" + "Result:\n\n" + + strprintf("* The peers listing in levels 1-%d displays all of the peers sorted by direction and minimum ping time:\n\n", MAX_DETAIL_LEVEL) + + " Column Description\n" + " ------ -----------\n" + " <-> Direction\n" + " \"in\" - inbound connections are those initiated by the peer\n" + " \"out\" - outbound connections are those initiated by us\n" + " type Type of peer connection\n" + " \"full\" - full relay, the default\n" + " \"block\" - block relay; like full relay but does not relay transactions or addresses\n" + " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" + " \"feeler\" - short-lived connection for testing addresses\n" + " \"addr\" - address fetch; short-lived connection for requesting addresses\n" + " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" + " mping Minimum observed ping time, in milliseconds (ms)\n" + " ping Last observed ping time, in milliseconds (ms)\n" + " send Time since last message sent to the peer, in seconds\n" + " recv Time since last message received from the peer, in seconds\n" + " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" + " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" + " hb High-bandwidth BIP152 compact block relay\n" + " \".\" (to) - we selected the peer as a high-bandwidth peer\n" + " \"*\" (from) - the peer selected us as a high-bandwidth peer\n" + " age Duration of connection to the peer, in minutes\n" + " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" + " peer selection (only displayed if the -asmap config option is set)\n" + " id Peer index, in increasing order of peer connections since node startup\n" + " address IP address and port of the peer\n" + " version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n" + "* The connection counts table displays the number of peers by direction, network, and the totals\n" + " for each, as well as two special outbound columns for block relay peers and manual peers.\n\n" + "* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n" + "Examples:\n\n" + "Connection counts and local addresses only\n" + "> bitcoin-cli -netinfo\n\n" + "Compact peers listing\n" + "> bitcoin-cli -netinfo 1\n\n" + "Full dashboard\n" + + strprintf("> bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) + + "Full live dashboard, adjust --interval or --no-title as needed (Linux)\n" + + strprintf("> watch --interval 1 --no-title bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) + + "See this help\n" + "> bitcoin-cli -netinfo help\n"}; }; /** Process RPC generatetoaddress request. */ @@ -907,6 +898,10 @@ static int CommandLineRPC(int argc, char *argv[]) if (gArgs.IsArgSet("-getinfo")) { rh.reset(new GetinfoRequestHandler()); } else if (gArgs.GetBoolArg("-netinfo", false)) { + if (!args.empty() && args.at(0) == "help") { + tfm::format(std::cout, "%s\n", NetinfoRequestHandler().m_help_doc); + return 0; + } rh.reset(new NetinfoRequestHandler()); } else if (gArgs.GetBoolArg("-generate", false)) { const UniValue getnewaddress{GetNewAddress()}; diff --git a/src/compat.h b/src/compat.h index dad14748a2..3449bc2661 100644 --- a/src/compat.h +++ b/src/compat.h @@ -44,6 +44,7 @@ typedef unsigned int SOCKET; #define WSAEINVAL EINVAL #define WSAEALREADY EALREADY #define WSAEWOULDBLOCK EWOULDBLOCK +#define WSAEAGAIN EAGAIN #define WSAEMSGSIZE EMSGSIZE #define WSAEINTR EINTR #define WSAEINPROGRESS EINPROGRESS @@ -51,6 +52,14 @@ typedef unsigned int SOCKET; #define WSAENOTSOCK EBADF #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR -1 +#else +#ifndef WSAEAGAIN +#ifdef EAGAIN +#define WSAEAGAIN EAGAIN +#else +#define WSAEAGAIN WSAEWOULDBLOCK +#endif +#endif #endif #ifdef WIN32 @@ -96,4 +105,14 @@ bool static inline IsSelectableSocket(const SOCKET& s) { #endif } +// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0 +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif + +// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define it as 0 +#if !defined(MSG_DONTWAIT) +#define MSG_DONTWAIT 0 +#endif + #endif // BITCOIN_COMPAT_H diff --git a/src/core_io.h b/src/core_io.h index 5469a760ee..01340ae2ee 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -40,7 +40,7 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN int ParseSighashString(const UniValue& sighash); // core_write.cpp -UniValue ValueFromAmount(const CAmount& amount); +UniValue ValueFromAmount(const CAmount amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0); std::string SighashToStr(unsigned char sighash_type); diff --git a/src/core_write.cpp b/src/core_write.cpp index a3902863d6..d3034ae25d 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -14,17 +14,20 @@ #include <undo.h> #include <univalue.h> #include <util/check.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> -UniValue ValueFromAmount(const CAmount& amount) +UniValue ValueFromAmount(const CAmount amount) { - bool sign = amount < 0; - int64_t n_abs = (sign ? -amount : amount); - int64_t quotient = n_abs / COIN; - int64_t remainder = n_abs % COIN; + static_assert(COIN > 1); + int64_t quotient = amount / COIN; + int64_t remainder = amount % COIN; + if (amount < 0) { + quotient = -quotient; + remainder = -remainder; + } return UniValue(UniValue::VNUM, - strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); + strprintf("%s%d.%08d", amount < 0 ? "-" : "", quotient, remainder)); } std::string FormatScript(const CScript& script) diff --git a/src/i2p.cpp b/src/i2p.cpp new file mode 100644 index 0000000000..42270deaeb --- /dev/null +++ b/src/i2p.cpp @@ -0,0 +1,407 @@ +// Copyright (c) 2020-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <chainparams.h> +#include <compat.h> +#include <compat/endian.h> +#include <crypto/sha256.h> +#include <fs.h> +#include <i2p.h> +#include <logging.h> +#include <netaddress.h> +#include <netbase.h> +#include <random.h> +#include <util/strencodings.h> +#include <tinyformat.h> +#include <util/readwritefile.h> +#include <util/sock.h> +#include <util/spanparsing.h> +#include <util/system.h> + +#include <chrono> +#include <stdexcept> +#include <string> + +namespace i2p { + +/** + * Swap Standard Base64 <-> I2P Base64. + * Standard Base64 uses `+` and `/` as last two characters of its alphabet. + * I2P Base64 uses `-` and `~` respectively. + * So it is easy to detect in which one is the input and convert to the other. + * @param[in] from Input to convert. + * @return converted `from` + */ +static std::string SwapBase64(const std::string& from) +{ + std::string to; + to.resize(from.size()); + for (size_t i = 0; i < from.size(); ++i) { + switch (from[i]) { + case '-': + to[i] = '+'; + break; + case '~': + to[i] = '/'; + break; + case '+': + to[i] = '-'; + break; + case '/': + to[i] = '~'; + break; + default: + to[i] = from[i]; + break; + } + } + return to; +} + +/** + * Decode an I2P-style Base64 string. + * @param[in] i2p_b64 I2P-style Base64 string. + * @return decoded `i2p_b64` + * @throw std::runtime_error if decoding fails + */ +static Binary DecodeI2PBase64(const std::string& i2p_b64) +{ + const std::string& std_b64 = SwapBase64(i2p_b64); + bool invalid; + Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); + if (invalid) { + throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); + } + return decoded; +} + +/** + * Derive the .b32.i2p address of an I2P destination (binary). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestBinToAddr(const Binary& dest) +{ + CSHA256 hasher; + hasher.Write(dest.data(), dest.size()); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(hash); + + CNetAddr addr; + const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; + if (!addr.SetSpecial(addr_str)) { + throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str)); + } + + return addr; +} + +/** + * Derive the .b32.i2p address of an I2P destination (I2P-style Base64). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestB64ToAddr(const std::string& dest) +{ + const Binary& decoded = DecodeI2PBase64(dest); + return DestBinToAddr(decoded); +} + +namespace sam { + +Session::Session(const fs::path& private_key_file, + const CService& control_host, + CThreadInterrupt* interrupt) + : m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt) +{ +} + +Session::~Session() +{ + LOCK(m_mutex); + Disconnect(); +} + +bool Session::Listen(Connection& conn) +{ + try { + LOCK(m_mutex); + CreateIfNotCreatedAlready(); + conn.me = m_my_addr; + conn.sock = StreamAccept(); + return true; + } catch (const std::runtime_error& e) { + Log("Error listening: %s", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Accept(Connection& conn) +{ + try { + while (!*m_interrupt) { + Sock::Event occurred; + conn.sock.Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred); + + if ((occurred & Sock::RECV) == 0) { + // Timeout, no incoming connections within MAX_WAIT_FOR_IO. + continue; + } + + const std::string& peer_dest = + conn.sock.RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt); + + conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); + + return true; + } + } catch (const std::runtime_error& e) { + Log("Error accepting: %s", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) +{ + proxy_error = true; + + std::string session_id; + Sock sock; + conn.peer = to; + + try { + { + LOCK(m_mutex); + CreateIfNotCreatedAlready(); + session_id = m_session_id; + conn.me = m_my_addr; + sock = Hello(); + } + + const Reply& lookup_reply = + SendRequestAndGetReply(sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); + + const std::string& dest = lookup_reply.Get("VALUE"); + + const Reply& connect_reply = SendRequestAndGetReply( + sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest), + false); + + const std::string& result = connect_reply.Get("RESULT"); + + if (result == "OK") { + conn.sock = std::move(sock); + return true; + } + + if (result == "INVALID_ID") { + LOCK(m_mutex); + Disconnect(); + throw std::runtime_error("Invalid session id"); + } + + if (result == "CANT_REACH_PEER" || result == "TIMEOUT") { + proxy_error = false; + } + + throw std::runtime_error(strprintf("\"%s\"", connect_reply.full)); + } catch (const std::runtime_error& e) { + Log("Error connecting to %s: %s", to.ToString(), e.what()); + CheckControlSock(); + return false; + } +} + +// Private methods + +std::string Session::Reply::Get(const std::string& key) const +{ + const auto& pos = keys.find(key); + if (pos == keys.end() || !pos->second.has_value()) { + throw std::runtime_error( + strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full)); + } + return pos->second.value(); +} + +template <typename... Args> +void Session::Log(const std::string& fmt, const Args&... args) const +{ + LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...)); +} + +Session::Reply Session::SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok) const +{ + sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt); + + Reply reply; + + // Don't log the full "SESSION CREATE ..." because it contains our private key. + reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request; + + // It could take a few minutes for the I2P router to reply as it is querying the I2P network + // (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking + // `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is + // signaled. + static constexpr auto recv_timeout = 3min; + + reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt); + + for (const auto& kv : spanparsing::Split(reply.full, ' ')) { + const auto& pos = std::find(kv.begin(), kv.end(), '='); + if (pos != kv.end()) { + reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); + } else { + reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt); + } + } + + if (check_result_ok && reply.Get("RESULT") != "OK") { + throw std::runtime_error( + strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full)); + } + + return reply; +} + +Sock Session::Hello() const +{ + auto sock = CreateSock(m_control_host); + + if (!sock) { + throw std::runtime_error("Cannot create socket"); + } + + if (!ConnectSocketDirectly(m_control_host, sock->Get(), nConnectTimeout, true)) { + throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString())); + } + + SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); + + return std::move(*sock); +} + +void Session::CheckControlSock() +{ + LOCK(m_mutex); + + std::string errmsg; + if (!m_control_sock.IsConnected(errmsg)) { + Log("Control socket error: %s", errmsg); + Disconnect(); + } +} + +void Session::DestGenerate(const Sock& sock) +{ + // https://geti2p.net/spec/common-structures#key-certificates + // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations". + // Use "7" because i2pd <2.24.0 does not recognize the textual form. + const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); + + m_private_key = DecodeI2PBase64(reply.Get("PRIV")); +} + +void Session::GenerateAndSavePrivateKey(const Sock& sock) +{ + DestGenerate(sock); + + // umask is set to 077 in init.cpp, which is ok (unless -sysperms is given) + if (!WriteBinaryFile(m_private_key_file, + std::string(m_private_key.begin(), m_private_key.end()))) { + throw std::runtime_error( + strprintf("Cannot save I2P private key to %s", m_private_key_file)); + } +} + +Binary Session::MyDestination() const +{ + // From https://geti2p.net/spec/common-structures#destination: + // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be + // non-zero" + static constexpr size_t DEST_LEN_BASE = 387; + static constexpr size_t CERT_LEN_POS = 385; + + uint16_t cert_len; + memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); + cert_len = be16toh(cert_len); + + const size_t dest_len = DEST_LEN_BASE + cert_len; + + return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; +} + +void Session::CreateIfNotCreatedAlready() +{ + std::string errmsg; + if (m_control_sock.IsConnected(errmsg)) { + return; + } + + Log("Creating SAM session with %s", m_control_host.ToString()); + + Sock sock = Hello(); + + const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file); + if (read_ok) { + m_private_key.assign(data.begin(), data.end()); + } else { + GenerateAndSavePrivateKey(sock); + } + + const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs + const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); + + SendRequestAndGetReply(sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + session_id, private_key_b64)); + + m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); + m_session_id = session_id; + m_control_sock = std::move(sock); + + LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id, + m_my_addr.ToString()); +} + +Sock Session::StreamAccept() +{ + Sock sock = Hello(); + + const Reply& reply = SendRequestAndGetReply( + sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false); + + const std::string& result = reply.Get("RESULT"); + + if (result == "OK") { + return sock; + } + + if (result == "INVALID_ID") { + // If our session id is invalid, then force session re-creation on next usage. + Disconnect(); + } + + throw std::runtime_error(strprintf("\"%s\"", reply.full)); +} + +void Session::Disconnect() +{ + if (m_control_sock.Get() != INVALID_SOCKET) { + if (m_session_id.empty()) { + Log("Destroying incomplete session"); + } else { + Log("Destroying session %s", m_session_id); + } + } + m_control_sock.Reset(); + m_session_id.clear(); +} +} // namespace sam +} // namespace i2p diff --git a/src/i2p.h b/src/i2p.h new file mode 100644 index 0000000000..8fafe0a4d0 --- /dev/null +++ b/src/i2p.h @@ -0,0 +1,260 @@ +// Copyright (c) 2020-2020 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_I2P_H +#define BITCOIN_I2P_H + +#include <compat.h> +#include <fs.h> +#include <netaddress.h> +#include <sync.h> +#include <threadinterrupt.h> +#include <util/sock.h> + +#include <optional> +#include <string> +#include <unordered_map> +#include <vector> + +namespace i2p { + +/** + * Binary data. + */ +using Binary = std::vector<uint8_t>; + +/** + * An established connection with another peer. + */ +struct Connection { + /** Connected socket. */ + Sock sock; + + /** Our I2P address. */ + CService me; + + /** The peer's I2P address. */ + CService peer; +}; + +namespace sam { + +/** + * I2P SAM session. + */ +class Session +{ +public: + /** + * Construct a session. This will not initiate any IO, the session will be lazily created + * later when first used. + * @param[in] private_key_file Path to a private key file. If the file does not exist then the + * private key will be generated and saved into the file. + * @param[in] control_host Location of the SAM proxy. + * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as + * possible and executing methods throw an exception. Notice: only a pointer to the + * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this + * `Session` object. + */ + Session(const fs::path& private_key_file, + const CService& control_host, + CThreadInterrupt* interrupt); + + /** + * Destroy the session, closing the internally used sockets. The sockets that have been + * returned by `Accept()` or `Connect()` will not be closed, but they will be closed by + * the SAM proxy because the session is destroyed. So they will return an error next time + * we try to read or write to them. + */ + ~Session(); + + /** + * Start listening for an incoming connection. + * @param[out] conn Upon successful completion the `sock` and `me` members will be set + * to the listening socket and address. + * @return true on success + */ + bool Listen(Connection& conn); + + /** + * Wait for and accept a new incoming connection. + * @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful + * completion the `peer` member will be set to the address of the incoming peer. + * @return true on success + */ + bool Accept(Connection& conn); + + /** + * Connect to an I2P peer. + * @param[in] to Peer to connect to. + * @param[out] conn Established connection. Only set if `true` is returned. + * @param[out] proxy_error If an error occurs due to proxy or general network failure, then + * this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then + * it is set to `false`. Only set if `false` is returned. + * @return true on success + */ + bool Connect(const CService& to, Connection& conn, bool& proxy_error); + +private: + /** + * A reply from the SAM proxy. + */ + struct Reply { + /** + * Full, unparsed reply. + */ + std::string full; + + /** + * Request, used for detailed error reporting. + */ + std::string request; + + /** + * A map of keywords from the parsed reply. + * For example, if the reply is "A=X B C=YZ", then the map will be + * keys["A"] == "X" + * keys["B"] == (empty std::optional) + * keys["C"] == "YZ" + */ + std::unordered_map<std::string, std::optional<std::string>> keys; + + /** + * Get the value of a given key. + * For example if the reply is "A=X B" then: + * Value("A") -> "X" + * Value("B") -> throws + * Value("C") -> throws + * @param[in] key Key whose value to retrieve + * @returns the key's value + * @throws std::runtime_error if the key is not present or if it has no value + */ + std::string Get(const std::string& key) const; + }; + + /** + * Log a message in the `BCLog::I2P` category. + * @param[in] fmt printf(3)-like format string. + * @param[in] args printf(3)-like arguments that correspond to `fmt`. + */ + template <typename... Args> + void Log(const std::string& fmt, const Args&... args) const; + + /** + * Send request and get a reply from the SAM proxy. + * @param[in] sock A socket that is connected to the SAM proxy. + * @param[in] request Raw request to send, a newline terminator is appended to it. + * @param[in] check_result_ok If true then after receiving the reply a check is made + * whether it contains "RESULT=OK" and an exception is thrown if it does not. + * @throws std::runtime_error if an error occurs + */ + Reply SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok = true) const; + + /** + * Open a new connection to the SAM proxy. + * @return a connected socket + * @throws std::runtime_error if an error occurs + */ + Sock Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Check the control socket for errors and possibly disconnect. + */ + void CheckControlSock(); + + /** + * Generate a new destination with the SAM proxy and set `m_private_key` to it. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Generate a new destination with the SAM proxy, set `m_private_key` to it and save + * it on disk to `m_private_key_file`. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Derive own destination from `m_private_key`. + * @see https://geti2p.net/spec/common-structures#destination + * @return an I2P destination + */ + Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Create the session if not already created. Reads the private key file and connects to the + * SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing + * session id. Return the idle socket that is waiting for a peer to connect to us. + * @throws std::runtime_error if an error occurs + */ + Sock StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Destroy the session, closing the internally used sockets. + */ + void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * The name of the file where this peer's private key is stored (in binary). + */ + const fs::path m_private_key_file; + + /** + * The host and port of the SAM control service. + */ + const CService m_control_host; + + /** + * Cease network activity when this is signaled. + */ + CThreadInterrupt* const m_interrupt; + + /** + * Mutex protecting the members that can be concurrently accessed. + */ + mutable Mutex m_mutex; + + /** + * The private key of this peer. + * @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3 + */ + Binary m_private_key GUARDED_BY(m_mutex); + + /** + * SAM control socket. + * Used to connect to the I2P SAM service and create a session + * ("SESSION CREATE"). With the established session id we later open + * other connections to the SAM service to accept incoming I2P + * connections and make outgoing ones. + * See https://geti2p.net/en/docs/api/samv3 + */ + Sock m_control_sock GUARDED_BY(m_mutex); + + /** + * Our .b32.i2p address. + * Derived from `m_private_key`. + */ + CService m_my_addr GUARDED_BY(m_mutex); + + /** + * SAM session id. + */ + std::string m_session_id GUARDED_BY(m_mutex); +}; + +} // namespace sam +} // namespace i2p + +#endif /* BITCOIN_I2P_H */ diff --git a/src/init.cpp b/src/init.cpp index 8adb637d6e..584b148aff 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -52,6 +52,7 @@ #include <torcontrol.h> #include <txdb.h> #include <txmempool.h> +#include <txorphanage.h> #include <util/asmap.h> #include <util/check.h> #include <util/moneystr.h> @@ -447,7 +448,9 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by 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 (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with ipv4 or ipv6 but not onion and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION); + argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with non-onion networks and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -718,7 +721,7 @@ static void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImp fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): - LoadGenesisBlock(chainparams); + ::ChainstateActive().LoadGenesisBlock(chainparams); } // -loadblock= @@ -848,6 +851,9 @@ void InitParameterInteraction(ArgsManager& args) LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); if (args.SoftSetBoolArg("-listenonion", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting -listenonion=0\n", __func__); + if (args.SoftSetBoolArg("-i2pacceptincoming", false)) { + LogPrintf("%s: parameter interaction: -listen=0 -> setting -i2pacceptincoming=0\n", __func__); + } } if (args.IsArgSet("-externalip")) { @@ -1631,7 +1637,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA // If we're not mid-reindex (based on disk + args), add a genesis block on disk // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !LoadGenesisBlock(chainparams)) { + if (!fReindex && !::ChainstateActive().LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; } @@ -1742,7 +1748,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA // work when we allow VerifyDB to be parameterized by chainstate. if (&::ChainstateActive() == chainstate && !CVerifyDB().VerifyDB( - chainparams, &chainstate->CoinsDB(), + chainparams, *chainstate, &chainstate->CoinsDB(), args.GetArg("-checklevel", DEFAULT_CHECKLEVEL), args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { strLoadError = _("Corrupted block database detected"); @@ -1991,6 +1997,21 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA connOptions.m_specified_outgoing = connect; } } + + const std::string& i2psam_arg = args.GetArg("-i2psam", ""); + if (!i2psam_arg.empty()) { + CService addr; + if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) { + return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg)); + } + SetReachable(NET_I2P, true); + SetProxy(NET_I2P, proxyType{addr}); + } else { + SetReachable(NET_I2P, false); + } + + connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", true); + if (!node.connman->Start(*node.scheduler, connOptions)) { return false; } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 1a49518d69..ebc5466173 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -159,6 +159,9 @@ public: //! Check if transaction is RBF opt in. virtual RBFTransactionState isRBFOptIn(const CTransaction& tx) = 0; + //! Check if transaction is in mempool. + virtual bool isInMempool(const uint256& txid) = 0; + //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const uint256& txid) = 0; diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 15f7ef6256..1dd1e92e2f 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -6,9 +6,10 @@ #define BITCOIN_INTERFACES_NODE_H #include <amount.h> // For CAmount -#include <net.h> // For CConnman::NumConnections +#include <net.h> // For NodeId #include <net_types.h> // For banmap_t #include <netaddress.h> // For Network +#include <netbase.h> // For ConnectionDirection #include <support/allocators/secure.h> // For SecureString #include <util/translation.h> @@ -88,7 +89,7 @@ public: virtual bool getProxy(Network net, proxyType& proxy_info) = 0; //! Get number of connections. - virtual size_t getNodeCount(CConnman::NumConnections flags) = 0; + virtual size_t getNodeCount(ConnectionDirection flags) = 0; //! Get stats for connected nodes. using NodesStats = std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>; diff --git a/src/logging.cpp b/src/logging.cpp index e82f2c2810..866213786e 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -156,6 +156,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::QT, "qt"}, {BCLog::LEVELDB, "leveldb"}, {BCLog::VALIDATION, "validation"}, + {BCLog::I2P, "i2p"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index 4ece8f5e3a..436f0cd12e 100644 --- a/src/logging.h +++ b/src/logging.h @@ -57,6 +57,7 @@ namespace BCLog { QT = (1 << 19), LEVELDB = (1 << 20), VALIDATION = (1 << 21), + I2P = (1 << 22), ALL = ~(uint32_t)0, }; diff --git a/src/miner.cpp b/src/miner.cpp index 076d43c951..fbaef0f224 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -39,13 +39,13 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam return nNewTime - nOldTime; } -void RegenerateCommitments(CBlock& block) +void RegenerateCommitments(CBlock& block, BlockManager& blockman) { CMutableTransaction tx{*block.vtx.at(0)}; tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block)); block.vtx.at(0) = MakeTransactionRef(tx); - GenerateCoinbaseCommitment(block, WITH_LOCK(cs_main, return g_chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)), Params().GetConsensus()); + GenerateCoinbaseCommitment(block, WITH_LOCK(::cs_main, assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); return blockman.LookupBlockIndex(block.hashPrevBlock)), Params().GetConsensus()); block.hashMerkleRoot = BlockMerkleRoot(block); } @@ -99,7 +99,7 @@ void BlockAssembler::resetBlock() Optional<int64_t> BlockAssembler::m_last_block_num_txs{nullopt}; Optional<int64_t> BlockAssembler::m_last_block_weight{nullopt}; -std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) +std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(CChainState& chainstate, const CScript& scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); @@ -117,7 +117,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end LOCK2(cs_main, m_mempool.cs); - CBlockIndex* pindexPrev = ::ChainActive().Tip(); + assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*chainstate.m_chain.Tip())); + CBlockIndex* pindexPrev = chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; @@ -176,7 +177,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, ::ChainstateActive(), *pblock, pindexPrev, false, false)) { + assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); + if (!TestBlockValidity(state, chainparams, chainstate, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } int64_t nTime2 = GetTimeMicros(); diff --git a/src/miner.h b/src/miner.h index 9a2b7063f4..a67fec6dd8 100644 --- a/src/miner.h +++ b/src/miner.h @@ -158,7 +158,7 @@ public: explicit BlockAssembler(const CTxMemPool& mempool, const CChainParams& params, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ - std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); + std::unique_ptr<CBlockTemplate> CreateNewBlock(CChainState& chainstate, const CScript& scriptPubKeyIn); static Optional<int64_t> m_last_block_num_txs; static Optional<int64_t> m_last_block_weight; @@ -202,6 +202,6 @@ void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */ -void RegenerateCommitments(CBlock& block); +void RegenerateCommitments(CBlock& block, BlockManager& blockman); #endif // BITCOIN_MINER_H diff --git a/src/net.cpp b/src/net.cpp index d9571e7036..cf7d3e50ba 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -11,8 +11,10 @@ #include <banman.h> #include <clientversion.h> +#include <compat.h> #include <consensus/consensus.h> #include <crypto/sha256.h> +#include <i2p.h> #include <net_permissions.h> #include <netbase.h> #include <node/ui_interface.h> @@ -72,16 +74,6 @@ static constexpr std::chrono::seconds MAX_UPLOAD_TIMEFRAME{60 * 60 * 24}; // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 -// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0 -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL 0 -#endif - -// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define it as 0 -#if !defined(MSG_DONTWAIT) -#define MSG_DONTWAIT 0 -#endif - /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, @@ -430,10 +422,20 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo bool connected = false; std::unique_ptr<Sock> sock; proxyType proxy; + CAddress addr_bind; + assert(!addr_bind.IsValid()); + if (addrConnect.IsValid()) { bool proxyConnectionFailed = false; - if (GetProxy(addrConnect.GetNetwork(), proxy)) { + if (addrConnect.GetNetwork() == NET_I2P && m_i2p_sam_session.get() != nullptr) { + i2p::Connection conn; + if (m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed)) { + connected = true; + sock = std::make_unique<Sock>(std::move(conn.sock)); + addr_bind = CAddress{conn.me, NODE_NONE}; + } + } else if (GetProxy(addrConnect.GetNetwork(), proxy)) { sock = CreateSock(proxy.proxy); if (!sock) { return nullptr; @@ -473,7 +475,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo // Add node NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); - CAddress addr_bind = GetBindAddress(sock->Get()); + if (!addr_bind.IsValid()) { + addr_bind = GetBindAddress(sock->Get()); + } CNode* pnode = new CNode(id, nLocalServices, sock->Release(), addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type, /* inbound_onion */ false); pnode->AddRef(); @@ -599,8 +603,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) stats.minFeeFilter = 0; } - stats.m_ping_usec = m_last_ping_time; - stats.m_min_ping_usec = m_min_ping_time; + X(m_last_ping_time); + X(m_min_ping_time); // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); @@ -1005,17 +1009,35 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; - int nInbound = 0; - int nMaxInbound = nMaxConnections - m_max_outbound; - if (hSocket != INVALID_SOCKET) { - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { - LogPrintf("Warning: Unknown socket family\n"); + if (hSocket == INVALID_SOCKET) { + const int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) { + LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); } + return; + } + + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { + LogPrintf("Warning: Unknown socket family\n"); } + const CAddress addr_bind = GetBindAddress(hSocket); + NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; hListenSocket.AddSocketPermissionFlags(permissionFlags); + + CreateNodeFromAcceptedSocket(hSocket, permissionFlags, addr_bind, addr); +} + +void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket, + NetPermissionFlags permissionFlags, + const CAddress& addr_bind, + const CAddress& addr) +{ + int nInbound = 0; + int nMaxInbound = nMaxConnections - m_max_outbound; + AddWhitelistPermissionFlags(permissionFlags, addr); if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); @@ -1032,14 +1054,6 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } - if (hSocket == INVALID_SOCKET) - { - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK) - LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); - return; - } - if (!fNetworkActive) { LogPrint(BCLog::NET, "connection from %s dropped: not accepting new connections\n", addr.ToString()); CloseSocket(hSocket); @@ -1087,7 +1101,6 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); - CAddress addr_bind = GetBindAddress(hSocket); ServiceFlags nodeServices = nLocalServices; if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { @@ -1748,12 +1761,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } // Initiate network connections - auto start = GetTime<std::chrono::seconds>(); + auto start = GetTime<std::chrono::microseconds>(); // Minimum time before next feeler connection (in microseconds). - - int64_t nNextFeeler = PoissonNextSend(count_microseconds(start), FEELER_INTERVAL); - int64_t nNextExtraBlockRelay = PoissonNextSend(count_microseconds(start), EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + auto next_feeler = PoissonNextSend(start, FEELER_INTERVAL); + auto next_extra_block_relay = PoissonNextSend(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); @@ -1836,7 +1848,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY; - int64_t nTime = GetTimeMicros(); + auto now = GetTime<std::chrono::microseconds>(); bool anchor = false; bool fFeeler = false; @@ -1848,7 +1860,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we // try opening an additional OUTBOUND_FULL_RELAY connection. If none of // these conditions are met, check to see if it's time to try an extra - // block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler + // block-relay-only peer (to confirm our tip is current, see below) or the next_feeler // timer to decide if we should open a FEELER. if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) { @@ -1860,7 +1872,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) conn_type = ConnectionType::BLOCK_RELAY; } else if (GetTryNewOutboundPeer()) { // OUTBOUND_FULL_RELAY - } else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) { + } else if (now > next_extra_block_relay && m_start_extra_block_relay_peers) { // Periodically connect to a peer (using regular outbound selection // methodology from addrman) and stay connected long enough to sync // headers, but not much else. @@ -1882,10 +1894,10 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // Because we can promote these connections to block-relay-only // connections, they do not get their own ConnectionType enum // (similar to how we deal with extra outbound peers). - nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + next_extra_block_relay = PoissonNextSend(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); conn_type = ConnectionType::BLOCK_RELAY; - } else if (nTime > nNextFeeler) { - nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); + } else if (now > next_feeler) { + next_feeler = PoissonNextSend(now, FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else { @@ -2175,6 +2187,45 @@ void CConnman::ThreadMessageHandler() } } +void CConnman::ThreadI2PAcceptIncoming() +{ + static constexpr auto err_wait_begin = 1s; + static constexpr auto err_wait_cap = 5min; + auto err_wait = err_wait_begin; + + bool advertising_listen_addr = false; + i2p::Connection conn; + + while (!interruptNet) { + + if (!m_i2p_sam_session->Listen(conn)) { + if (advertising_listen_addr && conn.me.IsValid()) { + RemoveLocal(conn.me); + advertising_listen_addr = false; + } + + interruptNet.sleep_for(err_wait); + if (err_wait < err_wait_cap) { + err_wait *= 2; + } + + continue; + } + + if (!advertising_listen_addr) { + AddLocal(conn.me, LOCAL_BIND); + advertising_listen_addr = true; + } + + if (!m_i2p_sam_session->Accept(conn)) { + continue; + } + + CreateNodeFromAcceptedSocket(conn.sock.Release(), NetPermissionFlags::PF_NONE, + CAddress{conn.me, NODE_NONE}, CAddress{conn.peer, NODE_NONE}); + } +} + bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, NetPermissionFlags permissions) { int nOne = 1; @@ -2374,6 +2425,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) return false; } + proxyType i2p_sam; + if (GetProxy(NET_I2P, i2p_sam)) { + m_i2p_sam_session = std::make_unique<i2p::sam::Session>(GetDataDir() / "i2p_private_key", + i2p_sam.proxy, &interruptNet); + } + for (const auto& strDest : connOptions.vSeedNodes) { AddAddrFetch(strDest); } @@ -2454,6 +2511,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) // Process messages threadMessageHandler = std::thread(&TraceThread<std::function<void()> >, "msghand", std::function<void()>(std::bind(&CConnman::ThreadMessageHandler, this))); + if (connOptions.m_i2p_accept_incoming && m_i2p_sam_session.get() != nullptr) { + threadI2PAcceptIncoming = + std::thread(&TraceThread<std::function<void()>>, "i2paccept", + std::function<void()>(std::bind(&CConnman::ThreadI2PAcceptIncoming, this))); + } + // Dump network addresses scheduler.scheduleEvery([this] { DumpAddresses(); }, DUMP_PEERS_INTERVAL); @@ -2501,6 +2564,9 @@ void CConnman::Interrupt() void CConnman::StopThreads() { + if (threadI2PAcceptIncoming.joinable()) { + threadI2PAcceptIncoming.join(); + } if (threadMessageHandler.joinable()) threadMessageHandler.join(); if (threadOpenConnections.joinable()) @@ -2597,9 +2663,7 @@ std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pc std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addresses, size_t max_pct) { - SOCKET socket; - WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket); - auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes(); + auto local_socket_bytes = requestor.addrBind.GetAddrBytes(); uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) .Write(requestor.addr.GetNetwork()) .Write(local_socket_bytes.data(), local_socket_bytes.size()) @@ -2661,15 +2725,15 @@ bool CConnman::RemoveAddedNode(const std::string& strNode) return false; } -size_t CConnman::GetNodeCount(NumConnections flags) +size_t CConnman::GetNodeCount(ConnectionDirection flags) { LOCK(cs_vNodes); - if (flags == CConnman::CONNECTIONS_ALL) // Shortcut if we want total + if (flags == ConnectionDirection::Both) // Shortcut if we want total return vNodes.size(); int nNum = 0; for (const auto& pnode : vNodes) { - if (flags & (pnode->IsInboundConn() ? CONNECTIONS_IN : CONNECTIONS_OUT)) { + if (flags & (pnode->IsInboundConn() ? ConnectionDirection::In : ConnectionDirection::Out)) { nNum++; } } @@ -2918,20 +2982,21 @@ bool CConnman::ForNode(NodeId id, std::function<bool(CNode* pnode)> func) return found != nullptr && NodeFullyConnected(found) && func(found); } -int64_t CConnman::PoissonNextSendInbound(int64_t now, int average_interval_seconds) +std::chrono::microseconds CConnman::PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval) { - if (m_next_send_inv_to_incoming < now) { + if (m_next_send_inv_to_incoming.load() < now) { // If this function were called from multiple threads simultaneously // it would possible that both update the next send variable, and return a different result to their caller. // This is not possible in practice as only the net processing thread invokes this function. - m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval_seconds); + m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval); } return m_next_send_inv_to_incoming; } -int64_t PoissonNextSend(int64_t now, int average_interval_seconds) +std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) { - return now + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); + double unscaled = -log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */); + return now + std::chrono::duration_cast<std::chrono::microseconds>(unscaled * average_interval + 0.5us); } CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const @@ -14,8 +14,10 @@ #include <compat.h> #include <crypto/siphash.h> #include <hash.h> +#include <i2p.h> #include <net_permissions.h> #include <netaddress.h> +#include <netbase.h> #include <optional.h> #include <policy/feerate.h> #include <protocol.h> @@ -48,10 +50,10 @@ static const bool DEFAULT_WHITELISTFORCERELAY = false; /** Time after which to disconnect, after waiting for a ping response (or inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; -/** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ -static const int FEELER_INTERVAL = 120; +/** Run the feeler connection loop once every 2 minutes. **/ +static constexpr auto FEELER_INTERVAL = 2min; /** Run the extra block-relay-only connection loop once every 5 minutes. **/ -static const int EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 300; +static constexpr auto EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 5min; /** The maximum number of addresses from our addrman to return in response to a getaddr message. */ static constexpr size_t MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 4 MB is currently acceptable). */ @@ -260,8 +262,8 @@ public: uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; NetPermissionFlags m_permissionFlags; - int64_t m_ping_usec; - int64_t m_min_ping_usec; + std::chrono::microseconds m_last_ping_time; + std::chrono::microseconds m_min_ping_time; CAmount minFeeFilter; // Our address, as reported by the peer std::string addrLocal; @@ -572,7 +574,7 @@ public: /** Minimum fee rate with which to filter inv's to this node */ std::atomic<CAmount> minFeeFilter{0}; CAmount lastSentFeeFilter{0}; - int64_t nextSendTimeFeeFilter{0}; + std::chrono::microseconds m_next_send_feefilter{0}; }; // m_tx_relay == nullptr if we're not relaying transactions with this peer @@ -592,11 +594,11 @@ public: std::atomic<int64_t> nLastTXTime{0}; /** Last measured round-trip time. Used only for RPC/GUI stats/debugging.*/ - std::atomic<int64_t> m_last_ping_time{0}; + std::atomic<std::chrono::microseconds> m_last_ping_time{0us}; /** Lowest measured round-trip time. Used as an inbound peer eviction * criterium in CConnman::AttemptToEvictConnection. */ - std::atomic<int64_t> m_min_ping_time{std::numeric_limits<int64_t>::max()}; + std::atomic<std::chrono::microseconds> m_min_ping_time{std::chrono::microseconds::max()}; CNode(NodeId id, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion); ~CNode(); @@ -718,8 +720,8 @@ public: /** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */ void PongReceived(std::chrono::microseconds ping_time) { - m_last_ping_time = count_microseconds(ping_time); - m_min_ping_time = std::min(m_min_ping_time.load(), count_microseconds(ping_time)); + m_last_ping_time = ping_time; + m_min_ping_time = std::min(m_min_ping_time.load(), ping_time); } private: @@ -800,13 +802,6 @@ class CConnman { public: - enum NumConnections { - CONNECTIONS_NONE = 0, - CONNECTIONS_IN = (1U << 0), - CONNECTIONS_OUT = (1U << 1), - CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), - }; - struct Options { ServiceFlags nLocalServices = NODE_NONE; @@ -831,6 +826,7 @@ public: std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; std::vector<bool> m_asmap; + bool m_i2p_accept_incoming; }; void Init(const Options& connOptions) { @@ -974,7 +970,7 @@ public: */ bool AddConnection(const std::string& address, ConnectionType conn_type); - size_t GetNodeCount(NumConnections num); + size_t GetNodeCount(ConnectionDirection); void GetNodeStats(std::vector<CNodeStats>& vstats); bool DisconnectNode(const std::string& node); bool DisconnectNode(const CSubNet& subnet); @@ -1019,7 +1015,7 @@ public: Works assuming that a single interval is used. Variable intervals will result in privacy decrease. */ - int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds); + std::chrono::microseconds PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval); void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = std::move(asmap); } @@ -1048,7 +1044,22 @@ private: void ProcessAddrFetch(); void ThreadOpenConnections(std::vector<std::string> connect); void ThreadMessageHandler(); + void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket& hListenSocket); + + /** + * Create a `CNode` object from a socket that has just been accepted and add the node to + * the `vNodes` member. + * @param[in] hSocket Connected socket to communicate with the peer. + * @param[in] permissionFlags The peer's permissions. + * @param[in] addr_bind The address and port at our side of the connection. + * @param[in] addr The address and port at the peer's side of the connection. + */ + void CreateNodeFromAcceptedSocket(SOCKET hSocket, + NetPermissionFlags permissionFlags, + const CAddress& addr_bind, + const CAddress& addr); + void DisconnectNodes(); void NotifyNumConnectionsChanged(); /** Return true if the peer is inactive and should be disconnected. */ @@ -1207,13 +1218,26 @@ private: Mutex mutexMsgProc; std::atomic<bool> flagInterruptMsgProc{false}; + /** + * This is signaled when network activity should cease. + * A pointer to it is saved in `m_i2p_sam_session`, so make sure that + * the lifetime of `interruptNet` is not shorter than + * the lifetime of `m_i2p_sam_session`. + */ CThreadInterrupt interruptNet; + /** + * I2P SAM session. + * Used to accept incoming and make outgoing I2P connections. + */ + std::unique_ptr<i2p::sam::Session> m_i2p_sam_session; + std::thread threadDNSAddressSeed; std::thread threadSocketHandler; std::thread threadOpenAddedConnections; std::thread threadOpenConnections; std::thread threadMessageHandler; + std::thread threadI2PAcceptIncoming; /** flag for deciding to connect to an extra outbound peer, * in excess of m_max_outbound_full_relay @@ -1226,7 +1250,7 @@ private: */ std::atomic_bool m_start_extra_block_relay_peers{false}; - std::atomic<int64_t> m_next_send_inv_to_incoming{0}; + std::atomic<std::chrono::microseconds> m_next_send_inv_to_incoming{0us}; /** * A vector of -bind=<address>:<port>=onion arguments each of which is @@ -1239,13 +1263,7 @@ private: }; /** Return a timestamp in the future (in microseconds) for exponentially distributed events. */ -int64_t PoissonNextSend(int64_t now, int average_interval_seconds); - -/** Wrapper to return mockable type */ -inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) -{ - return std::chrono::microseconds{PoissonNextSend(now.count(), average_interval.count())}; -} +std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval); /** Dump binary message to file, with timestamp */ void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Span<const unsigned char>& data, bool is_incoming); @@ -1254,7 +1272,7 @@ struct NodeEvictionCandidate { NodeId id; int64_t nTimeConnected; - int64_t m_min_ping_time; + std::chrono::microseconds m_min_ping_time; int64_t nLastBlockTime; int64_t nLastTXTime; bool fRelevantServices; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index c97f7ced46..01b55d8961 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -26,6 +26,7 @@ #include <streams.h> #include <tinyformat.h> #include <txmempool.h> +#include <txorphanage.h> #include <txrequest.h> #include <util/check.h> // For NDEBUG compile time check #include <util/strencodings.h> @@ -35,18 +36,14 @@ #include <memory> #include <typeinfo> -/** Expiration time for orphan transactions in seconds */ -static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; -/** Minimum time between orphan transactions expire time checks in seconds */ -static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; /** How long to cache transactions in mapRelay for normal relay */ -static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME = std::chrono::minutes{15}; +static constexpr auto RELAY_TX_CACHE_TIME = 15min; /** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */ -static constexpr std::chrono::seconds UNCONDITIONAL_RELAY_DELAY = std::chrono::minutes{2}; -/** Headers download timeout expressed in microseconds +static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min; +/** Headers download timeout. * Timeout = base + per_header * (expected number of headers) */ -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/header +static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min; +static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms; /** Protect at least this many outbound peers from disconnection due to slow/ * behind headers chain. */ @@ -93,8 +90,8 @@ static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seco static const unsigned int MAX_GETDATA_SZ = 1000; /** Number of blocks that can be requested at any given time from a single peer. */ static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; -/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */ -static const unsigned int BLOCK_STALLING_TIMEOUT = 2; +/** Time during which a peer must stall block download progress before being disconnected. */ +static constexpr auto BLOCK_STALLING_TIMEOUT = 2s; /** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends * less than this number, we reached its tip. Changing this value is a protocol upgrade. */ static const unsigned int MAX_HEADERS_RESULTS = 2000; @@ -108,10 +105,10 @@ static const int MAX_BLOCKTXN_DEPTH = 10; * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably * want to make this a per-peer adaptive value at some point. */ static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; -/** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; +/** Block download timeout base, expressed in multiples of the block interval (i.e. 10 min) */ +static constexpr double BLOCK_DOWNLOAD_TIMEOUT_BASE = 1; /** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000; +static constexpr double BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 0.5; /** Maximum number of headers to announce when relaying blocks with headers message.*/ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; /** Maximum number of unconnecting headers announcements before DoS score */ @@ -119,17 +116,21 @@ static const int MAX_UNCONNECTING_HEADERS = 10; /** Minimum blocks required to signal NODE_NETWORK_LIMITED */ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; /** Average delay between local address broadcasts */ -static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24}; +static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24h; /** Average delay between peer address broadcasts */ -static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30}; -/** Average delay between trickled inventory transmissions in seconds. - * Blocks and peers with noban permission bypass this, outbound peers get half this delay. */ -static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; +static constexpr auto AVG_ADDRESS_BROADCAST_INTERVAL = 30s; +/** Average delay between trickled inventory transmissions for inbound peers. + * Blocks and peers with noban permission bypass this. */ +static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL = 5s; +/** Average delay between trickled inventory transmissions for outbound peers. + * Use a smaller delay as there is less privacy concern for them. + * Blocks and peers with noban permission bypass this. */ +static constexpr auto OUTBOUND_INVENTORY_BROADCAST_INTERVAL = 2s; /** Maximum rate of inventory items to send per second. * Limits the impact of low-fee transaction floods. */ static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND = 7; /** Maximum number of inventory items to send per transmission. */ -static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * INVENTORY_BROADCAST_INTERVAL; +static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL); /** The number of most recently announced transactions a peer can request. */ static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500; /** Verify that INVENTORY_MAX_RECENT_RELAY is enough to cache everything typically @@ -138,9 +139,9 @@ static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500; * peers, and random variations in the broadcast mechanism. */ static_assert(INVENTORY_MAX_RECENT_RELAY >= INVENTORY_BROADCAST_PER_SECOND * UNCONDITIONAL_RELAY_DELAY / std::chrono::seconds{1}, "INVENTORY_RELAY_MAX too low"); /** Average delay between feefilter broadcasts in seconds. */ -static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; +static constexpr auto AVG_FEEFILTER_BROADCAST_INTERVAL = 10min; /** Maximum feefilter broadcast delay after significant change. */ -static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; +static constexpr auto MAX_FEEFILTER_CHANGE_DELAY = 5min; /** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */ static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; /** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */ @@ -148,25 +149,6 @@ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; /** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */ static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; -struct COrphanTx { - // When modifying, adapt the copy of this definition in tests/DoS_tests. - CTransactionRef tx; - NodeId fromPeer; - int64_t nTimeExpire; - size_t list_pos; -}; - -/** Guards orphan transactions and extra txs for compact blocks */ -RecursiveMutex g_cs_orphans; -/** Map from txid to orphan transaction record. Limited by - * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ -std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); -/** Index from wtxid into the mapOrphanTransactions to lookup orphan - * transactions using their witness ids. */ -std::map<uint256, std::map<uint256, COrphanTx>::iterator> g_orphans_by_wtxid GUARDED_BY(g_cs_orphans); - -void EraseOrphansFor(NodeId peer); - // Internal stuff namespace { /** Blocks that are in flight, and that are in the queue to be downloaded. */ @@ -463,7 +445,7 @@ private: typedef std::map<uint256, CTransactionRef> MapRelay; MapRelay mapRelay GUARDED_BY(cs_main); /** Expiration-time ordered list of (expire time, relay map entry) pairs. */ - std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration GUARDED_BY(cs_main); + std::deque<std::pair<std::chrono::microseconds, MapRelay::iterator>> g_relay_expiration GUARDED_BY(cs_main); /** * When a peer sends us a valid block, instruct it to announce blocks to us @@ -479,35 +461,41 @@ private: /** Number of peers from which we're downloading blocks. */ int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; -}; -} // namespace + /** Storage for orphan information */ + TxOrphanage m_orphanage; -namespace { - - /** Number of preferable block download peers. */ - int nPreferredDownload GUARDED_BY(cs_main) = 0; - - struct IteratorComparator - { - template<typename I> - bool operator()(const I& a, const I& b) const - { - return &(*a) < &(*b); - } - }; - - /** Index from the parents' COutPoint into the mapOrphanTransactions. Used - * to remove orphan transactions from the mapOrphanTransactions */ - std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); - /** Orphan transactions in vector for quick random eviction */ - std::vector<std::map<uint256, COrphanTx>::iterator> g_orphan_list GUARDED_BY(g_cs_orphans); + void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); /** Orphan/conflicted/etc transactions that are kept for compact block reconstruction. * The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of * these are kept in a ring buffer */ - static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); + std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); /** Offset into vExtraTxnForCompact to insert the next tx */ - static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + + void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& chainparams, const CInv& inv, CConnman& connman); + bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params, + BlockFilterType filter_type, uint32_t start_height, + const uint256& stop_hash, uint32_t max_height_diff, + const CBlockIndex*& stop_index, + BlockFilterIndex*& filter_index); + void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman); + void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman); + void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman); +}; +} // namespace + +namespace { + /** Number of preferable block download peers. */ + int nPreferredDownload GUARDED_BY(cs_main) = 0; } // namespace namespace { @@ -518,50 +506,48 @@ namespace { * and we're no longer holding the node's locks. */ struct CNodeState { - //! The peer's address - const CService address; //! The best known block we know this peer has announced. - const CBlockIndex *pindexBestKnownBlock; + const CBlockIndex* pindexBestKnownBlock{nullptr}; //! The hash of the last unknown block this peer has announced. - uint256 hashLastUnknownBlock; + uint256 hashLastUnknownBlock{}; //! The last full block we both have. - const CBlockIndex *pindexLastCommonBlock; + const CBlockIndex* pindexLastCommonBlock{nullptr}; //! The best header we have sent our peer. - const CBlockIndex *pindexBestHeaderSent; + const CBlockIndex* pindexBestHeaderSent{nullptr}; //! Length of current-streak of unconnecting headers announcements - int nUnconnectingHeaders; + int nUnconnectingHeaders{0}; //! Whether we've started headers synchronization with this peer. - bool fSyncStarted; + bool fSyncStarted{false}; //! When to potentially disconnect peer for stalling headers download - int64_t nHeadersSyncTimeout; + std::chrono::microseconds m_headers_sync_timeout{0us}; //! Since when we're stalling block download progress (in microseconds), or 0. - int64_t nStallingSince; + std::chrono::microseconds m_stalling_since{0us}; std::list<QueuedBlock> vBlocksInFlight; //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. - int64_t nDownloadingSince; - int nBlocksInFlight; - int nBlocksInFlightValidHeaders; + std::chrono::microseconds m_downloading_since{0us}; + int nBlocksInFlight{0}; + int nBlocksInFlightValidHeaders{0}; //! Whether we consider this a preferred download peer. - bool fPreferredDownload; + bool fPreferredDownload{false}; //! Whether this peer wants invs or headers (when possible) for block announcements. - bool fPreferHeaders; + bool fPreferHeaders{false}; //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. - bool fPreferHeaderAndIDs; + bool fPreferHeaderAndIDs{false}; /** * Whether this peer will send us cmpctblocks if we request them. * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. */ - bool fProvidesHeaderAndIDs; + bool fProvidesHeaderAndIDs{false}; //! Whether this peer can give us witnesses - bool fHaveWitness; + bool fHaveWitness{false}; //! Whether this peer wants witnesses in cmpctblocks/blocktxns - bool fWantsCmpctWitness; + bool fWantsCmpctWitness{false}; /** * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. */ - bool fSupportsDesiredCmpctVersion; + bool fSupportsDesiredCmpctVersion{false}; /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. * @@ -598,13 +584,13 @@ struct CNodeState { bool m_protect; }; - ChainSyncTimeoutState m_chain_sync; + ChainSyncTimeoutState m_chain_sync{0, nullptr, false, false}; //! Time of last new block announcement - int64_t m_last_block_announcement; + int64_t m_last_block_announcement{0}; //! Whether this peer is an inbound connection - bool m_is_inbound; + const bool m_is_inbound; //! A rolling bloom filter of all announced tx CInvs to this peer. CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; @@ -612,29 +598,9 @@ struct CNodeState { //! Whether this peer relays txs via wtxid bool m_wtxid_relay{false}; - CNodeState(CAddress addrIn, bool is_inbound) - : address(addrIn), m_is_inbound(is_inbound) + CNodeState(bool is_inbound) + : m_is_inbound(is_inbound) { - pindexBestKnownBlock = nullptr; - hashLastUnknownBlock.SetNull(); - pindexLastCommonBlock = nullptr; - pindexBestHeaderSent = nullptr; - nUnconnectingHeaders = 0; - fSyncStarted = false; - nHeadersSyncTimeout = 0; - nStallingSince = 0; - nDownloadingSince = 0; - nBlocksInFlight = 0; - nBlocksInFlightValidHeaders = 0; - fPreferredDownload = false; - fPreferHeaders = false; - fPreferHeaderAndIDs = false; - fProvidesHeaderAndIDs = false; - fHaveWitness = false; - fWantsCmpctWitness = false; - fSupportsDesiredCmpctVersion = false; - m_chain_sync = { 0, nullptr, false, false }; - m_last_block_announcement = 0; m_recently_announced_invs.reset(); } }; @@ -672,11 +638,11 @@ bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) } if (state->vBlocksInFlight.begin() == itInFlight->second.second) { // First block on the queue was received, update the start download time for the next one - state->nDownloadingSince = std::max(state->nDownloadingSince, count_microseconds(GetTime<std::chrono::microseconds>())); + state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); } state->vBlocksInFlight.erase(itInFlight->second.second); state->nBlocksInFlight--; - state->nStallingSince = 0; + state->m_stalling_since = 0us; mapBlocksInFlight.erase(itInFlight); return true; } @@ -706,7 +672,7 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { // We're starting a block download (batch) from this peer. - state->nDownloadingSince = GetTime<std::chrono::microseconds>().count(); + state->m_downloading_since = GetTime<std::chrono::microseconds>(); } if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) { nPeersWithValidatedDownloads++; @@ -717,41 +683,6 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co return true; } -/** Check whether the last unknown block a peer advertised is not yet known. */ -static void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - CNodeState *state = State(nodeid); - assert(state != nullptr); - - if (!state->hashLastUnknownBlock.IsNull()) { - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(state->hashLastUnknownBlock); - if (pindex && pindex->nChainWork > 0) { - if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { - state->pindexBestKnownBlock = pindex; - } - state->hashLastUnknownBlock.SetNull(); - } - } -} - -/** Update tracking information about which blocks a peer is assumed to have. */ -static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - CNodeState *state = State(nodeid); - assert(state != nullptr); - - ProcessBlockAvailability(nodeid); - - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hash); - if (pindex && pindex->nChainWork > 0) { - // An actually better block was announced. - if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { - state->pindexBestKnownBlock = pindex; - } - } else { - // An unknown block was announced; just assume that the latest one is the best one. - state->hashLastUnknownBlock = hash; - } -} - void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) { AssertLockHeld(cs_main); @@ -801,9 +732,9 @@ bool PeerManagerImpl::TipMayBeStale() return m_last_tip_update < GetTime() - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty(); } -static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::CanDirectFetch(const Consensus::Params &consensusParams) { - return ::ChainActive().Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; + return m_chainman.ActiveChain().Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -815,6 +746,41 @@ static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIV return false; } +/** Check whether the last unknown block a peer advertised is not yet known. */ +void PeerManagerImpl::ProcessBlockAvailability(NodeId nodeid) { + CNodeState *state = State(nodeid); + assert(state != nullptr); + + if (!state->hashLastUnknownBlock.IsNull()) { + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(state->hashLastUnknownBlock); + if (pindex && pindex->nChainWork > 0) { + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } + state->hashLastUnknownBlock.SetNull(); + } + } +} + +/** Update tracking information about which blocks a peer is assumed to have. */ +void PeerManagerImpl::UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { + CNodeState *state = State(nodeid); + assert(state != nullptr); + + ProcessBlockAvailability(nodeid); + + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hash); + if (pindex && pindex->nChainWork > 0) { + // An actually better block was announced. + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } + } else { + // An unknown block was announced; just assume that the latest one is the best one. + state->hashLastUnknownBlock = hash; + } +} + void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) { if (count == 0) @@ -827,7 +793,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(nodeid); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < ::ChainActive().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has nothing interesting. return; } @@ -835,7 +801,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count if (state->pindexLastCommonBlock == nullptr) { // Bootstrap quickly by guessing a parent of our best tip is the forking point. // Guessing wrong in either direction is not a problem. - state->pindexLastCommonBlock = ::ChainActive()[std::min(state->pindexBestKnownBlock->nHeight, ::ChainActive().Height())]; + state->pindexLastCommonBlock = m_chainman.ActiveChain()[std::min(state->pindexBestKnownBlock->nHeight, m_chainman.ActiveChain().Height())]; } // If the peer reorganized, our previous pindexLastCommonBlock may not be an ancestor @@ -878,7 +844,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // We wouldn't download this block or its descendants from this peer. return; } - if (pindex->nStatus & BLOCK_HAVE_DATA || ::ChainActive().Contains(pindex)) { + if (pindex->nStatus & BLOCK_HAVE_DATA || m_chainman.ActiveChain().Contains(pindex)) { if (pindex->HaveTxsDownloaded()) state->pindexLastCommonBlock = pindex; } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { @@ -970,12 +936,10 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) void PeerManagerImpl::InitializeNode(CNode *pnode) { - CAddress addr = pnode->addr; - std::string addrName = pnode->GetAddrName(); NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, pnode->IsInboundConn())); + mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); assert(m_txrequest.Count(nodeid) == 0); } { @@ -1040,7 +1004,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node, bool& fUpdateConnectionTim for (const QueuedBlock& entry : state->vBlocksInFlight) { mapBlocksInFlight.erase(entry.hash); } - EraseOrphansFor(nodeid); + WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); nPreferredDownload -= state->fPreferredDownload; nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); @@ -1112,17 +1076,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load(); } - stats.m_ping_wait_usec = count_microseconds(ping_wait); + stats.m_ping_wait = ping_wait; return true; } -////////////////////////////////////////////////////////////////////////////// -// -// mapOrphanTransactions -// - -static void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) +void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) { size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN); if (max_extra_txn <= 0) @@ -1133,126 +1092,6 @@ static void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_L vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; } -bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) -{ - const uint256& hash = tx->GetHash(); - if (mapOrphanTransactions.count(hash)) - return false; - - // Ignore big transactions, to avoid a - // send-big-orphans memory exhaustion attack. If a peer has a legitimate - // large transaction with a missing parent then we assume - // it will rebroadcast it later, after the parent transaction(s) - // have been mined or received. - // 100 orphans, each of which is at most 100,000 bytes big is - // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): - unsigned int sz = GetTransactionWeight(*tx); - if (sz > MAX_STANDARD_TX_WEIGHT) - { - LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); - return false; - } - - auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, g_orphan_list.size()}); - assert(ret.second); - g_orphan_list.push_back(ret.first); - // Allow for lookups in the orphan pool by wtxid, as well as txid - g_orphans_by_wtxid.emplace(tx->GetWitnessHash(), ret.first); - for (const CTxIn& txin : tx->vin) { - mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); - } - - AddToCompactExtraTransactions(tx); - - LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(), - mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size()); - return true; -} - -int static EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) -{ - std::map<uint256, COrphanTx>::iterator it = mapOrphanTransactions.find(hash); - if (it == mapOrphanTransactions.end()) - return 0; - for (const CTxIn& txin : it->second.tx->vin) - { - auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout); - if (itPrev == mapOrphanTransactionsByPrev.end()) - continue; - itPrev->second.erase(it); - if (itPrev->second.empty()) - mapOrphanTransactionsByPrev.erase(itPrev); - } - - size_t old_pos = it->second.list_pos; - assert(g_orphan_list[old_pos] == it); - if (old_pos + 1 != g_orphan_list.size()) { - // Unless we're deleting the last entry in g_orphan_list, move the last - // entry to the position we're deleting. - auto it_last = g_orphan_list.back(); - g_orphan_list[old_pos] = it_last; - it_last->second.list_pos = old_pos; - } - g_orphan_list.pop_back(); - g_orphans_by_wtxid.erase(it->second.tx->GetWitnessHash()); - - mapOrphanTransactions.erase(it); - return 1; -} - -void EraseOrphansFor(NodeId peer) -{ - LOCK(g_cs_orphans); - int nErased = 0; - std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); - while (iter != mapOrphanTransactions.end()) - { - std::map<uint256, COrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid - if (maybeErase->second.fromPeer == peer) - { - nErased += EraseOrphanTx(maybeErase->second.tx->GetHash()); - } - } - if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); -} - - -unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) -{ - LOCK(g_cs_orphans); - - unsigned int nEvicted = 0; - static int64_t nNextSweep; - int64_t nNow = GetTime(); - if (nNextSweep <= nNow) { - // Sweep out expired orphan pool entries: - int nErased = 0; - int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; - std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); - while (iter != mapOrphanTransactions.end()) - { - std::map<uint256, COrphanTx>::iterator maybeErase = iter++; - if (maybeErase->second.nTimeExpire <= nNow) { - nErased += EraseOrphanTx(maybeErase->second.tx->GetHash()); - } else { - nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); - } - } - // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan. - nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; - if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); - } - FastRandomContext rng; - while (mapOrphanTransactions.size() > nMaxOrphans) - { - // Evict a random orphan: - size_t randompos = rng.randrange(g_orphan_list.size()); - EraseOrphanTx(g_orphan_list[randompos]->first); - ++nEvicted; - } - return nEvicted; -} - void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) { assert(howmuch > 0); @@ -1358,10 +1197,10 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat // active chain if they are no more than a month older (both in time, and in // best equivalent proof of work) than the best header chain we know about and // we fully-validated them at some point. -static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) { AssertLockHeld(cs_main); - if (::ChainActive().Contains(pindex)) return true; + if (m_chainman.ActiveChain().Contains(pindex)) return true; return pindex->IsValid(BLOCK_VALID_SCRIPTS) && (pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); @@ -1385,6 +1224,7 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn m_stale_tip_check_time(0), m_ignore_incoming_txs(ignore_incoming_txs) { + assert(std::addressof(g_chainman) == std::addressof(m_chainman)); // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -1412,43 +1252,15 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn } /** - * Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected + * Evict orphan txn pool entries based on a newly connected * block, remember the recently confirmed transactions, and delete tracked * announcements for them. Also save the time of the last tip update. */ void PeerManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex) { - { - LOCK(g_cs_orphans); - - std::vector<uint256> vOrphanErase; + m_orphanage.EraseForBlock(*pblock); + m_last_tip_update = GetTime(); - for (const CTransactionRef& ptx : pblock->vtx) { - const CTransaction& tx = *ptx; - - // Which orphan pool entries must we evict? - for (const auto& txin : tx.vin) { - auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout); - if (itByPrev == mapOrphanTransactionsByPrev.end()) continue; - for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { - const CTransaction& orphanTx = *(*mi)->second.tx; - const uint256& orphanHash = orphanTx.GetHash(); - vOrphanErase.push_back(orphanHash); - } - } - } - - // Erase orphan transactions included or precluded by this block - if (vOrphanErase.size()) { - int nErased = 0; - for (const uint256& orphanHash : vOrphanErase) { - nErased += EraseOrphanTx(orphanHash); - } - LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); - } - - m_last_tip_update = GetTime(); - } { LOCK(m_recent_confirmed_transactions_mutex); for (const auto& ptx : pblock->vtx) { @@ -1538,7 +1350,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha /** * Update our best height and announce any block hashes which weren't previously - * in ::ChainActive() to our peers. + * in m_chainman.ActiveChain() to our peers. */ void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { @@ -1600,7 +1412,7 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta // the tip yet so we have no way to check this directly here. Instead we // just check that there are currently no other blocks in flight. else if (state.IsValid() && - !::ChainstateActive().IsInitialBlockDownload() && + !m_chainman.ActiveChainstate().IsInitialBlockDownload() && mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) { if (it != mapBlockSource.end()) { MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first); @@ -1619,25 +1431,18 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) { assert(recentRejects); - if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { + if (m_chainman.ActiveChain().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { // If the chain tip has changed previously rejected transactions // might be now valid, e.g. due to a nLockTime'd tx becoming valid, // or a double-spend. Reset the rejects filter and give those // txs a second chance. - hashRecentRejectsChainTip = ::ChainActive().Tip()->GetBlockHash(); + hashRecentRejectsChainTip = m_chainman.ActiveChain().Tip()->GetBlockHash(); recentRejects->reset(); } const uint256& hash = gtxid.GetHash(); - { - LOCK(g_cs_orphans); - if (!gtxid.IsWtxid() && mapOrphanTransactions.count(hash)) { - return true; - } else if (gtxid.IsWtxid() && g_orphans_by_wtxid.count(hash)) { - return true; - } - } + if (m_orphanage.HaveTx(gtxid)) return true; { LOCK(m_recent_confirmed_transactions_mutex); @@ -1647,9 +1452,9 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) return recentRejects->contains(hash) || m_mempool.exists(gtxid); } -bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::AlreadyHaveBlock(const uint256& block_hash) { - return g_chainman.m_blockman.LookupBlockIndex(block_hash) != nullptr; + return m_chainman.m_blockman.LookupBlockIndex(block_hash) != nullptr; } void PeerManagerImpl::SendPings() @@ -1728,7 +1533,7 @@ static void RelayAddress(const CNode& originator, connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc)); } -void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& chainparams, const CInv& inv, CConnman& connman) +void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& chainparams, const CInv& inv, CConnman& connman) { bool send = false; std::shared_ptr<const CBlock> a_recent_block; @@ -1745,7 +1550,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch bool need_activate_chain = false; { LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(inv.hash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); if (pindex) { if (pindex->HaveTxsDownloaded() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && pindex->IsValid(BLOCK_VALID_TREE)) { @@ -1760,13 +1565,13 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch } // release cs_main before calling ActivateBestChain if (need_activate_chain) { BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, chainparams, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(inv.hash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); if (pindex) { send = BlockRequestAllowed(pindex, consensusParams); if (!send) { @@ -1788,7 +1593,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold if (send && !pfrom.HasPermission(PF_NOBAN) && ( - (((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom.GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + (((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom.GetLocalServices() & 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 from peer=%d\n", pfrom.GetId()); @@ -1855,7 +1660,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch // instead we respond with the full, non-compact block. bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - if (CanDirectFetch(consensusParams) && pindex->nHeight >= ::ChainActive().Height() - MAX_CMPCTBLOCK_DEPTH) { + if (CanDirectFetch(consensusParams) && pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_CMPCTBLOCK_DEPTH) { if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { @@ -1876,7 +1681,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch // and we want it right after the last block so they don't // wait for other stuff first. std::vector<CInv> vInv; - vInv.push_back(CInv(MSG_BLOCK, ::ChainActive().Tip()->GetBlockHash())); + vInv.push_back(CInv(MSG_BLOCK, m_chainman.ActiveChain().Tip()->GetBlockHash())); connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv)); peer.m_continuation_block.SetNull(); } @@ -2055,9 +1860,9 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // don't connect before giving DoS points // - Once a headers message is received that is valid and does connect, // nUnconnectingHeaders gets reset back to 0. - if (!g_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { + if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { nodestate->nUnconnectingHeaders++; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), uint256())); LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", headers[0].GetHash().ToString(), headers[0].hashPrevBlock.ToString(), @@ -2085,7 +1890,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // If we don't have the last header, then they'll have given us // something new (if these headers are valid). - if (!g_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) { + if (!m_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) { received_new_header = true; } } @@ -2113,27 +1918,27 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // because it is set in UpdateBlockAvailability. Some nullptr checks // are still present, however, as belt-and-suspenders. - if (received_new_header && pindexLast->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } if (nCount == MAX_HEADERS_RESULTS) { // Headers message had its maximum size; the peer may have more headers. - // TODO: optimize: if pindexLast is an ancestor of ::ChainActive().Tip or pindexBestHeader, continue + // TODO: optimize: if pindexLast is an ancestor of m_chainman.ActiveChain().Tip or pindexBestHeader, continue // from there instead. LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256())); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexLast), uint256())); } bool fCanDirectFetch = CanDirectFetch(m_chainparams.GetConsensus()); // If this set of headers is valid and ends in a block with at least as // much work as our tip, download as much as possible. - if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && ::ChainActive().Tip()->nChainWork <= pindexLast->nChainWork) { + if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) { std::vector<const CBlockIndex*> vToFetch; const CBlockIndex *pindexWalk = pindexLast; // Calculate all the blocks we'd need to switch to pindexLast, up to a limit. - while (pindexWalk && !::ChainActive().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && !mapBlocksInFlight.count(pindexWalk->GetBlockHash()) && (!IsWitnessEnabled(pindexWalk->pprev, m_chainparams.GetConsensus()) || State(pfrom.GetId())->fHaveWitness)) { @@ -2146,7 +1951,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // very large reorg at a time we think we're close to caught up to // the main chain -- this shouldn't really happen. Bail out on the // direct fetch and rely on parallel download instead. - if (!::ChainActive().Contains(pindexWalk)) { + if (!m_chainman.ActiveChain().Contains(pindexWalk)) { LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n", pindexLast->GetBlockHash().ToString(), pindexLast->nHeight); @@ -2179,7 +1984,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } // If we're in IBD, we want outbound peers that will serve us a useful // chain. Disconnect peers that are on chains with insufficient work. - if (::ChainstateActive().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { + if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { // When nCount < MAX_HEADERS_RESULTS, we know we have no more // headers to fetch from this peer. if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { @@ -2187,7 +1992,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // us sync -- disconnect if it is an outbound disconnection // candidate. // Note: We compare their tip to nMinimumChainWork (rather than - // ::ChainActive().Tip()) because we won't start block download + // m_chainman.ActiveChain().Tip()) because we won't start block download // until we have a headers chain that has at least // nMinimumChainWork, even if a peer has a chain past our tip, // as an anti-DoS measure. @@ -2204,7 +2009,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // thus always subject to eviction under the bad/lagging chain logic. // See ChainSyncTimeoutState. if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) { - if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { + if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId()); nodestate->m_chain_sync.m_protect = true; ++m_outbound_peers_with_protect_from_disconnect; @@ -2232,25 +2037,17 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) const uint256 orphanHash = *orphan_work_set.begin(); orphan_work_set.erase(orphan_work_set.begin()); - auto orphan_it = mapOrphanTransactions.find(orphanHash); - if (orphan_it == mapOrphanTransactions.end()) continue; + const auto [porphanTx, from_peer] = m_orphanage.GetTx(orphanHash); + if (porphanTx == nullptr) continue; - const CTransactionRef porphanTx = orphan_it->second.tx; - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, porphanTx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, porphanTx, false /* bypass_limits */); const TxValidationState& state = result.m_state; if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), m_connman); - for (unsigned int i = 0; i < porphanTx->vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - orphan_work_set.insert(elem->first); - } - } - } - EraseOrphanTx(orphanHash); + m_orphanage.AddChildrenToWorkSet(*porphanTx, orphan_work_set); + m_orphanage.EraseTx(orphanHash); for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) { AddToCompactExtraTransactions(removedTx); } @@ -2259,10 +2056,10 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) if (state.IsInvalid()) { LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s from peer=%d. %s\n", orphanHash.ToString(), - orphan_it->second.fromPeer, + from_peer, state.ToString()); // Maybe punish peer that gave us an invalid orphan tx - MaybePunishNodeForTx(orphan_it->second.fromPeer, state); + MaybePunishNodeForTx(from_peer, state); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee @@ -2297,11 +2094,11 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) recentRejects->insert(porphanTx->GetHash()); } } - EraseOrphanTx(orphanHash); + m_orphanage.EraseTx(orphanHash); break; } } - m_mempool.check(&::ChainstateActive().CoinsTip()); + m_mempool.check(m_chainman.ActiveChainstate()); } /** @@ -2319,7 +2116,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) * @param[out] filter_index The filter index, if the request can be serviced. * @return True if the request can be serviced. */ -static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params, +bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params, BlockFilterType filter_type, uint32_t start_height, const uint256& stop_hash, uint32_t max_height_diff, const CBlockIndex*& stop_index, @@ -2337,7 +2134,7 @@ static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_par { LOCK(cs_main); - stop_index = g_chainman.m_blockman.LookupBlockIndex(stop_hash); + stop_index = m_chainman.m_blockman.LookupBlockIndex(stop_hash); // Check that the stop block exists and the peer would be allowed to fetch it. if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) { @@ -2382,7 +2179,7 @@ static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_par * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, +void PeerManagerImpl::ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, CConnman& connman) { uint8_t filter_type_ser; @@ -2424,7 +2221,7 @@ static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainPara * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, +void PeerManagerImpl::ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, CConnman& connman) { uint8_t filter_type_ser; @@ -2479,7 +2276,7 @@ static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainPar * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, +void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, CConnman& connman) { uint8_t filter_type_ser; @@ -2662,7 +2459,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // // We skip this for block-relay-only peers to avoid potentially leaking // information about our block-relay-only connections via address relay. - if (fListen && !::ChainstateActive().IsInitialBlockDownload()) + if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices()); FastRandomContext insecure_rand; @@ -2979,7 +2776,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (best_block != nullptr) { - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), *best_block)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), *best_block)); LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, best_block->ToString(), pfrom.GetId()); } @@ -3035,7 +2832,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, a_recent_block = most_recent_block; } BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, m_chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } @@ -3043,14 +2840,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LOCK(cs_main); // Find the last block the caller has in the main chain - const CBlockIndex* pindex = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator); + const CBlockIndex* pindex = m_chainman.m_blockman.FindForkInGlobalIndex(m_chainman.ActiveChain(), locator); // Send the rest of the chain if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = m_chainman.ActiveChain().Next(pindex); int nLimit = 500; LogPrint(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + for (; pindex; pindex = m_chainman.ActiveChain().Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { @@ -3060,7 +2857,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // If pruning, don't inv blocks unless we have on disk and are likely to still have // for some reasonable time window (1 hour) that block relay might require. const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; - if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= ::ChainActive().Tip()->nHeight - nPrunedBlocksLikelyToHave)) + if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= m_chainman.ActiveChain().Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint(BCLog::NET, " getblocks stopping, pruned or too old block at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; @@ -3096,13 +2893,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(req.blockhash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(req.blockhash); if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have\n", pfrom.GetId()); return; } - if (pindex->nHeight >= ::ChainActive().Height() - MAX_BLOCKTXN_DEPTH) { + if (pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_BLOCKTXN_DEPTH) { CBlock block; bool ret = ReadBlockFromDisk(block, pindex, m_chainparams.GetConsensus()); assert(ret); @@ -3140,7 +2937,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) { + if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId()); return; } @@ -3150,7 +2947,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (locator.IsNull()) { // If locator is null, return the hashStop block - pindex = g_chainman.m_blockman.LookupBlockIndex(hashStop); + pindex = m_chainman.m_blockman.LookupBlockIndex(hashStop); if (!pindex) { return; } @@ -3163,23 +2960,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, else { // Find the last block the caller has in the main chain - pindex = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator); + pindex = m_chainman.m_blockman.FindForkInGlobalIndex(m_chainman.ActiveChain(), locator); if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = m_chainman.ActiveChain().Next(pindex); } // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end std::vector<CBlock> vHeaders; int nLimit = MAX_HEADERS_RESULTS; LogPrint(BCLog::NET, "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + for (; pindex; pindex = m_chainman.ActiveChain().Next(pindex)) { vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) break; } - // pindex can be nullptr either if we sent ::ChainActive().Tip() OR - // if our peer has ::ChainActive().Tip() (and thus we are sending an empty + // pindex can be nullptr either if we sent m_chainman.ActiveChain().Tip() OR + // if our peer has m_chainman.ActiveChain().Tip() (and thus we are sending an empty // headers message). In both cases it's safe to update // pindexBestHeaderSent to be our tip. // @@ -3190,7 +2987,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // without the new block. By resetting the BestHeaderSent, we ensure we // will re-announce the new block via headers (or compact blocks again) // in the SendMessages logic. - nodestate->pindexBestHeaderSent = pindex ? pindex : ::ChainActive().Tip(); + nodestate->pindexBestHeaderSent = pindex ? pindex : m_chainman.ActiveChain().Tip(); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); return; } @@ -3258,24 +3055,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, ptx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, ptx, false /* bypass_limits */); const TxValidationState& state = result.m_state; if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { - m_mempool.check(&::ChainstateActive().CoinsTip()); + m_mempool.check(m_chainman.ActiveChainstate()); // As this version of the transaction was acceptable, we can forget about any // requests for it. m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash()); RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman); - for (unsigned int i = 0; i < tx.vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - peer->m_orphan_work_set.insert(elem->first); - } - } - } + m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set); pfrom.nLastTXTime = GetTime(); @@ -3324,17 +3114,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, pfrom.AddKnownTx(parent_txid); if (!AlreadyHaveTx(gtxid)) AddTxAnnouncement(pfrom, gtxid, current_time); } - AddOrphanTx(ptx, pfrom.GetId()); + + if (m_orphanage.AddTx(ptx, pfrom.GetId())) { + AddToCompactExtraTransactions(ptx); + } // Once added to the orphan pool, a tx is considered AlreadyHave, and we shouldn't request it anymore. m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - // DoS prevention: do not allow mapOrphanTransactions to grow unbounded (see CVE-2012-3789) + // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); - unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); + unsigned int nEvicted = m_orphanage.LimitOrphans(nMaxOrphanTx); if (nEvicted > 0) { - LogPrint(BCLog::MEMPOOL, "mapOrphan overflow, removed %u tx\n", nEvicted); + LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted); } } else { LogPrint(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString()); @@ -3427,14 +3220,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { LOCK(cs_main); - if (!g_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { + if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers - if (!::ChainstateActive().IsInitialBlockDownload()) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); + if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), uint256())); return; } - if (!g_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) { + if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) { received_new_header = true; } } @@ -3474,7 +3267,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // If this was a new header with more work than our tip, update the // peer's last block announcement time - if (received_new_header && pindex->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindex->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } @@ -3484,7 +3277,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (pindex->nStatus & BLOCK_HAVE_DATA) // Nothing to do here return; - if (pindex->nChainWork <= ::ChainActive().Tip()->nChainWork || // We know something better + if (pindex->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || // We know something better pindex->nTx != 0) { // We had this block at some point, but pruned it if (fAlreadyInFlight) { // We requested this block for some reason, but our mempool will probably be useless @@ -3508,7 +3301,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // We want to be a bit conservative just to be extra careful about DoS // possibilities in compact block processing... - if (pindex->nHeight <= ::ChainActive().Height() + 2) { + if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) { if ((!fAlreadyInFlight && nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) || (fAlreadyInFlight && blockInFlightIt->second.first == pfrom.GetId())) { std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr; @@ -4156,7 +3949,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // their chain has more work than ours, we should sync to it, // unless it's invalid, in which case we should find that out and // disconnect from them elsewhere). - if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork) { + if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork) { if (state.m_chain_sync.m_timeout != 0) { state.m_chain_sync.m_timeout = 0; state.m_chain_sync.m_work_header = nullptr; @@ -4168,7 +3961,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // where we checked against our tip. // Either way, set a new timeout based on current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; - state.m_chain_sync.m_work_header = ::ChainActive().Tip(); + state.m_chain_sync.m_work_header = m_chainman.ActiveChain().Tip(); state.m_chain_sync.m_sent_getheaders = false; } else if (state.m_chain_sync.m_timeout > 0 && time_in_seconds > state.m_chain_sync.m_timeout) { // No evidence yet that our peer has synced to a chain with work equal to that @@ -4181,7 +3974,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) } else { assert(state.m_chain_sync.m_work_header); LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>", state.m_chain_sync.m_work_header->GetBlockHash().ToString()); - m_connman.PushMessage(&pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); + m_connman.PushMessage(&pto, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); state.m_chain_sync.m_sent_getheaders = true; constexpr int64_t HEADERS_RESPONSE_TIME = 120; // 2 minutes // Bump the timeout to allow a response, which could clear the timeout @@ -4418,7 +4211,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) auto current_time = GetTime<std::chrono::microseconds>(); if (fListen && pto->RelayAddrsWithConn() && - !::ChainstateActive().IsInitialBlockDownload() && + !m_chainman.ActiveChainstate().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) { // If we've sent before, clear the bloom filter for the peer, so that our // self-announcement will actually go out. @@ -4479,13 +4272,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Start block sync if (pindexBestHeader == nullptr) - pindexBestHeader = ::ChainActive().Tip(); + pindexBestHeader = m_chainman.ActiveChain().Tip(); bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. if ((nSyncStarted == 0 && fFetch) || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { state.fSyncStarted = true; - state.nHeadersSyncTimeout = count_microseconds(current_time) + HEADERS_DOWNLOAD_TIMEOUT_BASE + HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER * (GetAdjustedTime() - pindexBestHeader->GetBlockTime())/(consensusParams.nPowTargetSpacing); + state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE + + ( + // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling + // to maintain precision + std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} * + (GetAdjustedTime() - pindexBestHeader->GetBlockTime()) / consensusParams.nPowTargetSpacing + ); nSyncStarted++; const CBlockIndex *pindexStart = pindexBestHeader; /* If possible, start at the block preceding the currently @@ -4498,7 +4297,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pindexStart->pprev) pindexStart = pindexStart->pprev; LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), peer->m_starting_height); - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexStart), uint256())); + m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexStart), uint256())); } } @@ -4525,11 +4324,11 @@ bool PeerManagerImpl::SendMessages(CNode* pto) bool fFoundStartingHeader = false; // Try to find first header that our peer doesn't have, and // then send all headers past that one. If we come across any - // headers that aren't on ::ChainActive(), give up. + // headers that aren't on m_chainman.ActiveChain(), give up. for (const uint256& hash : peer->m_blocks_for_headers_relay) { - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hash); assert(pindex); - if (::ChainActive()[pindex->nHeight] != pindex) { + if (m_chainman.ActiveChain()[pindex->nHeight] != pindex) { // Bail out if we reorged away from this block fRevertToInv = true; break; @@ -4619,15 +4418,15 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // in the past. if (!peer->m_blocks_for_headers_relay.empty()) { const uint256& hashToAnnounce = peer->m_blocks_for_headers_relay.back(); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hashToAnnounce); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hashToAnnounce); assert(pindex); // Warn if we're announcing a block that is not on the main chain. // This should be very rare and could be optimized out. // Just log for now. - if (::ChainActive()[pindex->nHeight] != pindex) { + if (m_chainman.ActiveChain()[pindex->nHeight] != pindex) { LogPrint(BCLog::NET, "Announcing block %s not on main chain (tip=%s)\n", - hashToAnnounce.ToString(), ::ChainActive().Tip()->GetBlockHash().ToString()); + hashToAnnounce.ToString(), m_chainman.ActiveChain().Tip()->GetBlockHash().ToString()); } // If the peer's chain has this block, don't inv it back. @@ -4666,10 +4465,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->m_tx_relay->nNextInvSend < current_time) { fSendTrickle = true; if (pto->IsInboundConn()) { - pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{m_connman.PoissonNextSendInbound(count_microseconds(current_time), INVENTORY_BROADCAST_INTERVAL)}; + pto->m_tx_relay->nNextInvSend = m_connman.PoissonNextSendInbound(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, std::chrono::seconds{INVENTORY_BROADCAST_INTERVAL >> 1}); + pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } @@ -4757,20 +4555,20 @@ bool PeerManagerImpl::SendMessages(CNode* pto) nRelayedTransactions++; { // Expire old relay messages - while (!vRelayExpiration.empty() && vRelayExpiration.front().first < count_microseconds(current_time)) + while (!g_relay_expiration.empty() && g_relay_expiration.front().first < current_time) { - mapRelay.erase(vRelayExpiration.front().second); - vRelayExpiration.pop_front(); + mapRelay.erase(g_relay_expiration.front().second); + g_relay_expiration.pop_front(); } auto ret = mapRelay.emplace(txid, std::move(txinfo.tx)); if (ret.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret.first); + g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret.first); } // Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid auto ret2 = mapRelay.emplace(wtxid, ret.first->second); if (ret2.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret2.first); + g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret2.first); } } if (vInv.size() == MAX_INV_SZ) { @@ -4795,7 +4593,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Detect whether we're stalling current_time = GetTime<std::chrono::microseconds>(); - if (state.nStallingSince && state.nStallingSince < count_microseconds(current_time) - 1000000 * BLOCK_STALLING_TIMEOUT) { + if (state.m_stalling_since.count() && state.m_stalling_since < current_time - BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, // the download window should be much larger than the to-be-downloaded set of blocks, so disconnection // should only happen during initial block download. @@ -4803,7 +4601,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) pto->fDisconnect = true; return true; } - // In case there is a block that has been in flight from this peer for 2 + 0.5 * N times the block interval + // In case there is a block that has been in flight from this peer for block_interval * (1 + 0.5 * N) // (with N the number of peers from which we're downloading validated blocks), disconnect due to timeout. // We compensate for other peers to prevent killing off peers due to our own downstream link // being saturated. We only count validated in-flight blocks so peers can't advertise non-existing block hashes @@ -4811,17 +4609,17 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0); - if (count_microseconds(current_time) > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { + if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.nPowTargetSpacing} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->GetId()); pto->fDisconnect = true; return true; } } // Check for headers sync timeouts - if (state.fSyncStarted && state.nHeadersSyncTimeout < std::numeric_limits<int64_t>::max()) { + if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) { // Detect whether this is a stalling initial-headers-sync peer if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { - if (count_microseconds(current_time) > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { + if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { // Disconnect a peer (without the noban permission) if it is our only sync peer, // and we have others we could be using instead. // Note: If all our peers are inbound, then we won't @@ -4840,13 +4638,13 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // this peer (eventually). state.fSyncStarted = false; nSyncStarted--; - state.nHeadersSyncTimeout = 0; + state.m_headers_sync_timeout = 0us; } } } else { // After we've caught up once, reset the timeout so we can't trigger // disconnect later. - state.nHeadersSyncTimeout = std::numeric_limits<int64_t>::max(); + state.m_headers_sync_timeout = std::chrono::microseconds::max(); } } @@ -4858,7 +4656,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Message: getdata (blocks) // std::vector<CInv> vGetData; - if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !::ChainstateActive().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { std::vector<const CBlockIndex*> vToDownload; NodeId staller = -1; FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller); @@ -4870,8 +4668,8 @@ bool PeerManagerImpl::SendMessages(CNode* pto) pindex->nHeight, pto->GetId()); } if (state.nBlocksInFlight == 0 && staller != -1) { - if (State(staller)->nStallingSince == 0) { - State(staller)->nStallingSince = count_microseconds(current_time); + if (State(staller)->m_stalling_since == 0us) { + State(staller)->m_stalling_since = current_time; LogPrint(BCLog::NET, "Stall started peer=%d\n", staller); } } @@ -4924,10 +4722,10 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->m_tx_relay->lastSentFeeFilter == MAX_FILTER) { // Send the current filter if we sent MAX_FILTER previously // and made it out of IBD. - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) - 1; + pto->m_tx_relay->m_next_send_feefilter = 0us; } } - if (count_microseconds(current_time) > pto->m_tx_relay->nextSendTimeFeeFilter) { + if (current_time > pto->m_tx_relay->m_next_send_feefilter) { CAmount filterToSend = g_filter_rounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); @@ -4935,28 +4733,15 @@ bool PeerManagerImpl::SendMessages(CNode* pto) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); pto->m_tx_relay->lastSentFeeFilter = filterToSend; } - pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(count_microseconds(current_time), AVG_FEEFILTER_BROADCAST_INTERVAL); + pto->m_tx_relay->m_next_send_feefilter = PoissonNextSend(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (count_microseconds(current_time) + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter && + else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < pto->m_tx_relay->m_next_send_feefilter && (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) { - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; + pto->m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); } } } // release cs_main return true; } - -class CNetProcessingCleanup -{ -public: - CNetProcessingCleanup() {} - ~CNetProcessingCleanup() { - // orphan transactions - mapOrphanTransactions.clear(); - mapOrphanTransactionsByPrev.clear(); - g_orphans_by_wtxid.clear(); - } -}; -static CNetProcessingCleanup instance_of_cnetprocessingcleanup; diff --git a/src/net_processing.h b/src/net_processing.h index d7be453df5..6f900410a7 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -15,7 +15,6 @@ class CTxMemPool; class ChainstateManager; extern RecursiveMutex cs_main; -extern RecursiveMutex g_cs_orphans; /** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; @@ -30,7 +29,7 @@ struct CNodeStateStats { int nSyncHeight = -1; int nCommonHeight = -1; int m_starting_height = -1; - int64_t m_ping_wait_usec; + std::chrono::microseconds m_ping_wait; std::vector<int> vHeightInFlight; }; diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 85e46fd373..69edc15c66 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -221,25 +221,34 @@ static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKS }; // namespace torv3 -/** - * Parse a TOR address and set this object to it. - * - * @returns Whether or not the operation was successful. - * - * @see CNetAddr::IsTor() - */ -bool CNetAddr::SetSpecial(const std::string& str) +bool CNetAddr::SetSpecial(const std::string& addr) +{ + if (!ValidAsCString(addr)) { + return false; + } + + if (SetTor(addr)) { + return true; + } + + if (SetI2P(addr)) { + return true; + } + + return false; +} + +bool CNetAddr::SetTor(const std::string& addr) { static const char* suffix{".onion"}; static constexpr size_t suffix_len{6}; - if (!ValidAsCString(str) || str.size() <= suffix_len || - str.substr(str.size() - suffix_len) != suffix) { + if (addr.size() <= suffix_len || addr.substr(addr.size() - suffix_len) != suffix) { return false; } bool invalid; - const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid); + const auto& input = DecodeBase32(addr.substr(0, addr.size() - suffix_len).c_str(), &invalid); if (invalid) { return false; @@ -275,6 +284,34 @@ bool CNetAddr::SetSpecial(const std::string& str) return false; } +bool CNetAddr::SetI2P(const std::string& addr) +{ + // I2P addresses that we support consist of 52 base32 characters + ".b32.i2p". + static constexpr size_t b32_len{52}; + static const char* suffix{".b32.i2p"}; + static constexpr size_t suffix_len{8}; + + if (addr.size() != b32_len + suffix_len || ToLower(addr.substr(b32_len)) != suffix) { + return false; + } + + // Remove the ".b32.i2p" suffix and pad to a multiple of 8 chars, so DecodeBase32() + // can decode it. + const std::string b32_padded = addr.substr(0, b32_len) + "===="; + + bool invalid; + const auto& address_bytes = DecodeBase32(b32_padded.c_str(), &invalid); + + if (invalid || address_bytes.size() != ADDR_I2P_SIZE) { + return false; + } + + m_net = NET_I2P; + m_addr.assign(address_bytes.begin(), address_bytes.end()); + + return true; +} + CNetAddr::CNetAddr(const struct in_addr& ipv4Addr) { m_net = NET_IPV4; @@ -841,6 +878,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well case NET_ONION: return REACH_PRIVATE; } + case NET_I2P: + switch (ourNet) { + case NET_I2P: return REACH_PRIVATE; + default: return REACH_DEFAULT; + } case NET_TEREDO: switch(ourNet) { default: return REACH_DEFAULT; diff --git a/src/netaddress.h b/src/netaddress.h index d0986557f7..897ce46cda 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -151,7 +151,16 @@ class CNetAddr bool SetInternal(const std::string& name); - bool SetSpecial(const std::string &strName); // for Tor addresses + /** + * Parse a Tor or I2P address and set this object to it. + * @param[in] addr Address to parse, for example + * pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion or + * ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p. + * @returns Whether the operation was successful. + * @see CNetAddr::IsTor(), CNetAddr::IsI2P() + */ + bool SetSpecial(const std::string& addr); + bool IsBindAny() const; // INADDR_ANY equivalent bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0) bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor) @@ -249,6 +258,25 @@ class CNetAddr private: /** + * Parse a Tor address and set this object to it. + * @param[in] addr Address to parse, must be a valid C string, for example + * pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion or + * 6hzph5hv6337r6p2.onion. + * @returns Whether the operation was successful. + * @see CNetAddr::IsTor() + */ + bool SetTor(const std::string& addr); + + /** + * Parse an I2P address and set this object to it. + * @param[in] addr Address to parse, must be a valid C string, for example + * ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p. + * @returns Whether the operation was successful. + * @see CNetAddr::IsI2P() + */ + bool SetI2P(const std::string& addr); + + /** * BIP155 network ids recognized by this software. */ enum BIP155Network : uint8_t { diff --git a/src/netbase.cpp b/src/netbase.cpp index 0c5b3a220e..88c36ed86c 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -5,6 +5,7 @@ #include <netbase.h> +#include <compat.h> #include <sync.h> #include <tinyformat.h> #include <util/sock.h> @@ -14,6 +15,7 @@ #include <util/time.h> #include <atomic> +#include <chrono> #include <cstdint> #include <functional> #include <limits> @@ -29,10 +31,6 @@ #include <poll.h> #endif -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL 0 -#endif - // Settings static Mutex g_proxyinfo_mutex; static proxyType proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); @@ -41,7 +39,7 @@ int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; // Need ample time for negotiation for very slow proxies such as Tor (milliseconds) -static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; +int g_socks5_recv_timeout = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); enum Network ParseNetwork(const std::string& net_in) { @@ -53,6 +51,9 @@ enum Network ParseNetwork(const std::string& net_in) { LogPrintf("Warning: net name 'tor' is deprecated and will be removed in the future. You should use 'onion' instead.\n"); return NET_ONION; } + if (net == "i2p") { + return NET_I2P; + } return NET_UNROUTABLE; } @@ -77,7 +78,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable) std::vector<std::string> names; for (int n = 0; n < NET_MAX; ++n) { const enum Network network{static_cast<Network>(n)}; - if (network == NET_UNROUTABLE || network == NET_I2P || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; names.emplace_back(GetNetworkName(network)); } if (append_unroutable) { @@ -360,9 +361,6 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; - // Maximum time to wait for I/O readiness. It will take up until this time - // (in millis) to break off in case of an interruption. - const int64_t maxWait = 1000; while (len > 0 && curTime < endTime) { ssize_t ret = sock.Recv(data, len, 0); // Optimistically try the recv first if (ret > 0) { @@ -373,10 +371,11 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c } else { // Other error or blocking int nErr = WSAGetLastError(); if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { - // Only wait at most maxWait milliseconds at a time, unless + // Only wait at most MAX_WAIT_FOR_IO at a time, unless // we're approaching the end of the specified total timeout - int timeout_ms = std::min(endTime - curTime, maxWait); - if (!sock.Wait(std::chrono::milliseconds{timeout_ms}, Sock::RECV)) { + const auto remaining = std::chrono::milliseconds{endTime - curTime}; + const auto timeout = std::min(remaining, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + if (!sock.Wait(timeout, Sock::RECV)) { return IntrRecvError::NetworkError; } } else { @@ -390,13 +389,6 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; } -/** Credentials for proxy authentication */ -struct ProxyCredentials -{ - std::string username; - std::string password; -}; - /** Convert SOCKS5 reply to an error message */ static std::string Socks5ErrorString(uint8_t err) { @@ -440,7 +432,7 @@ static std::string Socks5ErrorString(uint8_t err) * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol * Version 5</a> */ -static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock) +bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock) { IntrRecvError recvr; LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); @@ -463,7 +455,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* return error("Error sending to proxy"); } uint8_t pchRet1[2]; - if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port); return false; } @@ -486,7 +478,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* } LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; - if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { return error("Error reading proxy authentication response"); } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { @@ -511,7 +503,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* return error("Error sending to proxy"); } uint8_t pchRet2[4]; - if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { if (recvr == IntrRecvError::Timeout) { /* If a timeout happens here, this effectively means we timed out while connecting * to the remote node. This is very common for Tor, so do not print an @@ -535,16 +527,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* uint8_t pchRet3[256]; switch (pchRet2[3]) { - case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, sock); break; - case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, sock); break; + case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break; + case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break; case SOCKS5Atyp::DOMAINNAME: { - recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, sock); + recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock); if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } int nRecv = pchRet3[0]; - recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, sock); + recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock); break; } default: return error("Error: malformed proxy response"); @@ -552,7 +544,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } - if ((recvr = InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { return error("Error reading from proxy"); } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); diff --git a/src/netbase.h b/src/netbase.h index 847a72ca8e..751f7eb3f0 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -18,6 +18,7 @@ #include <memory> #include <stdint.h> #include <string> +#include <type_traits> #include <vector> extern int nConnectTimeout; @@ -28,6 +29,22 @@ static const int DEFAULT_CONNECT_TIMEOUT = 5000; //! -dns default static const int DEFAULT_NAME_LOOKUP = true; +enum class ConnectionDirection { + None = 0, + In = (1U << 0), + Out = (1U << 1), + Both = (In | Out), +}; +static inline ConnectionDirection& operator|=(ConnectionDirection& a, ConnectionDirection b) { + using underlying = typename std::underlying_type<ConnectionDirection>::type; + a = ConnectionDirection(underlying(a) | underlying(b)); + return a; +} +static inline bool operator&(ConnectionDirection a, ConnectionDirection b) { + using underlying = typename std::underlying_type<ConnectionDirection>::type; + return (underlying(a) & underlying(b)); +} + class proxyType { public: @@ -40,6 +57,13 @@ public: bool randomize_credentials; }; +/** Credentials for proxy authentication */ +struct ProxyCredentials +{ + std::string username; + std::string password; +}; + enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); /** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */ @@ -77,4 +101,6 @@ bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking); bool SetSocketNoDelay(const SOCKET& hSocket); void InterruptSocks5(bool interrupt); +bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& socket); + #endif // BITCOIN_NETBASE_H diff --git a/src/node/coin.cpp b/src/node/coin.cpp index f4f86cdbe9..263dcff657 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -12,7 +12,8 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) { assert(node.mempool); LOCK2(cs_main, node.mempool->cs); - CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip(); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); + CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { if (!mempool_view.GetCoin(coin.first, coin.second)) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 06fcc33725..88bdba5953 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -83,7 +83,7 @@ static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, con //! Calculate statistics about the unspent transaction output set template <typename T> -static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) +static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) { stats = CCoinsStats(); std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); @@ -92,7 +92,8 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); - stats.nHeight = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock)->nHeight; + assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); + stats.nHeight = blockman.LookupBlockIndex(stats.hashBlock)->nHeight; } PrepareHash(hash_obj, stats); @@ -126,19 +127,19 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const return true; } -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) { switch (hash_type) { case(CoinStatsHashType::HASH_SERIALIZED): { CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, stats, ss, interruption_point); + return GetUTXOStats(view, blockman, stats, ss, interruption_point); } case(CoinStatsHashType::MUHASH): { MuHash3072 muhash; - return GetUTXOStats(view, stats, muhash, interruption_point); + return GetUTXOStats(view, blockman, stats, muhash, interruption_point); } case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, stats, nullptr, interruption_point); + return GetUTXOStats(view, blockman, stats, nullptr, interruption_point); } } // no default case, so the compiler can warn about missing cases assert(false); diff --git a/src/node/coinstats.h b/src/node/coinstats.h index f02b95235f..83f228aa7e 100644 --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -8,6 +8,7 @@ #include <amount.h> #include <uint256.h> +#include <validation.h> #include <cstdint> #include <functional> @@ -36,6 +37,6 @@ struct CCoinsStats }; //! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); #endif // BITCOIN_NODE_COINSTATS_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index ec976fe9bf..9a6cee6433 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -96,7 +96,7 @@ public: bool shutdownRequested() override { return ShutdownRequested(); } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } - size_t getNodeCount(CConnman::NumConnections flags) override + size_t getNodeCount(ConnectionDirection flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; } @@ -182,18 +182,21 @@ public: int getNumBlocks() override { LOCK(::cs_main); - return ::ChainActive().Height(); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + return m_context->chainman->ActiveChain().Height(); } uint256 getBestBlockHash() override { - const CBlockIndex* tip = WITH_LOCK(::cs_main, return ::ChainActive().Tip()); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + const CBlockIndex* tip = WITH_LOCK(::cs_main, return m_context->chainman->ActiveChain().Tip()); return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); - if (::ChainActive().Tip()) { - return ::ChainActive().Tip()->GetBlockTime(); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + if (m_context->chainman->ActiveChain().Tip()) { + return m_context->chainman->ActiveChain().Tip()->GetBlockTime(); } return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network } @@ -202,11 +205,15 @@ public: const CBlockIndex* tip; { LOCK(::cs_main); - tip = ::ChainActive().Tip(); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + tip = m_context->chainman->ActiveChain().Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } - bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isInitialBlockDownload() override { + assert(std::addressof(::ChainstateActive()) == std::addressof(m_context->chainman->ActiveChainstate())); + return m_context->chainman->ActiveChainstate().IsInitialBlockDownload(); + } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override @@ -231,7 +238,8 @@ public: bool getUnspentOutput(const COutPoint& output, Coin& coin) override { LOCK(::cs_main); - return ::ChainstateActive().CoinsTip().GetCoin(output, coin); + assert(std::addressof(::ChainstateActive()) == std::addressof(m_context->chainman->ActiveChainstate())); + return m_context->chainman->ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient& walletClient() override { @@ -441,13 +449,15 @@ public: bool checkFinalTx(const CTransaction& tx) override { LOCK(cs_main); - return CheckFinalTx(::ChainActive().Tip(), tx); + assert(std::addressof(::ChainActive()) == std::addressof(m_node.chainman->ActiveChain())); + return CheckFinalTx(m_node.chainman->ActiveChain().Tip(), tx); } Optional<int> findLocatorFork(const CBlockLocator& locator) override { LOCK(cs_main); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (CBlockIndex* fork = g_chainman.m_blockman.FindForkInGlobalIndex(active, locator)) { + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) { return fork->nHeight; } return nullopt; @@ -456,7 +466,8 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - return FillBlock(g_chainman.m_blockman.LookupBlockIndex(hash), block, lock, active); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override { @@ -468,7 +479,8 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (const CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash)) { + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); } @@ -479,8 +491,10 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash); - const CBlockIndex* ancestor = g_chainman.m_blockman.LookupBlockIndex(ancestor_hash); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; return FillBlock(ancestor, ancestor_out, lock, active); } @@ -488,8 +502,10 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block1 = g_chainman.m_blockman.LookupBlockIndex(block_hash1); - const CBlockIndex* block2 = g_chainman.m_blockman.LookupBlockIndex(block_hash2); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving // output uninitialized. @@ -499,7 +515,8 @@ public: double guessVerificationProgress(const uint256& block_hash) override { LOCK(cs_main); - return GuessVerificationProgress(Params().TxData(), g_chainman.m_blockman.LookupBlockIndex(block_hash)); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + return GuessVerificationProgress(Params().TxData(), m_node.chainman->m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const uint256& block_hash, int min_height, Optional<int> max_height) override { @@ -511,7 +528,8 @@ public: // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); - if (CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash)) { + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { // Check pprev to not segfault if min_height is too low @@ -526,6 +544,12 @@ public: LOCK(m_node.mempool->cs); return IsRBFOptIn(tx, *m_node.mempool); } + bool isInMempool(const uint256& txid) override + { + if (!m_node.mempool) return false; + LOCK(m_node.mempool->cs); + return m_node.mempool->exists(txid); + } bool hasDescendantsInMempool(const uint256& txid) override { if (!m_node.mempool) return false; @@ -595,7 +619,10 @@ public: return ::fHavePruned; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } - bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isInitialBlockDownload() override { + assert(std::addressof(::ChainstateActive()) == std::addressof(m_node.chainman->ActiveChainstate())); + return m_node.chainman->ActiveChainstate().IsInitialBlockDownload(); + } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string& message) override { ::uiInterface.InitMessage(message); } diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 3b3fab7b6b..97763f4fa2 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -39,9 +39,10 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t { // cs_main scope LOCK(cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); // If the transaction is already confirmed in the chain, don't do anything // and return early. - CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); + CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip(); for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. @@ -53,7 +54,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t if (max_tx_fee > 0) { // First, call ATMP with test_accept and check the fee. If ATMP // fails here, return error immediately. - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */, + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, true /* test_accept */); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { return HandleATMPError(result.m_state, err_string); @@ -62,7 +63,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t } } // Try to submit the transaction to the mempool. - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */, + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, false /* test_accept */); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { return HandleATMPError(result.m_state, err_string); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index bb444f22b3..ee2462ed74 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -198,42 +198,38 @@ QVariant AddressTableModel::data(const QModelIndex &index, int role) const AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer()); - if(role == Qt::DisplayRole || role == Qt::EditRole) - { - switch(index.column()) - { + const auto column = static_cast<ColumnIndex>(index.column()); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (column) { case Label: - if(rec->label.isEmpty() && role == Qt::DisplayRole) - { + if (rec->label.isEmpty() && role == Qt::DisplayRole) { return tr("(no label)"); - } - else - { + } else { return rec->label; } case Address: return rec->address; - } - } - else if (role == Qt::FontRole) - { - QFont font; - if(index.column() == Address) - { - font = GUIUtil::fixedPitchFont(); - } - return font; - } - else if (role == TypeRole) - { + } // no default case, so the compiler can warn about missing cases + assert(false); + } else if (role == Qt::FontRole) { + switch (column) { + case Label: + return QFont(); + case Address: + return GUIUtil::fixedPitchFont(); + } // no default case, so the compiler can warn about missing cases + assert(false); + } else if (role == TypeRole) { switch(rec->type) { case AddressTableEntry::Sending: return Send; case AddressTableEntry::Receiving: return Receive; - default: break; - } + case AddressTableEntry::Hidden: + return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); } return QVariant(); } diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index a01a7bc386..6cb4a4c546 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -23,15 +23,13 @@ bool BannedNodeLessThan::operator()(const CCombinedBan& left, const CCombinedBan if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); - switch(column) - { + switch (static_cast<BanTableModel::ColumnIndex>(column)) { case BanTableModel::Address: return pLeft->subnet.ToString().compare(pRight->subnet.ToString()) < 0; case BanTableModel::Bantime: return pLeft->banEntry.nBanUntil < pRight->banEntry.nBanUntil; - } - - return false; + } // no default case, so the compiler can warn about missing cases + assert(false); } // private implementation @@ -119,16 +117,17 @@ QVariant BanTableModel::data(const QModelIndex &index, int role) const CCombinedBan *rec = static_cast<CCombinedBan*>(index.internalPointer()); + const auto column = static_cast<ColumnIndex>(index.column()); if (role == Qt::DisplayRole) { - switch(index.column()) - { + switch (column) { case Address: return QString::fromStdString(rec->subnet.ToString()); case Bantime: QDateTime date = QDateTime::fromMSecsSinceEpoch(0); date = date.addSecs(rec->banEntry.nBanUntil); return QLocale::system().toString(date, QLocale::LongFormat); - } + } // no default case, so the compiler can warn about missing cases + assert(false); } return QVariant(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index bad74fcbcc..f25b415820 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -62,6 +62,7 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +Q_IMPORT_PLUGIN(QMacStylePlugin); #endif #endif @@ -464,9 +465,7 @@ int GuiMain(int argc, char* argv[]) // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#if QT_VERSION >= 0x050600 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif #if (QT_VERSION <= QT_VERSION_CHECK(5, 9, 8)) && defined(Q_OS_MACOS) const auto os_name = QSysInfo::prettyProductName(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index ccc0a6828c..6677c9e3b5 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -88,6 +88,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } + setContextMenuPolicy(Qt::PreventContextMenu); + #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET @@ -544,7 +546,6 @@ void BitcoinGUI::createToolBars() { QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); appToolBar = toolbar; - toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 56813b2d19..b244bc94f2 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -71,14 +71,14 @@ ClientModel::~ClientModel() int ClientModel::getNumConnections(unsigned int flags) const { - CConnman::NumConnections connections = CConnman::CONNECTIONS_NONE; + ConnectionDirection connections = ConnectionDirection::None; if(flags == CONNECTIONS_IN) - connections = CConnman::CONNECTIONS_IN; + connections = ConnectionDirection::In; else if (flags == CONNECTIONS_OUT) - connections = CConnman::CONNECTIONS_OUT; + connections = ConnectionDirection::Out; else if (flags == CONNECTIONS_ALL) - connections = CConnman::CONNECTIONS_ALL; + connections = ConnectionDirection::Both; return m_node.getNodeCount(connections); } diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 9e828ce0a6..e45cafe48a 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1079,7 +1079,7 @@ <item row="1" column="0"> <widget class="QLabel" name="peerConnectionTypeLabel"> <property name="toolTip"> - <string>The direction and type of peer connection: %1</string> + <string>The direction and type of peer connection: %1</string> </property> <property name="text"> <string>Direction/Type</string> @@ -1342,13 +1342,65 @@ </widget> </item> <item row="12" column="0"> + <widget class="QLabel" name="peerLastBlockLabel"> + <property name="toolTip"> + <string>Elapsed time since a novel block passing initial validity checks was received from this peer.</string> + </property> + <property name="text"> + <string>Last Block</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QLabel" name="peerLastBlock"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="peerLastTxLabel"> + <property name="toolTip"> + <string>Elapsed time since a novel transaction accepted into our mempool was received from this peer.</string> + </property> + <property name="text"> + <string>Last Tx</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QLabel" name="peerLastTx"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="14" column="0"> <widget class="QLabel" name="label_15"> <property name="text"> <string>Last Send</string> </property> </widget> </item> - <item row="12" column="1"> + <item row="14" column="1"> <widget class="QLabel" name="peerLastSend"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1364,14 +1416,14 @@ </property> </widget> </item> - <item row="13" column="0"> + <item row="15" column="0"> <widget class="QLabel" name="label_19"> <property name="text"> <string>Last Receive</string> </property> </widget> </item> - <item row="13" column="1"> + <item row="15" column="1"> <widget class="QLabel" name="peerLastRecv"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1387,14 +1439,14 @@ </property> </widget> </item> - <item row="14" column="0"> + <item row="16" column="0"> <widget class="QLabel" name="label_18"> <property name="text"> <string>Sent</string> </property> </widget> </item> - <item row="14" column="1"> + <item row="16" column="1"> <widget class="QLabel" name="peerBytesSent"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1410,14 +1462,14 @@ </property> </widget> </item> - <item row="15" column="0"> + <item row="17" column="0"> <widget class="QLabel" name="label_20"> <property name="text"> <string>Received</string> </property> </widget> </item> - <item row="15" column="1"> + <item row="17" column="1"> <widget class="QLabel" name="peerBytesRecv"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1433,14 +1485,14 @@ </property> </widget> </item> - <item row="16" column="0"> + <item row="18" column="0"> <widget class="QLabel" name="label_26"> <property name="text"> <string>Ping Time</string> </property> </widget> </item> - <item row="16" column="1"> + <item row="18" column="1"> <widget class="QLabel" name="peerPingTime"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1456,7 +1508,7 @@ </property> </widget> </item> - <item row="17" column="0"> + <item row="19" column="0"> <widget class="QLabel" name="peerPingWaitLabel"> <property name="toolTip"> <string>The duration of a currently outstanding ping.</string> @@ -1466,7 +1518,7 @@ </property> </widget> </item> - <item row="17" column="1"> + <item row="19" column="1"> <widget class="QLabel" name="peerPingWait"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1482,14 +1534,14 @@ </property> </widget> </item> - <item row="18" column="0"> + <item row="20" column="0"> <widget class="QLabel" name="peerMinPingLabel"> <property name="text"> <string>Min Ping</string> </property> </widget> </item> - <item row="18" column="1"> + <item row="20" column="1"> <widget class="QLabel" name="peerMinPing"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1505,14 +1557,14 @@ </property> </widget> </item> - <item row="19" column="0"> + <item row="21" column="0"> <widget class="QLabel" name="label_timeoffset"> <property name="text"> <string>Time Offset</string> </property> </widget> </item> - <item row="19" column="1"> + <item row="21" column="1"> <widget class="QLabel" name="timeoffset"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1528,7 +1580,7 @@ </property> </widget> </item> - <item row="20" column="0"> + <item row="22" column="0"> <widget class="QLabel" name="peerMappedASLabel"> <property name="toolTip"> <string>The mapped Autonomous System used for diversifying peer selection.</string> @@ -1538,7 +1590,7 @@ </property> </widget> </item> - <item row="20" column="1"> + <item row="22" column="1"> <widget class="QLabel" name="peerMappedAS"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -1554,7 +1606,7 @@ </property> </widget> </item> - <item row="21" column="0"> + <item row="23" column="0"> <spacer name="verticalSpacer_3"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index b82143e1ba..f85de0811a 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -68,7 +68,7 @@ </property> <property name="maximumSize"> <size> - <width>30</width> + <width>45</width> <height>16777215</height> </size> </property> @@ -89,9 +89,6 @@ <height>24</height> </size> </property> - <property name="flat"> - <bool>true</bool> - </property> </widget> </item> <item> @@ -406,7 +403,7 @@ </property> <property name="maximumSize"> <size> - <width>30</width> + <width>45</width> <height>16777215</height> </size> </property> @@ -427,9 +424,6 @@ <height>24</height> </size> </property> - <property name="flat"> - <bool>true</bool> - </property> </widget> </item> <item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index c70bd9f418..89d6deb70d 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -59,6 +59,8 @@ #include <QUrlQuery> #include <QtGlobal> +#include <chrono> + #if defined(Q_OS_MAC) #include <QProcess> @@ -706,9 +708,11 @@ QString formatServicesStr(quint64 mask) return QObject::tr("None"); } -QString formatPingTime(int64_t ping_usec) +QString formatPingTime(std::chrono::microseconds ping_time) { - return (ping_usec == std::numeric_limits<int64_t>::max() || ping_usec == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")).arg(QString::number((int)(ping_usec / 1000), 10)); + return (ping_time == std::chrono::microseconds::max() || ping_time == 0us) ? + QObject::tr("N/A") : + QString(QObject::tr("%1 ms")).arg(QString::number((int)(count_microseconds(ping_time) / 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 7984aa1141..6395ec6abd 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -20,6 +20,8 @@ #include <QString> #include <QTableView> +#include <chrono> + class QValidatedLineEdit; class SendCoinsRecipient; @@ -202,8 +204,8 @@ namespace GUIUtil /** Format CNodeStats.nServices bitmask into a user-readable string */ QString formatServicesStr(quint64 mask); - /** Format a CNodeStats.m_ping_usec into a user-readable string or display N/A, if 0 */ - QString formatPingTime(int64_t ping_usec); + /** 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); diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 5f518a67cd..3459bf4cf8 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -23,8 +23,7 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); - switch(column) - { + switch (static_cast<PeerTableModel::ColumnIndex>(column)) { case PeerTableModel::NetNodeId: return pLeft->nodeid < pRight->nodeid; case PeerTableModel::Address: @@ -34,16 +33,15 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine case PeerTableModel::Network: return pLeft->m_network < pRight->m_network; case PeerTableModel::Ping: - return pLeft->m_min_ping_usec < pRight->m_min_ping_usec; + return pLeft->m_min_ping_time < pRight->m_min_ping_time; case PeerTableModel::Sent: return pLeft->nSendBytes < pRight->nSendBytes; case PeerTableModel::Received: return pLeft->nRecvBytes < pRight->nRecvBytes; case PeerTableModel::Subversion: return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; - } - - return false; + } // no default case, so the compiler can warn about missing cases + assert(false); } // private implementation @@ -157,9 +155,9 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const CNodeCombinedStats *rec = static_cast<CNodeCombinedStats*>(index.internalPointer()); + const auto column = static_cast<ColumnIndex>(index.column()); if (role == Qt::DisplayRole) { - switch(index.column()) - { + switch (column) { case NetNodeId: return (qint64)rec->nodeStats.nodeid; case Address: @@ -170,26 +168,31 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const case Network: return GUIUtil::NetworkToQString(rec->nodeStats.m_network); case Ping: - return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_usec); + return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_time); case Sent: return GUIUtil::formatBytes(rec->nodeStats.nSendBytes); case Received: return GUIUtil::formatBytes(rec->nodeStats.nRecvBytes); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); - } + } // no default case, so the compiler can warn about missing cases + assert(false); } else if (role == Qt::TextAlignmentRole) { - switch (index.column()) { - case ConnectionType: - case Network: - return QVariant(Qt::AlignCenter); - case Ping: - case Sent: - case Received: - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: - return QVariant(); - } + switch (column) { + case NetNodeId: + case Address: + return {}; + case ConnectionType: + case Network: + return QVariant(Qt::AlignCenter); + case Ping: + case Sent: + case Received: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + case Subversion: + return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); } else if (role == StatsRole) { switch (index.column()) { case NetNodeId: return QVariant::fromValue(rec); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 62adaa4e9f..61cb89d75a 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -45,9 +45,9 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid // context menu actions QAction *copyURIAction = new QAction(tr("Copy URI"), this); QAction* copyAddressAction = new QAction(tr("Copy address"), this); - QAction *copyLabelAction = new QAction(tr("Copy label"), this); - QAction *copyMessageAction = new QAction(tr("Copy message"), this); - QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + copyLabelAction = new QAction(tr("Copy label"), this); + copyMessageAction = new QAction(tr("Copy message"), this); + copyAmountAction = new QAction(tr("Copy amount"), this); // context menu contextMenu = new QMenu(this); @@ -81,7 +81,6 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid tableView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH); tableView->horizontalHeader()->setStretchLastSection(true); } - tableView->horizontalHeader()->setSortIndicator(RecentRequestsTableModel::Date, Qt::DescendingOrder); } void ReceiveCoinsDialog::setModel(WalletModel *_model) @@ -96,6 +95,8 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) QTableView* tableView = ui->recentRequestsView; tableView->setModel(_model->getRecentRequestsTableModel()); + tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder); + connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ReceiveCoinsDialog::recentRequestsView_selectionChanged); @@ -269,9 +270,18 @@ void ReceiveCoinsDialog::copyColumnToClipboard(int column) // context menu void ReceiveCoinsDialog::showMenu(const QPoint &point) { - if (!selectedRow().isValid()) { + const QModelIndex sel = selectedRow(); + if (!sel.isValid()) { return; } + + // disable context menu actions when appropriate + const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel(); + const RecentRequestEntry& req = submodel->entry(sel.row()); + copyLabelAction->setDisabled(req.recipient.label.isEmpty()); + copyMessageAction->setDisabled(req.recipient.message.isEmpty()); + copyAmountAction->setDisabled(req.recipient.amount == 0); + contextMenu->exec(QCursor::pos()); } diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index f12cd8ce0c..fbbccc5a33 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -53,6 +53,9 @@ private: Ui::ReceiveCoinsDialog *ui; WalletModel *model; QMenu *contextMenu; + QAction* copyLabelAction; + QAction* copyMessageAction; + QAction* copyAmountAction; const PlatformStyle *platformStyle; QModelIndex selectedRow(); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 4a4b557acc..b258219894 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -474,9 +474,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty const QString list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"}; ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(list)); const QString hb_list{"<ul><li>\"" - + tr("To") + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\"" - + tr("From") + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\"" - + tr("No") + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"}; + + ts.to + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\"" + + ts.from + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\"" + + ts.no + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"}; ui->peerHighBandwidthLabel->setToolTip(ui->peerHighBandwidthLabel->toolTip().arg(hb_list)); ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir")); ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir")); @@ -619,10 +619,10 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create peer table context menu actions QAction* disconnectAction = new QAction(tr("&Disconnect"), this); - QAction* banAction1h = new QAction(tr("Ban for") + " " + tr("1 &hour"), this); - QAction* banAction24h = new QAction(tr("Ban for") + " " + tr("1 &day"), this); - QAction* banAction7d = new QAction(tr("Ban for") + " " + tr("1 &week"), this); - QAction* banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this); + QAction* banAction1h = new QAction(ts.ban_for + " " + tr("1 &hour"), this); + QAction* banAction24h = new QAction(ts.ban_for + " " + tr("1 &day"), this); + QAction* banAction7d = new QAction(ts.ban_for + " " + tr("1 &week"), this); + QAction* banAction365d = new QAction(ts.ban_for + " " + tr("1 &year"), this); // create peer table context menu peersTableContextMenu = new QMenu(this); @@ -1114,26 +1114,29 @@ void RPCConsole::updateDetailWidget() peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal)); ui->peerHeading->setText(peerAddrDetails); ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices)); - ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? "Yes" : "No"); + ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? ts.yes : ts.no); QString bip152_hb_settings; - if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings += "To"; - if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings == "" ? "From" : "/From"); - if (bip152_hb_settings == "") bip152_hb_settings = "No"; + if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings = ts.to; + if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings.isEmpty() ? ts.from : QLatin1Char('/') + ts.from); + if (bip152_hb_settings.isEmpty()) bip152_hb_settings = ts.no; ui->peerHighBandwidth->setText(bip152_hb_settings); - ui->peerLastSend->setText(stats->nodeStats.nLastSend ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastSend) : tr("never")); - ui->peerLastRecv->setText(stats->nodeStats.nLastRecv ? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nLastRecv) : tr("never")); + const int64_t time_now{GetSystemTimeInSeconds()}; + ui->peerConnTime->setText(GUIUtil::formatDurationStr(time_now - stats->nodeStats.nTimeConnected)); + ui->peerLastBlock->setText(TimeDurationField(time_now, stats->nodeStats.nLastBlockTime)); + ui->peerLastTx->setText(TimeDurationField(time_now, stats->nodeStats.nLastTXTime)); + ui->peerLastSend->setText(TimeDurationField(time_now, stats->nodeStats.nLastSend)); + ui->peerLastRecv->setText(TimeDurationField(time_now, stats->nodeStats.nLastRecv)); ui->peerBytesSent->setText(GUIUtil::formatBytes(stats->nodeStats.nSendBytes)); ui->peerBytesRecv->setText(GUIUtil::formatBytes(stats->nodeStats.nRecvBytes)); - ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected)); - ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_ping_usec)); - ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_usec)); + ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time)); + ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time)); ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true)); ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network)); if (stats->nodeStats.m_permissionFlags == PF_NONE) { - ui->peerPermissions->setText(tr("N/A")); + ui->peerPermissions->setText(ts.na); } else { QStringList permissions; for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permissionFlags)) { @@ -1141,25 +1144,25 @@ void RPCConsole::updateDetailWidget() } ui->peerPermissions->setText(permissions.join(" & ")); } - ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : tr("N/A")); + ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : ts.na); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { // Sync height is init to -1 - if (stats->nodeStateStats.nSyncHeight > -1) + if (stats->nodeStateStats.nSyncHeight > -1) { ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight)); - else - ui->peerSyncHeight->setText(tr("Unknown")); - + } else { + ui->peerSyncHeight->setText(ts.unknown); + } // Common height is init to -1 - if (stats->nodeStateStats.nCommonHeight > -1) + if (stats->nodeStateStats.nCommonHeight > -1) { ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight)); - else - ui->peerCommonHeight->setText(tr("Unknown")); - + } else { + ui->peerCommonHeight->setText(ts.unknown); + } ui->peerHeight->setText(QString::number(stats->nodeStateStats.m_starting_height)); - ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait_usec)); + ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait)); } ui->peersTabRightPanel->show(); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 5f308dc36d..b9806e40c9 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -136,6 +136,11 @@ Q_SIGNALS: void cmdRequest(const QString &command, const WalletModel* wallet_model); private: + struct TranslatedStrings { + const QString yes{tr("Yes")}, no{tr("No")}, to{tr("To")}, from{tr("From")}, + ban_for{tr("Ban for")}, na{tr("N/A")}, unknown{tr("Unknown")}; + } const ts; + void startExecutor(); void setTrafficGraphRange(int mins); @@ -168,6 +173,11 @@ private: /** Update UI with latest network info from model. */ void updateNetworkState(); + /** Helper for the output of a time duration field. Inputs are UNIX epoch times. */ + QString TimeDurationField(uint64_t time_now, uint64_t time_at_event) const { + return time_at_event ? GUIUtil::formatDurationStr(time_now - time_at_event) : tr("Never"); + } + private Q_SLOTS: void updateAlerts(const QString& warnings); }; diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 9ef5fe8fc7..a1baf6a402 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -54,6 +54,13 @@ int main(int argc, char* argv[]) NodeContext node_context; std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(&node_context); + gArgs.ForceSetArg("-listen", "0"); + gArgs.ForceSetArg("-listenonion", "0"); + gArgs.ForceSetArg("-discover", "0"); + gArgs.ForceSetArg("-dnsseed", "0"); + gArgs.ForceSetArg("-fixedseeds", "0"); + gArgs.ForceSetArg("-upnp", "0"); + gArgs.ForceSetArg("-natpmp", "0"); bool fInvalid = false; diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index e6d483364c..a7556eed04 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -526,27 +526,30 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return QVariant(); TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer()); - switch(role) - { + const auto column = static_cast<ColumnIndex>(index.column()); + switch (role) { case RawDecorationRole: - switch(index.column()) - { + switch (column) { case Status: return txStatusDecoration(rec); case Watchonly: return txWatchonlyDecoration(rec); + case Date: return {}; + case Type: return {}; case ToAddress: return txAddressDecoration(rec); - } - break; + case Amount: return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::DecorationRole: { QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole)); return platformStyle->TextColorIcon(icon); } case Qt::DisplayRole: - switch(index.column()) - { + switch (column) { + case Status: return {}; + case Watchonly: return {}; case Date: return formatTxDate(rec); case Type: @@ -555,12 +558,11 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return formatTxToAddress(rec, false); case Amount: return formatTxAmount(rec, true, BitcoinUnits::SeparatorStyle::ALWAYS); - } - break; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::EditRole: // Edit role is used for sorting, so return the unformatted values - switch(index.column()) - { + switch (column) { case Status: return QString::fromStdString(rec->status.sortKey); case Date: @@ -573,8 +575,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return formatTxToAddress(rec, true); case Amount: return qint64(rec->credit + rec->debit); - } - break; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::ToolTipRole: return formatTooltip(rec); case Qt::TextAlignmentRole: diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index b568f41158..f99a3e286d 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -160,7 +160,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa transactionView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH); transactionView->horizontalHeader()->setStretchLastSection(true); } - transactionView->horizontalHeader()->setSortIndicator(TransactionTableModel::Date, Qt::DescendingOrder); // Actions abandonAction = new QAction(tr("Abandon transaction"), this); @@ -172,6 +171,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this); QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this); + QAction *editLabelAction = new QAction(tr("Edit address label"), this); QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); contextMenu = new QMenu(this); @@ -186,6 +186,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu->addSeparator(); contextMenu->addAction(bumpFeeAction); contextMenu->addAction(abandonAction); + contextMenu->addAction(editLabelAction); connect(dateWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate); connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType); @@ -206,6 +207,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa connect(copyTxIDAction, &QAction::triggered, this, &TransactionView::copyTxID); connect(copyTxHexAction, &QAction::triggered, this, &TransactionView::copyTxHex); connect(copyTxPlainText, &QAction::triggered, this, &TransactionView::copyTxPlainText); + connect(editLabelAction, &QAction::triggered, this, &TransactionView::editLabel); connect(showDetailsAction, &QAction::triggered, this, &TransactionView::showDetails); // Double-clicking on a transaction on the transaction history page shows details connect(this, &TransactionView::doubleClicked, this, &TransactionView::showDetails); @@ -233,6 +235,7 @@ void TransactionView::setModel(WalletModel *_model) transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); transactionView->setModel(transactionProxyModel); + transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); if (_model->getOptionsModel()) { @@ -474,6 +477,52 @@ void TransactionView::copyTxPlainText() GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxPlainTextRole); } +void TransactionView::editLabel() +{ + if(!transactionView->selectionModel() ||!model) + return; + QModelIndexList selection = transactionView->selectionModel()->selectedRows(); + if(!selection.isEmpty()) + { + AddressTableModel *addressBook = model->getAddressTableModel(); + if(!addressBook) + return; + QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString(); + if(address.isEmpty()) + { + // If this transaction has no associated address, exit + return; + } + // Is address in address book? Address book can miss address when a transaction is + // sent from outside the UI. + int idx = addressBook->lookupAddress(address); + if(idx != -1) + { + // Edit sending / receiving address + QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex()); + // Determine type of address, launch appropriate editor dialog type + QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); + + EditAddressDialog dlg( + type == AddressTableModel::Receive + ? EditAddressDialog::EditReceivingAddress + : EditAddressDialog::EditSendingAddress, this); + dlg.setModel(addressBook); + dlg.loadRow(idx); + dlg.exec(); + } + else + { + // Add sending address + EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, + this); + dlg.setModel(addressBook); + dlg.setAddress(address); + dlg.exec(); + } + } +} + void TransactionView::showDetails() { if(!transactionView->selectionModel()) diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index cd40813461..35ada4aa7a 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -90,6 +90,7 @@ private Q_SLOTS: void dateRangeChanged(); void showDetails(); void copyAddress(); + void editLabel(); void copyLabel(); void copyAmount(); void copyTxID(); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c71d2cbe49..99182a3767 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1015,7 +1015,7 @@ static RPCHelpMan pruneblockchain() height = chainHeight - MIN_BLOCKS_TO_KEEP; } - PruneBlockFilesManual(height); + PruneBlockFilesManual(::ChainstateActive(), height); const CBlockIndex* block = ::ChainActive().Tip(); CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { @@ -1073,9 +1073,9 @@ static RPCHelpMan gettxoutsetinfo() const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; - CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); + CCoinsView* coins_view = WITH_LOCK(::cs_main, return &::ChainstateActive().CoinsDB()); NodeContext& node = EnsureNodeContext(request.context); - if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) { + if (GetUTXOStats(coins_view, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, hash_type, node.rpc_interruption_point)) { ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", (int64_t)stats.nTransactions); @@ -1200,7 +1200,7 @@ static RPCHelpMan verifychain() LOCK(cs_main); - return CVerifyDB().VerifyDB(Params(), &::ChainstateActive().CoinsTip(), check_level, check_depth); + return CVerifyDB().VerifyDB(Params(), ::ChainstateActive(), &::ChainstateActive().CoinsTip(), check_level, check_depth); }, }; } @@ -1232,7 +1232,7 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam if (consensusParams.vDeployments[id].nTimeout <= 1230768000) return; UniValue bip9(UniValue::VOBJ); - const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); + const ThresholdState thresholdState = VersionBitsState(::ChainActive().Tip(), consensusParams, id, versionbitscache); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; @@ -1246,12 +1246,12 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id); + int64_t since_height = VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); - BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id); + BIP9Stats statsStruct = VersionBitsStatistics(::ChainActive().Tip(), consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); @@ -1562,7 +1562,7 @@ static RPCHelpMan preciousblock() } BlockValidationState state; - PreciousBlock(state, Params(), pblockindex); + ::ChainstateActive().PreciousBlock(state, Params(), pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -1598,7 +1598,7 @@ static RPCHelpMan invalidateblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } - InvalidateBlock(state, Params(), pblockindex); + ::ChainstateActive().InvalidateBlock(state, Params(), pblockindex); if (state.IsValid()) { ::ChainstateActive().ActivateBestChain(state, Params()); @@ -1637,7 +1637,7 @@ static RPCHelpMan reconsiderblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - ResetBlockFailureFlags(pblockindex); + ::ChainstateActive().ResetBlockFailureFlags(pblockindex); } BlockValidationState state; @@ -2444,7 +2444,7 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { + if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 50987a735b..62b9d1138c 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -150,7 +150,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) { - std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(::ChainstateActive(), coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; @@ -358,7 +358,7 @@ static RPCHelpMan generateblock() LOCK(cs_main); CTxMemPool empty_mempool; - std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(empty_mempool, chainparams).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(empty_mempool, chainparams).CreateNewBlock(::ChainstateActive(), coinbase_script)); if (!blocktemplate) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } @@ -369,7 +369,7 @@ static RPCHelpMan generateblock() // Add transactions block.vtx.insert(block.vtx.end(), txs.begin(), txs.end()); - RegenerateCommitments(block); + RegenerateCommitments(block, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman))); { LOCK(cs_main); @@ -659,7 +659,7 @@ static RPCHelpMan getblocktemplate() throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); if (!Params().IsTestChain()) { - if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { + if (node.connman->GetNodeCount(ConnectionDirection::Both) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); } @@ -747,7 +747,7 @@ static RPCHelpMan getblocktemplate() // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(mempool, Params()).CreateNewBlock(scriptDummy); + pblocktemplate = BlockAssembler(mempool, Params()).CreateNewBlock(::ChainstateActive(), scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 38a0bddddb..143be1274e 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -305,11 +305,11 @@ static RPCHelpMan verifymessage() switch (MessageVerify(strAddress, strSign, strMessage)) { case MessageVerificationResult::ERR_INVALID_ADDRESS: - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); case MessageVerificationResult::ERR_ADDRESS_NO_KEY: throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); + throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: case MessageVerificationResult::ERR_NOT_SIGNED: return false; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 0224ee697a..cf4d46cf2c 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -57,7 +57,7 @@ static RPCHelpMan getconnectioncount() if(!node.connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - return (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL); + return (int)node.connman->GetNodeCount(ConnectionDirection::Both); }, }; } @@ -202,14 +202,14 @@ static RPCHelpMan getpeerinfo() obj.pushKV("bytesrecv", stats.nRecvBytes); obj.pushKV("conntime", stats.nTimeConnected); obj.pushKV("timeoffset", stats.nTimeOffset); - if (stats.m_ping_usec > 0) { - obj.pushKV("pingtime", ((double)stats.m_ping_usec) / 1e6); + if (stats.m_last_ping_time > 0us) { + obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time)); } - if (stats.m_min_ping_usec < std::numeric_limits<int64_t>::max()) { - obj.pushKV("minping", ((double)stats.m_min_ping_usec) / 1e6); + if (stats.m_min_ping_time < std::chrono::microseconds::max()) { + obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time)); } - if (fStateStats && statestats.m_ping_wait_usec > 0) { - obj.pushKV("pingwait", ((double)statestats.m_ping_wait_usec) / 1e6); + if (fStateStats && statestats.m_ping_wait > 0s) { + obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); // Use the sanitized form of subver here, to avoid tricksy remote peers from @@ -546,7 +546,7 @@ static UniValue GetNetworksInfo() UniValue networks(UniValue::VARR); for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast<enum Network>(n); - if (network == NET_UNROUTABLE || network == NET_I2P || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; proxyType proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); @@ -630,9 +630,9 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("timeoffset", GetTimeOffset()); if (node.connman) { obj.pushKV("networkactive", node.connman->GetNetworkActive()); - obj.pushKV("connections", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); - obj.pushKV("connections_in", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_IN)); - obj.pushKV("connections_out", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_OUT)); + obj.pushKV("connections", (int)node.connman->GetNodeCount(ConnectionDirection::Both)); + obj.pushKV("connections_in", (int)node.connman->GetNodeCount(ConnectionDirection::In)); + obj.pushKV("connections_out", (int)node.connman->GetNodeCount(ConnectionDirection::Out)); } obj.pushKV("networks", GetNetworksInfo()); obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index ecac3b9e7e..20a4ce48b0 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1834,7 +1834,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash) { const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; - //! The inner pubkey (x-only, so no Y coordinate parity). + //! The internal pubkey (x-only, so no Y coordinate parity). const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; //! The output pubkey (taken from the scriptPubKey). const XOnlyPubKey q{uint256(program)}; @@ -1852,9 +1852,9 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c } k = ss_branch.GetSHA256(); } - // Compute the tweak from the Merkle root and the inner pubkey. + // Compute the tweak from the Merkle root and the internal pubkey. k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256(); - // Verify that the output pubkey matches the tweaked inner pubkey, after correcting for parity. + // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. return q.CheckPayToContract(p, k, control[0] & 1); } diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 633a95ce96..04da10715f 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -62,7 +62,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const CScript& scriptPubKey) { const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(*m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(*m_node.mempool, chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 0d480e35ea..5906913b58 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -14,6 +14,7 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <serialize.h> +#include <txorphanage.h> #include <util/memory.h> #include <util/string.h> #include <util/system.h> @@ -43,18 +44,6 @@ struct CConnmanTest : public CConnman { } }; -// Tests these internal-to-net_processing.cpp methods: -extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer); -extern void EraseOrphansFor(NodeId peer); -extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans); - -struct COrphanTx { - CTransactionRef tx; - NodeId fromPeer; - int64_t nTimeExpire; -}; -extern std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); - static CService ip(uint32_t i) { struct in_addr s; @@ -295,15 +284,23 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) peerLogic->FinalizeNode(dummyNode, dummy); } -static CTransactionRef RandomOrphan() +class TxOrphanageTest : public TxOrphanage { - std::map<uint256, COrphanTx>::iterator it; - LOCK2(cs_main, g_cs_orphans); - it = mapOrphanTransactions.lower_bound(InsecureRand256()); - if (it == mapOrphanTransactions.end()) - it = mapOrphanTransactions.begin(); - return it->second.tx; -} +public: + inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + return m_orphans.size(); + } + + CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + std::map<uint256, OrphanTx>::iterator it; + it = m_orphans.lower_bound(InsecureRand256()); + if (it == m_orphans.end()) + it = m_orphans.begin(); + return it->second.tx; + } +}; static void MakeNewKeyWithFastRandomContext(CKey& key) { @@ -323,11 +320,14 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) // signature's R and S values have leading zeros. g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33))); + TxOrphanageTest orphanage; CKey key; MakeNewKeyWithFastRandomContext(key); FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(key)); + LOCK(g_cs_orphans); + // 50 orphan transactions: for (int i = 0; i < 50; i++) { @@ -340,13 +340,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vout[0].nValue = 1*CENT; tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - AddOrphanTx(MakeTransactionRef(tx), i); + orphanage.AddTx(MakeTransactionRef(tx), i); } // ... and 50 that depend on other orphans: for (int i = 0; i < 50; i++) { - CTransactionRef txPrev = RandomOrphan(); + CTransactionRef txPrev = orphanage.RandomOrphan(); CMutableTransaction tx; tx.vin.resize(1); @@ -357,13 +357,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - AddOrphanTx(MakeTransactionRef(tx), i); + orphanage.AddTx(MakeTransactionRef(tx), i); } // This really-big orphan should be ignored: for (int i = 0; i < 10; i++) { - CTransactionRef txPrev = RandomOrphan(); + CTransactionRef txPrev = orphanage.RandomOrphan(); CMutableTransaction tx; tx.vout.resize(1); @@ -381,25 +381,24 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) for (unsigned int j = 1; j < tx.vin.size(); j++) tx.vin[j].scriptSig = tx.vin[0].scriptSig; - BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i)); + BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); } - LOCK2(cs_main, g_cs_orphans); // Test EraseOrphansFor: for (NodeId i = 0; i < 3; i++) { - size_t sizeBefore = mapOrphanTransactions.size(); - EraseOrphansFor(i); - BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore); + size_t sizeBefore = orphanage.CountOrphans(); + orphanage.EraseForPeer(i); + BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); } // Test LimitOrphanTxSize() function: - LimitOrphanTxSize(40); - BOOST_CHECK(mapOrphanTransactions.size() <= 40); - LimitOrphanTxSize(10); - BOOST_CHECK(mapOrphanTransactions.size() <= 10); - LimitOrphanTxSize(0); - BOOST_CHECK(mapOrphanTransactions.empty()); + orphanage.LimitOrphans(40); + BOOST_CHECK(orphanage.CountOrphans() <= 40); + orphanage.LimitOrphans(10); + BOOST_CHECK(orphanage.CountOrphans() <= 10); + orphanage.LimitOrphans(0); + BOOST_CHECK(orphanage.CountOrphans() == 0); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/FuzzedDataProvider.h b/src/test/fuzz/FuzzedDataProvider.h index 744a9d78ce..6cbfc39bc2 100644 --- a/src/test/fuzz/FuzzedDataProvider.h +++ b/src/test/fuzz/FuzzedDataProvider.h @@ -20,6 +20,7 @@ #include <cstdint> #include <cstring> #include <initializer_list> +#include <limits> #include <string> #include <type_traits> #include <utility> diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 1ea6b3d01d..b55f1c72b1 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index 9ecd172e19..dbc0b5ab81 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index e0715f3e29..8bf484722c 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -8,6 +8,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <cstdint> @@ -26,7 +27,7 @@ int64_t ConsumeBanTimeOffset(FuzzedDataProvider& fuzzed_data_provider) noexcept void initialize_banman() { - static const auto testing_setup = MakeFuzzingContext<>(); + static const auto testing_setup = MakeNoLogFileContext<>(); } FUZZ_TARGET_INIT(banman, initialize_banman) diff --git a/src/test/fuzz/bloom_filter.cpp b/src/test/fuzz/bloom_filter.cpp index d43c182644..c5bb8744a4 100644 --- a/src/test/fuzz/bloom_filter.cpp +++ b/src/test/fuzz/bloom_filter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/buffered_file.cpp b/src/test/fuzz/buffered_file.cpp index 3a1b2dbbe7..ffe38f10fc 100644 --- a/src/test/fuzz/buffered_file.cpp +++ b/src/test/fuzz/buffered_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 8ece94d771..d951bda20f 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -16,6 +16,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <validation.h> #include <cstdint> @@ -36,7 +37,7 @@ bool operator==(const Coin& a, const Coin& b) void initialize_coins_view() { - static const auto testing_setup = MakeFuzzingContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); } FUZZ_TARGET_INIT(coins_view, initialize_coins_view) @@ -263,7 +264,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) CCoinsStats stats; bool expected_code_path = false; try { - (void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED); + (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED); } catch (const std::logic_error&) { expected_code_path = true; } diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 71b4b00116..ae77a45e44 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -10,6 +10,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <util/translation.h> #include <cstdint> @@ -17,7 +18,7 @@ void initialize_connman() { - static const auto testing_setup = MakeFuzzingContext<>(); + static const auto testing_setup = MakeNoLogFileContext<>(); } FUZZ_TARGET_INIT(connman, initialize_connman) @@ -94,7 +95,7 @@ FUZZ_TARGET_INIT(connman, initialize_connman) (void)connman.GetDeterministicRandomizer(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); }, [&] { - (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({CConnman::CONNECTIONS_NONE, CConnman::CONNECTIONS_IN, CConnman::CONNECTIONS_OUT, CConnman::CONNECTIONS_ALL})); + (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({ConnectionDirection::None, ConnectionDirection::In, ConnectionDirection::Out, ConnectionDirection::Both})); }, [&] { connman.MarkAddressGood(random_address); @@ -104,7 +105,9 @@ FUZZ_TARGET_INIT(connman, initialize_connman) }, [&] { // Limit now to int32_t to avoid signed integer overflow - (void)connman.PoissonNextSendInbound(fuzzed_data_provider.ConsumeIntegral<int32_t>(), fuzzed_data_provider.ConsumeIntegral<int>()); + (void)connman.PoissonNextSendInbound( + std::chrono::microseconds{fuzzed_data_provider.ConsumeIntegral<int32_t>()}, + std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int>()}); }, [&] { CSerializedNetMsg serialized_net_msg; diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp index 17ac48fca7..eeeac18968 100644 --- a/src/test/fuzz/crypto.cpp +++ b/src/test/fuzz/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index bb8dd4594f..8adfa92420 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp index 0e1c44cded..bb4ef22158 100644 --- a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp +++ b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/data_stream.cpp b/src/test/fuzz/data_stream.cpp index f3b6e6af04..473caec6ff 100644 --- a/src/test/fuzz/data_stream.cpp +++ b/src/test/fuzz/data_stream.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -7,13 +7,14 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <vector> void initialize_data_stream_addr_man() { - static const auto testing_setup = MakeFuzzingContext<>(); + static const auto testing_setup = MakeNoLogFileContext<>(); } FUZZ_TARGET_INIT(data_stream_addr_man, initialize_data_stream_addr_man) diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index ba5f0c1a75..64c6e49615 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index edb270d437..1fab46ff13 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -44,7 +44,7 @@ void initialize() std::get<1>(it->second)(); } -#if defined(PROVIDE_MAIN_FUNCTION) +#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) static bool read_stdin(std::vector<uint8_t>& data) { uint8_t buffer[1024]; @@ -71,7 +71,7 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) return 0; } -#if defined(PROVIDE_MAIN_FUNCTION) +#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) int main(int argc, char** argv) { initialize(); diff --git a/src/test/fuzz/fuzz.h b/src/test/fuzz/fuzz.h index 4abc52c15a..2bad77bdc1 100644 --- a/src/test/fuzz/fuzz.h +++ b/src/test/fuzz/fuzz.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index ac83d91ea0..5bc99ddcb9 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -84,8 +84,7 @@ FUZZ_TARGET_INIT(integer, initialize_integer) (void)DecompressAmount(u64); (void)FormatISO8601Date(i64); (void)FormatISO8601DateTime(i64); - // FormatMoney(i) not defined when i == std::numeric_limits<int64_t>::min() - if (i64 != std::numeric_limits<int64_t>::min()) { + { int64_t parsed_money; if (ParseMoney(FormatMoney(i64), parsed_money)) { assert(parsed_money == i64); @@ -132,8 +131,7 @@ FUZZ_TARGET_INIT(integer, initialize_integer) (void)SipHashUint256Extra(u64, u64, u256, u32); (void)ToLower(ch); (void)ToUpper(ch); - // ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min() - if (i64 != std::numeric_limits<int64_t>::min()) { + { int64_t parsed_money; if (ParseMoney(ValueFromAmount(i64).getValStr(), parsed_money)) { assert(parsed_money == i64); diff --git a/src/test/fuzz/kitchen_sink.cpp b/src/test/fuzz/kitchen_sink.cpp index fa4024fc38..908e9a1c83 100644 --- a/src/test/fuzz/kitchen_sink.cpp +++ b/src/test/fuzz/kitchen_sink.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp index 95597bf082..dbd0c76d42 100644 --- a/src/test/fuzz/load_external_block_file.cpp +++ b/src/test/fuzz/load_external_block_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -15,7 +15,7 @@ void initialize_load_external_block_file() { - static const auto testing_setup = MakeFuzzingContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); } FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) diff --git a/src/test/fuzz/merkleblock.cpp b/src/test/fuzz/merkleblock.cpp index 23e0baa564..1eefd4c521 100644 --- a/src/test/fuzz/merkleblock.cpp +++ b/src/test/fuzz/merkleblock.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/muhash.cpp b/src/test/fuzz/muhash.cpp index 2d761cef15..4ea9511870 100644 --- a/src/test/fuzz/muhash.cpp +++ b/src/test/fuzz/muhash.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index 21dca4eb05..b056f46f2e 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -22,7 +22,7 @@ void initialize_net() { - static const auto testing_setup = MakeFuzzingContext<>(CBaseChainParams::MAIN); + static const auto testing_setup = MakeNoLogFileContext<>(CBaseChainParams::MAIN); } FUZZ_TARGET_INIT(net, initialize_net) diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp index a42080eb66..f9d8129ca9 100644 --- a/src/test/fuzz/netaddress.cpp +++ b/src/test/fuzz/netaddress.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp index aaebe83c0a..606ebfc151 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -21,17 +21,17 @@ FUZZ_TARGET(node_eviction) std::vector<NodeEvictionCandidate> eviction_candidates; while (fuzzed_data_provider.ConsumeBool()) { eviction_candidates.push_back({ - fuzzed_data_provider.ConsumeIntegral<NodeId>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeIntegral<uint64_t>(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), + /* id */ fuzzed_data_provider.ConsumeIntegral<NodeId>(), + /* nTimeConnected */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* m_min_ping_time */ std::chrono::microseconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()}, + /* nLastBlockTime */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* nLastTXTime */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* fRelevantServices */ fuzzed_data_provider.ConsumeBool(), + /* fRelayTxes */ fuzzed_data_provider.ConsumeBool(), + /* fBloomFilter */ fuzzed_data_provider.ConsumeBool(), + /* nKeyedNetGroup */ fuzzed_data_provider.ConsumeIntegral<uint64_t>(), + /* prefer_evict */ fuzzed_data_provider.ConsumeBool(), + /* m_is_local */ fuzzed_data_provider.ConsumeBool(), }); } // Make a copy since eviction_candidates may be in some valid but otherwise diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index fff893fb3f..116b7a71d9 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -7,6 +7,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <txmempool.h> #include <cstdint> @@ -16,7 +17,7 @@ void initialize_policy_estimator() { - static const auto testing_setup = MakeFuzzingContext<>(); + static const auto testing_setup = MakeNoLogFileContext<>(); } FUZZ_TARGET_INIT(policy_estimator, initialize_policy_estimator) diff --git a/src/test/fuzz/policy_estimator_io.cpp b/src/test/fuzz/policy_estimator_io.cpp index 73242870a0..9021d95954 100644 --- a/src/test/fuzz/policy_estimator_io.cpp +++ b/src/test/fuzz/policy_estimator_io.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -6,13 +6,14 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <vector> void initialize_policy_estimator_io() { - static const auto testing_setup = MakeFuzzingContext<>(); + static const auto testing_setup = MakeNoLogFileContext<>(); } FUZZ_TARGET_INIT(policy_estimator_io, initialize_policy_estimator_io) diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp index c4348495bf..53726ca893 100644 --- a/src/test/fuzz/pow.cpp +++ b/src/test/fuzz/pow.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 442e32d4ca..0289d49ccc 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -18,6 +18,7 @@ #include <test/util/net.h> #include <test/util/setup_common.h> #include <test/util/validation.h> +#include <txorphanage.h> #include <util/memory.h> #include <validationinterface.h> #include <version.h> @@ -56,7 +57,7 @@ void initialize_process_message() { Assert(GetNumMsgTypes() == getAllNetMessageTypes().size()); // If this fails, add or remove the message type below - static const auto testing_setup = MakeFuzzingContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); g_setup = testing_setup.get(); for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { MineBlock(g_setup->m_node, CScript() << OP_TRUE); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index ef45196671..617a71ea60 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -13,6 +13,7 @@ #include <test/util/net.h> #include <test/util/setup_common.h> #include <test/util/validation.h> +#include <txorphanage.h> #include <util/memory.h> #include <validation.h> #include <validationinterface.h> @@ -23,7 +24,7 @@ const TestingSetup* g_setup; void initialize_process_messages() { - static const auto testing_setup = MakeFuzzingContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); g_setup = testing_setup.get(); for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { MineBlock(g_setup->m_node, CScript() << OP_TRUE); diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp index 2a08b45aa3..07059cce76 100644 --- a/src/test/fuzz/rolling_bloom_filter.cpp +++ b/src/test/fuzz/rolling_bloom_filter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index 7fadf36f98..193862e847 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2020 The Bitcoin Core developers +// Copyright (c) 2019-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. diff --git a/src/test/fuzz/script_assets_test_minimizer.cpp b/src/test/fuzz/script_assets_test_minimizer.cpp index 8d9a939dab..5f07acbcc7 100644 --- a/src/test/fuzz/script_assets_test_minimizer.cpp +++ b/src/test/fuzz/script_assets_test_minimizer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/script_ops.cpp b/src/test/fuzz/script_ops.cpp index bdbfe817ff..eb1c808a88 100644 --- a/src/test/fuzz/script_ops.cpp +++ b/src/test/fuzz/script_ops.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/scriptnum_ops.cpp b/src/test/fuzz/scriptnum_ops.cpp index bc4867839c..62ed50d13f 100644 --- a/src/test/fuzz/scriptnum_ops.cpp +++ b/src/test/fuzz/scriptnum_ops.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/signet.cpp b/src/test/fuzz/signet.cpp index 83effec064..303dcf13e3 100644 --- a/src/test/fuzz/signet.cpp +++ b/src/test/fuzz/signet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. @@ -10,6 +10,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <optional> @@ -17,7 +18,7 @@ void initialize_signet() { - static const auto testing_setup = MakeFuzzingContext<>(CBaseChainParams::SIGNET); + static const auto testing_setup = MakeNoLogFileContext<>(CBaseChainParams::SIGNET); } FUZZ_TARGET_INIT(signet, initialize_signet) diff --git a/src/test/fuzz/socks5.cpp b/src/test/fuzz/socks5.cpp new file mode 100644 index 0000000000..e5cc4cabe5 --- /dev/null +++ b/src/test/fuzz/socks5.cpp @@ -0,0 +1,44 @@ +// 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 <netbase.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> + +#include <cstdint> +#include <string> +#include <vector> + +namespace { +int default_socks5_recv_timeout; +}; + +extern int g_socks5_recv_timeout; + +void initialize_socks5() +{ + static const auto testing_setup = MakeNoLogFileContext<const BasicTestingSetup>(); + default_socks5_recv_timeout = g_socks5_recv_timeout; +} + +FUZZ_TARGET_INIT(socks5, initialize_socks5) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + ProxyCredentials proxy_credentials; + proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512); + proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512); + InterruptSocks5(fuzzed_data_provider.ConsumeBool()); + // Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This + // will slow down fuzzing. + g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1 : default_socks5_recv_timeout; + FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider); + // This Socks5(...) fuzzing harness would have caught CVE-2017-18350 within + // a few seconds of fuzzing. + (void)Socks5(fuzzed_data_provider.ConsumeRandomLengthString(512), + fuzzed_data_provider.ConsumeIntegral<int>(), + fuzzed_data_provider.ConsumeBool() ? &proxy_credentials : nullptr, + fuzzed_sock); +} diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index ec8a3b23db..93b4948a2f 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/strprintf.cpp b/src/test/fuzz/strprintf.cpp index b66a7abfb3..2c92b159a5 100644 --- a/src/test/fuzz/strprintf.cpp +++ b/src/test/fuzz/strprintf.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp index 3621702e45..d9571209fa 100644 --- a/src/test/fuzz/system.cpp +++ b/src/test/fuzz/system.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Bitcoin Core developers +// 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. diff --git a/src/test/fuzz/torcontrol.cpp b/src/test/fuzz/torcontrol.cpp new file mode 100644 index 0000000000..a97d3962bf --- /dev/null +++ b/src/test/fuzz/torcontrol.cpp @@ -0,0 +1,80 @@ +// 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 <test/util/setup_common.h> +#include <torcontrol.h> + +#include <cstdint> +#include <string> +#include <vector> + +class DummyTorControlConnection : public TorControlConnection +{ +public: + DummyTorControlConnection() : TorControlConnection{nullptr} + { + } + + bool Connect(const std::string&, const ConnectionCB&, const ConnectionCB&) + { + return true; + } + + void Disconnect() + { + } + + bool Command(const std::string&, const ReplyHandlerCB&) + { + return true; + } +}; + +void initialize_torcontrol() +{ + static const auto testing_setup = MakeNoLogFileContext<>(); +} + +FUZZ_TARGET_INIT(torcontrol, initialize_torcontrol) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + + TorController tor_controller; + while (fuzzed_data_provider.ConsumeBool()) { + TorControlReply tor_control_reply; + CallOneOf( + fuzzed_data_provider, + [&] { + tor_control_reply.code = 250; + }, + [&] { + tor_control_reply.code = 510; + }, + [&] { + tor_control_reply.code = fuzzed_data_provider.ConsumeIntegral<int>(); + }); + tor_control_reply.lines = ConsumeRandomLengthStringVector(fuzzed_data_provider); + if (tor_control_reply.lines.empty()) { + break; + } + DummyTorControlConnection dummy_tor_control_connection; + CallOneOf( + fuzzed_data_provider, + [&] { + tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.auth_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.authchallenge_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.protocolinfo_cb(dummy_tor_control_connection, tor_control_reply); + }); + } +} diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 13ae450756..41e1687405 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -100,16 +100,7 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) (void)IsWitnessStandard(tx, coins_view_cache); UniValue u(UniValue::VOBJ); - // ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min() - bool skip_tx_to_univ = false; - for (const CTxOut& txout : tx.vout) { - if (txout.nValue == std::numeric_limits<int64_t>::min()) { - skip_tx_to_univ = true; - } - } - if (!skip_tx_to_univ) { - TxToUniv(tx, /* hashBlock */ {}, u); - static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - TxToUniv(tx, u256_max, u); - } + TxToUniv(tx, /* hashBlock */ {}, u); + static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + TxToUniv(tx, u256_max, u); } diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 7a2dcfe84a..d8c536e8b1 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -10,6 +10,7 @@ #include <attributes.h> #include <chainparamsbase.h> #include <coins.h> +#include <compat.h> #include <consensus/consensus.h> #include <merkleblock.h> #include <net.h> @@ -23,14 +24,13 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/util/net.h> -#include <test/util/setup_common.h> #include <txmempool.h> #include <uint256.h> #include <util/time.h> -#include <util/vector.h> #include <version.h> #include <algorithm> +#include <array> #include <cstdint> #include <cstdio> #include <optional> @@ -251,6 +251,15 @@ template <class T> } /** + * Sets errno to a value selected from the given std::array `errnos`. + */ +template <typename T, size_t size> +void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider, const std::array<T, size>& errnos) +{ + errno = fuzzed_data_provider.PickValueInArray(errnos); +} + +/** * Returns a byte vector of specified size regardless of the number of remaining bytes available * from the fuzzer. Pads with zero value bytes if needed to achieve the specified size. */ @@ -324,19 +333,6 @@ inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, co void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept; -template <class T = const BasicTestingSetup> -std::unique_ptr<T> MakeFuzzingContext(const std::string& chain_name = CBaseChainParams::REGTEST, const std::vector<const char*>& extra_args = {}) -{ - // Prepend default arguments for fuzzing - const std::vector<const char*> arguments = Cat( - { - "-nodebuglogfile", - }, - extra_args); - - return MakeUnique<T>(chain_name, arguments); -} - class FuzzedFileProvider { FuzzedDataProvider& m_fuzzed_data_provider; @@ -534,4 +530,127 @@ void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) no } } +class FuzzedSock : public Sock +{ + FuzzedDataProvider& m_fuzzed_data_provider; + +public: + explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider} + { + } + + ~FuzzedSock() override + { + } + + FuzzedSock& operator=(Sock&& other) override + { + assert(false && "Not implemented yet."); + return *this; + } + + SOCKET Get() const override + { + assert(false && "Not implemented yet."); + return INVALID_SOCKET; + } + + SOCKET Release() override + { + assert(false && "Not implemented yet."); + return INVALID_SOCKET; + } + + void Reset() override + { + assert(false && "Not implemented yet."); + } + + ssize_t Send(const void* data, size_t len, int flags) const override + { + constexpr std::array send_errnos{ + EACCES, + EAGAIN, + EALREADY, + EBADF, + ECONNRESET, + EDESTADDRREQ, + EFAULT, + EINTR, + EINVAL, + EISCONN, + EMSGSIZE, + ENOBUFS, + ENOMEM, + ENOTCONN, + ENOTSOCK, + EOPNOTSUPP, + EPIPE, + EWOULDBLOCK, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + return len; + } + const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len); + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos); + } + return r; + } + + ssize_t Recv(void* buf, size_t len, int flags) const override + { + constexpr std::array recv_errnos{ + EAGAIN, + EBADF, + ECONNREFUSED, + EFAULT, + EINTR, + EINVAL, + ENOMEM, + ENOTCONN, + ENOTSOCK, + EWOULDBLOCK, + }; + assert(buf != nullptr || len == 0); + if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) { + const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos); + } + return r; + } + const std::vector<uint8_t> random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>( + m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len)); + if (random_bytes.empty()) { + const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos); + } + return r; + } + std::memcpy(buf, random_bytes.data(), random_bytes.size()); + if (m_fuzzed_data_provider.ConsumeBool()) { + if (len > random_bytes.size()) { + std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size()); + } + return len; + } + if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) { + std::this_thread::sleep_for(std::chrono::milliseconds{2}); + } + return random_bytes.size(); + } + + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override + { + return m_fuzzed_data_provider.ConsumeBool(); + } +}; + +[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider) +{ + return FuzzedSock{fuzzed_data_provider}; +} + #endif // BITCOIN_TEST_FUZZ_UTIL_H diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index aa628371e6..9acd17c463 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -122,7 +122,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co uint256 hashHighFeeTx = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx); @@ -143,7 +143,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx); @@ -157,7 +157,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx); @@ -179,7 +179,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); // Verify that this tx isn't selected. for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { @@ -192,7 +192,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) fCheckpointsEnabled = false; // Simple block creation, nothing special yet: - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); // We can't make transactions until we have inputs // Therefore, load 110 blocks :) @@ -252,7 +252,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) LOCK(m_node.mempool->cs); // Just to make sure we can still make simple blocks - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); const CAmount BLOCKSUBSIDY = 50*COIN; const CAmount LOWFEE = CENT; @@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.hash = hash; } - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); + BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); m_node.mempool->clear(); tx.vin[0].prevout.hash = txFirst[0]->GetHash(); @@ -291,7 +291,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); tx.vin[0].prevout.hash = hash; } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); m_node.mempool->clear(); // block size > limit @@ -311,13 +311,13 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); m_node.mempool->clear(); // orphan in *m_node.mempool, template creation fails hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); + BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); // child with higher feerate than parent @@ -334,7 +334,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); m_node.mempool->clear(); // coinbase in *m_node.mempool, template creation fails @@ -346,7 +346,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // give it a fee so it'll get mined m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw bad-cb-multiple - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); + BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); m_node.mempool->clear(); // double spend txn pair in *m_node.mempool, template creation fails @@ -359,7 +359,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); + BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); // subsidy changing @@ -375,7 +375,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) next->BuildSkip(); ::ChainActive().SetTip(next); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); // Extend to a 210000-long block chain. while (::ChainActive().Tip()->nHeight < 210000) { CBlockIndex* prev = ::ChainActive().Tip(); @@ -387,7 +387,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) next->BuildSkip(); ::ChainActive().SetTip(next); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); // invalid p2sh txn in *m_node.mempool, template creation fails tx.vin[0].prevout.hash = txFirst[0]->GetHash(); @@ -404,7 +404,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // Should throw block-validation-failed - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); + BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); m_node.mempool->clear(); // Delete the dummy blocks again. @@ -492,7 +492,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); // None of the of the absolute height/time locked tx should have made // it into the template because we still check IsFinalTx in CreateNewBlock, @@ -505,7 +505,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) ::ChainActive().Tip()->nHeight++; SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); ::ChainActive().Tip()->nHeight--; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 1c7c35528e..a29c6a2665 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -322,6 +322,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); + BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); @@ -332,6 +333,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); + BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); @@ -352,6 +354,35 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) // TOR, invalid base32 BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"})); + // I2P + const char* i2p_addr = "UDHDrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.I2P"; + BOOST_REQUIRE(addr.SetSpecial(i2p_addr)); + BOOST_REQUIRE(addr.IsValid()); + BOOST_REQUIRE(addr.IsI2P()); + + BOOST_CHECK(!addr.IsTor()); + BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); + BOOST_CHECK_EQUAL(addr.ToString(), ToLower(i2p_addr)); + + // I2P, correct length, but decodes to less than the expected number of bytes. + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jn=.b32.i2p")); + + // I2P, extra unnecessary padding + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna=.b32.i2p")); + + // I2P, malicious + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v\0wtf.b32.i2p"s)); + + // I2P, valid but unsupported (56 Base32 characters) + // See "Encrypted LS with Base 32 Addresses" in + // https://geti2p.net/spec/encryptedleaseset.txt + BOOST_CHECK( + !addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.b32.i2p")); + + // I2P, invalid base32 + BOOST_CHECK(!addr.SetSpecial(std::string{"tp*szydbh4dp.b32.i2p"})); + // Internal addr.SetInternal("esffpp"); BOOST_REQUIRE(!addr.IsValid()); // "internal" is considered invalid @@ -772,21 +803,6 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_CHECK_EQUAL(IsLocal(addr), false); } -BOOST_AUTO_TEST_CASE(PoissonNextSend) -{ - g_mock_deterministic_tests = true; - - int64_t now = 5000; - int average_interval_seconds = 600; - - auto poisson = ::PoissonNextSend(now, average_interval_seconds); - std::chrono::microseconds poisson_chrono = ::PoissonNextSend(std::chrono::microseconds{now}, std::chrono::seconds{average_interval_seconds}); - - BOOST_CHECK_EQUAL(poisson, poisson_chrono.count()); - - g_mock_deterministic_tests = false; -} - std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context) { std::vector<NodeEvictionCandidate> candidates; @@ -794,7 +810,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_c candidates.push_back({ /* id */ id, /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)), - /* m_min_ping_time */ static_cast<int64_t>(random_context.randrange(100)), + /* m_min_ping_time */ std::chrono::microseconds{random_context.randrange(100)}, /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)), /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)), /* fRelevantServices */ random_context.randbool(), @@ -854,7 +870,7 @@ BOOST_AUTO_TEST_CASE(node_eviction_test) // from eviction. BOOST_CHECK(!IsEvicted( number_of_nodes, [](NodeEvictionCandidate& candidate) { - candidate.m_min_ping_time = candidate.id; + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; }, {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); @@ -900,10 +916,10 @@ BOOST_AUTO_TEST_CASE(node_eviction_test) // Combination of all tests above. BOOST_CHECK(!IsEvicted( number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected - candidate.m_min_ping_time = candidate.id; // 8 protected - candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected - candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected + candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected + candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected }, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index b54cbb3f00..810665877d 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -174,6 +174,16 @@ BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) BOOST_CHECK_EQUAL(ValueFromAmount(COIN/1000000).write(), "0.00000100"); BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10000000).write(), "0.00000010"); BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100000000).write(), "0.00000001"); + + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max()).write(), "92233720368.54775807"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 1).write(), "92233720368.54775806"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 2).write(), "92233720368.54775805"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 3).write(), "92233720368.54775804"); + // ... + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 3).write(), "-92233720368.54775805"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 2).write(), "-92233720368.54775806"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 1).write(), "-92233720368.54775807"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min()).write(), "-92233720368.54775808"); } static UniValue ValueFromString(const std::string &str) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index d1770f750f..8f1d99b199 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -63,7 +63,7 @@ static std::map<std::string, unsigned int> mapFlagNames = { unsigned int ParseScriptFlags(std::string strFlags) { - if (strFlags.empty() | strFlags == "NONE") return 0; + if (strFlags.empty() || strFlags == "NONE") return 0; unsigned int flags = 0; std::vector<std::string> words; boost::algorithm::split(words, strFlags, boost::algorithm::is_any_of(",")); diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 0c6487fbfa..ba1edba0ae 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -42,7 +42,7 @@ std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coi { auto block = std::make_shared<CBlock>( BlockAssembler{*Assert(node.mempool), Params()} - .CreateNewBlock(coinbase_scriptPubKey) + .CreateNewBlock(::ChainstateActive(), coinbase_scriptPubKey) ->block); LOCK(cs_main); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 1ffe435531..f2174cd084 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -179,7 +179,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const assert(!::ChainstateActive().CanFlushToDisk()); ::ChainstateActive().InitCoinsCache(1 << 23); assert(::ChainstateActive().CanFlushToDisk()); - if (!LoadGenesisBlock(chainparams)) { + if (!::ChainstateActive().LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } @@ -245,13 +245,13 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa { const CChainParams& chainparams = Params(); CTxMemPool empty_pool; - CBlock block = BlockAssembler(empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler(empty_pool, chainparams).CreateNewBlock(::ChainstateActive(), scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } - RegenerateCommitments(block); + RegenerateCommitments(block, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman))); while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 33f24e7c44..7323f1f0b6 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -15,6 +15,7 @@ #include <txmempool.h> #include <util/check.h> #include <util/string.h> +#include <util/vector.h> #include <type_traits> #include <vector> @@ -152,6 +153,23 @@ struct TestChain100DeterministicSetup : public TestChain100Setup { TestChain100DeterministicSetup() : TestChain100Setup(true) { } }; +/** + * Make a test setup that has disk access to the debug.log file disabled. Can + * be used in "hot loops", for example fuzzing or benchmarking. + */ +template <class T = const BasicTestingSetup> +std::unique_ptr<T> MakeNoLogFileContext(const std::string& chain_name = CBaseChainParams::REGTEST, const std::vector<const char*>& extra_args = {}) +{ + const std::vector<const char*> arguments = Cat( + { + "-nodebuglogfile", + "-nodebug", + }, + extra_args); + + return std::make_unique<T>(chain_name, arguments); +} + class CTxMemPoolEntry; struct TestMemPoolEntryHelper diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 845854bd4b..5a46002a79 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1180,6 +1180,16 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney) BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000), "0.000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000), "0.0000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000), "0.00000001"); + + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max()), "92233720368.54775807"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 1), "92233720368.54775806"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 2), "92233720368.54775805"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 3), "92233720368.54775804"); + // ... + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 3), "-92233720368.54775805"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 2), "-92233720368.54775806"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 1), "-92233720368.54775807"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min()), "-92233720368.54775808"); } BOOST_AUTO_TEST_CASE(util_ParseMoney) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 0c87c4d360..f3fc83078f 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -63,7 +63,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) static int i = 0; static uint64_t time = Params().GenesisBlock().nTime; - auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE); + auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(::ChainstateActive(), CScript{} << i++ << OP_TRUE); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -325,7 +325,7 @@ BOOST_AUTO_TEST_CASE(witness_commitment_index) { CScript pubKey; pubKey << 1 << OP_TRUE; - auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey); + auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(::ChainstateActive(), pubKey); CBlock pblock = ptemplate->block; CTxOut witness; diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 50444f7bbe..8841a540f2 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -14,6 +14,18 @@ /* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */ static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; } +static const std::string StateName(ThresholdState state) +{ + switch (state) { + case ThresholdState::DEFINED: return "DEFINED"; + case ThresholdState::STARTED: return "STARTED"; + case ThresholdState::LOCKED_IN: return "LOCKED_IN"; + case ThresholdState::ACTIVE: return "ACTIVE"; + case ThresholdState::FAILED: return "FAILED"; + } // no default case, so the compiler can warn about missing cases + return ""; +} + static const Consensus::Params paramsDummy = Consensus::Params(); class TestConditionChecker : public AbstractThresholdConditionChecker @@ -38,6 +50,13 @@ public: int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; } }; +class TestNeverActiveConditionChecker : public TestConditionChecker +{ +public: + int64_t BeginTime(const Consensus::Params& params) const override { return 0; } + int64_t EndTime(const Consensus::Params& params) const override { return 1230768000; } +}; + #define CHECKERS 6 class VersionBitsTester @@ -51,6 +70,8 @@ class VersionBitsTester TestConditionChecker checker[CHECKERS]; // Another 6 that assume always active activation TestAlwaysActiveConditionChecker checker_always[CHECKERS]; + // Another 6 that assume never active activation + TestNeverActiveConditionChecker checker_never[CHECKERS]; // Test counter (to identify failures) int num; @@ -65,6 +86,7 @@ public: for (unsigned int i = 0; i < CHECKERS; i++) { checker[i] = TestConditionChecker(); checker_always[i] = TestAlwaysActiveConditionChecker(); + checker_never[i] = TestNeverActiveConditionChecker(); } vpblock.clear(); return *this; @@ -92,66 +114,40 @@ public: if (InsecureRandBits(i) == 0) { BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == height, strprintf("Test %i for StateSinceHeight", num)); BOOST_CHECK_MESSAGE(checker_always[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == 0, strprintf("Test %i for StateSinceHeight (always active)", num)); - } - } - num++; - return *this; - } - VersionBitsTester& TestDefined() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::DEFINED, strprintf("Test %i for DEFINED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); + // never active may go from DEFINED -> FAILED at the first period + const auto never_height = checker_never[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()); + BOOST_CHECK_MESSAGE(never_height == 0 || never_height == checker_never[i].Period(paramsDummy), strprintf("Test %i for StateSinceHeight (never active)", num)); } } num++; return *this; } - VersionBitsTester& TestStarted() { + VersionBitsTester& TestState(ThresholdState exp) { for (int i = 0; i < CHECKERS; i++) { if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::STARTED, strprintf("Test %i for STARTED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); + const CBlockIndex* pindex = vpblock.empty() ? nullptr : vpblock.back(); + ThresholdState got = checker[i].GetStateFor(pindex); + ThresholdState got_always = checker_always[i].GetStateFor(pindex); + ThresholdState got_never = checker_never[i].GetStateFor(pindex); + // nHeight of the next block. If vpblock is empty, the next (ie first) + // block should be the genesis block with nHeight == 0. + int height = pindex == nullptr ? 0 : pindex->nHeight + 1; + BOOST_CHECK_MESSAGE(got == exp, strprintf("Test %i for %s height %d (got %s)", num, StateName(exp), height, StateName(got))); + BOOST_CHECK_MESSAGE(got_always == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE height %d (got %s; always active case)", num, height, StateName(got_always))); + BOOST_CHECK_MESSAGE(got_never == ThresholdState::DEFINED|| got_never == ThresholdState::FAILED, strprintf("Test %i for DEFINED/FAILED height %d (got %s; never active case)", num, height, StateName(got_never))); } } num++; return *this; } - VersionBitsTester& TestLockedIn() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::LOCKED_IN, strprintf("Test %i for LOCKED_IN", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } - - VersionBitsTester& TestActive() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } - - VersionBitsTester& TestFailed() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::FAILED, strprintf("Test %i for FAILED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } + VersionBitsTester& TestDefined() { return TestState(ThresholdState::DEFINED); } + VersionBitsTester& TestStarted() { return TestState(ThresholdState::STARTED); } + VersionBitsTester& TestLockedIn() { return TestState(ThresholdState::LOCKED_IN); } + VersionBitsTester& TestActive() { return TestState(ThresholdState::ACTIVE); } + VersionBitsTester& TestFailed() { return TestState(ThresholdState::FAILED); } CBlockIndex * Tip() { return vpblock.size() ? vpblock.back() : nullptr; } }; diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 605c77fc3a..6666e49a2b 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -12,6 +12,7 @@ #include <net.h> #include <netaddress.h> #include <netbase.h> +#include <util/readwritefile.h> #include <util/strencodings.h> #include <util/system.h> #include <util/time.h> @@ -55,77 +56,6 @@ static const int MAX_LINE_LENGTH = 100000; /****** Low-level TorControlConnection ********/ -/** Reply from Tor, can be single or multi-line */ -class TorControlReply -{ -public: - TorControlReply() { Clear(); } - - int code; - std::vector<std::string> lines; - - void Clear() - { - code = 0; - lines.clear(); - } -}; - -/** Low-level handling for Tor control connection. - * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt - */ -class TorControlConnection -{ -public: - typedef std::function<void(TorControlConnection&)> ConnectionCB; - typedef std::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB; - - /** Create a new TorControlConnection. - */ - explicit TorControlConnection(struct event_base *base); - ~TorControlConnection(); - - /** - * Connect to a Tor control port. - * tor_control_center is address of the form host:port. - * connected is the handler that is called when connection is successfully established. - * disconnected is a handler that is called when the connection is broken. - * Return true on success. - */ - bool Connect(const std::string& tor_control_center, const ConnectionCB& connected, const ConnectionCB& disconnected); - - /** - * Disconnect from Tor control port. - */ - void Disconnect(); - - /** Send a command, register a handler for the reply. - * A trailing CRLF is automatically added. - * Return true on success. - */ - bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); - - /** Response handlers for async replies */ - boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; -private: - /** Callback when ready for use */ - std::function<void(TorControlConnection&)> connected; - /** Callback when connection lost */ - std::function<void(TorControlConnection&)> disconnected; - /** Libevent event base */ - struct event_base *base; - /** Connection to control socket */ - struct bufferevent *b_conn; - /** Message being received */ - TorControlReply message; - /** Response handlers */ - std::deque<ReplyHandlerCB> reply_handlers; - - /** Libevent handlers: internal */ - static void readcb(struct bufferevent *bev, void *ctx); - static void eventcb(struct bufferevent *bev, short what, void *ctx); -}; - TorControlConnection::TorControlConnection(struct event_base *_base): base(_base), b_conn(nullptr) { @@ -362,101 +292,6 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) return mapping; } -/** Read full contents of a file and return them in a std::string. - * Returns a pair <status, string>. - * If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. - * - * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data - * (with len > maxsize) will be returned. - */ -static std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) -{ - FILE *f = fsbridge::fopen(filename, "rb"); - if (f == nullptr) - return std::make_pair(false,""); - std::string retval; - char buffer[128]; - size_t n; - while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) { - // Check for reading errors so we don't return any data if we couldn't - // read the entire file (or up to maxsize) - if (ferror(f)) { - fclose(f); - return std::make_pair(false,""); - } - retval.append(buffer, buffer+n); - if (retval.size() > maxsize) - break; - } - fclose(f); - return std::make_pair(true,retval); -} - -/** Write contents of std::string to a file. - * @return true on success. - */ -static bool WriteBinaryFile(const fs::path &filename, const std::string &data) -{ - FILE *f = fsbridge::fopen(filename, "wb"); - if (f == nullptr) - return false; - if (fwrite(data.data(), 1, data.size(), f) != data.size()) { - fclose(f); - return false; - } - fclose(f); - return true; -} - -/****** Bitcoin specific TorController implementation ********/ - -/** Controller that connects to Tor control socket, authenticate, then create - * and maintain an ephemeral onion service. - */ -class TorController -{ -public: - TorController(struct event_base* base, const std::string& tor_control_center, const CService& target); - ~TorController(); - - /** Get name of file to store private key in */ - fs::path GetPrivateKeyFile(); - - /** Reconnect, after getting disconnected */ - void Reconnect(); -private: - struct event_base* base; - const std::string m_tor_control_center; - TorControlConnection conn; - std::string private_key; - std::string service_id; - bool reconnect; - struct event *reconnect_ev; - float reconnect_timeout; - CService service; - const CService m_target; - /** Cookie for SAFECOOKIE auth */ - std::vector<uint8_t> cookie; - /** ClientNonce for SAFECOOKIE auth */ - std::vector<uint8_t> clientNonce; - - /** Callback for ADD_ONION result */ - void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for AUTHENTICATE result */ - void auth_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for AUTHCHALLENGE result */ - void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for PROTOCOLINFO result */ - void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback after successful connection */ - void connected_cb(TorControlConnection& conn); - /** Callback after connection lost or failed connection attempt */ - void disconnected_cb(TorControlConnection& conn); - - /** Callback for reconnect timer */ - static void reconnect_cb(evutil_socket_t fd, short what, void *arg); -}; - TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target): base(_base), m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0), diff --git a/src/torcontrol.h b/src/torcontrol.h index 00f19db6ae..7258f27cb6 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -8,7 +8,19 @@ #ifndef BITCOIN_TORCONTROL_H #define BITCOIN_TORCONTROL_H +#include <fs.h> +#include <netaddress.h> + +#include <boost/signals2/signal.hpp> + +#include <event2/bufferevent.h> +#include <event2/event.h> + +#include <cstdlib> +#include <deque> +#include <functional> #include <string> +#include <vector> class CService; @@ -21,4 +33,128 @@ void StopTorControl(); CService DefaultOnionServiceTarget(); +/** Reply from Tor, can be single or multi-line */ +class TorControlReply +{ +public: + TorControlReply() { Clear(); } + + int code; + std::vector<std::string> lines; + + void Clear() + { + code = 0; + lines.clear(); + } +}; + +/** Low-level handling for Tor control connection. + * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt + */ +class TorControlConnection +{ +public: + typedef std::function<void(TorControlConnection&)> ConnectionCB; + typedef std::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB; + + /** Create a new TorControlConnection. + */ + explicit TorControlConnection(struct event_base *base); + ~TorControlConnection(); + + /** + * Connect to a Tor control port. + * tor_control_center is address of the form host:port. + * connected is the handler that is called when connection is successfully established. + * disconnected is a handler that is called when the connection is broken. + * Return true on success. + */ + bool Connect(const std::string& tor_control_center, const ConnectionCB& connected, const ConnectionCB& disconnected); + + /** + * Disconnect from Tor control port. + */ + void Disconnect(); + + /** Send a command, register a handler for the reply. + * A trailing CRLF is automatically added. + * Return true on success. + */ + bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); + + /** Response handlers for async replies */ + boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; +private: + /** Callback when ready for use */ + std::function<void(TorControlConnection&)> connected; + /** Callback when connection lost */ + std::function<void(TorControlConnection&)> disconnected; + /** Libevent event base */ + struct event_base *base; + /** Connection to control socket */ + struct bufferevent *b_conn; + /** Message being received */ + TorControlReply message; + /** Response handlers */ + std::deque<ReplyHandlerCB> reply_handlers; + + /** Libevent handlers: internal */ + static void readcb(struct bufferevent *bev, void *ctx); + static void eventcb(struct bufferevent *bev, short what, void *ctx); +}; + +/****** Bitcoin specific TorController implementation ********/ + +/** Controller that connects to Tor control socket, authenticate, then create + * and maintain an ephemeral onion service. + */ +class TorController +{ +public: + TorController(struct event_base* base, const std::string& tor_control_center, const CService& target); + TorController() : conn{nullptr} { + // Used for testing only. + } + ~TorController(); + + /** Get name of file to store private key in */ + fs::path GetPrivateKeyFile(); + + /** Reconnect, after getting disconnected */ + void Reconnect(); +private: + struct event_base* base; + const std::string m_tor_control_center; + TorControlConnection conn; + std::string private_key; + std::string service_id; + bool reconnect; + struct event *reconnect_ev = nullptr; + float reconnect_timeout; + CService service; + const CService m_target; + /** Cookie for SAFECOOKIE auth */ + std::vector<uint8_t> cookie; + /** ClientNonce for SAFECOOKIE auth */ + std::vector<uint8_t> clientNonce; + +public: + /** Callback for ADD_ONION result */ + void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHENTICATE result */ + void auth_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHCHALLENGE result */ + void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for PROTOCOLINFO result */ + void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback after successful connection */ + void connected_cb(TorControlConnection& conn); + /** Callback after connection lost or failed connection attempt */ + void disconnected_cb(TorControlConnection& conn); + + /** Callback for reconnect timer */ + static void reconnect_cb(evutil_socket_t fd, short what, void *arg); +}; + #endif /* BITCOIN_TORCONTROL_H */ diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 899835019a..f10b4ad740 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -23,7 +23,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFe int64_t _nTime, unsigned int _entryHeight, bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp) : tx(_tx), nFee(_nFee), nTxWeight(GetTransactionWeight(*tx)), nUsageSize(RecursiveDynamicUsage(tx)), nTime(_nTime), entryHeight(_entryHeight), - spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp), m_epoch(0) + spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp) { nCountWithDescendants = 1; nSizeWithDescendants = GetTxSize(); @@ -132,7 +132,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes // include them, and update their CTxMemPoolEntry::m_parents to include this tx. // we cache the in-mempool children to avoid duplicate updates { - const auto epoch = GetFreshEpoch(); + WITH_FRESH_EPOCH(m_epoch); for (; iter != mapNextTx.end() && iter->first->hash == hash; ++iter) { const uint256 &childHash = iter->second->GetHash(); txiter childIter = mapTx.find(childHash); @@ -619,7 +619,7 @@ static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& m UpdateCoins(tx, mempoolDuplicate, std::numeric_limits<int>::max()); } -void CTxMemPool::check(const CCoinsViewCache *pcoins) const +void CTxMemPool::check(CChainState& active_chainstate) const { if (m_check_ratio == 0) return; @@ -633,8 +633,11 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const CAmount check_total_fee{0}; uint64_t innerUsage = 0; - CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(pcoins)); - const int64_t spendheight = g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate); + CCoinsViewCache& active_coins_tip = active_chainstate.CoinsTip(); + assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(active_coins_tip)); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT + CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&active_coins_tip)); + const int64_t spendheight = active_chainstate.m_chain.Height() + 1; + assert(g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate) == spendheight); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT std::list<const CTxMemPoolEntry*> waitingOnDependants; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { @@ -655,7 +658,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const fDependsWait = true; setParentCheck.insert(*it2); } else { - assert(pcoins->HaveCoin(txin.prevout)); + assert(active_coins_tip.HaveCoin(txin.prevout)); } // Check whether its inputs are marked in mapNextTx. auto it3 = mapNextTx.find(txin.prevout); @@ -1119,22 +1122,3 @@ void CTxMemPool::SetIsLoaded(bool loaded) LOCK(cs); m_is_loaded = loaded; } - - -CTxMemPool::EpochGuard CTxMemPool::GetFreshEpoch() const -{ - return EpochGuard(*this); -} -CTxMemPool::EpochGuard::EpochGuard(const CTxMemPool& in) : pool(in) -{ - assert(!pool.m_has_epoch_guard); - ++pool.m_epoch; - pool.m_has_epoch_guard = true; -} - -CTxMemPool::EpochGuard::~EpochGuard() -{ - // prevents stale results being used - ++pool.m_epoch; - pool.m_has_epoch_guard = false; -} diff --git a/src/txmempool.h b/src/txmempool.h index b8de326737..8a96294192 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -21,6 +21,7 @@ #include <primitives/transaction.h> #include <random.h> #include <sync.h> +#include <util/epochguard.h> #include <util/hasher.h> #include <boost/multi_index_container.hpp> @@ -64,6 +65,7 @@ struct CompareIteratorByHash { return a->GetTx().GetHash() < b->GetTx().GetHash(); } }; + /** \class CTxMemPoolEntry * * CTxMemPoolEntry stores data about the corresponding transaction, as well @@ -156,7 +158,7 @@ public: Children& GetMemPoolChildren() const { return m_children; } mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes - mutable uint64_t m_epoch; //!< epoch when last touched, useful for graph algorithms + mutable Epoch::Marker m_epoch_marker; //!< epoch when last touched, useful for graph algorithms }; // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. @@ -486,8 +488,7 @@ private: mutable int64_t lastRollingFeeUpdate; mutable bool blockSinceLastRollingFeeBump; mutable double rollingMinimumFeeRate; //!< minimum fee to get into the pool, decreases exponentially - mutable uint64_t m_epoch{0}; - mutable bool m_has_epoch_guard{false}; + mutable Epoch m_epoch GUARDED_BY(cs); // In-memory counter for external mempool tracking purposes. // This number is incremented once every time a transaction @@ -604,7 +605,7 @@ public: * all inputs are in the mapNextTx array). If sanity-checking is turned off, * check does nothing. */ - void check(const CCoinsViewCache *pcoins) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void check(CChainState& active_chainstate) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); // addUnchecked must updated state for all ancestors of a given transaction, // to track size/count of descendant transactions. First version of @@ -666,7 +667,7 @@ public: * for). Note: vHashesToUpdate should be the set of transactions from the * disconnected block that have been accepted back into the mempool. */ - void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); + void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch); /** Try to calculate all in-mempool ancestors of entry. * (these are all calculated including the tx itself) @@ -827,52 +828,22 @@ private: */ void removeUnchecked(txiter entry, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); public: - /** EpochGuard: RAII-style guard for using epoch-based graph traversal algorithms. - * When walking ancestors or descendants, we generally want to avoid - * visiting the same transactions twice. Some traversal algorithms use - * std::set (or setEntries) to deduplicate the transaction we visit. - * However, use of std::set is algorithmically undesirable because it both - * adds an asymptotic factor of O(log n) to traverals cost and triggers O(n) - * more dynamic memory allocations. - * In many algorithms we can replace std::set with an internal mempool - * counter to track the time (or, "epoch") that we began a traversal, and - * check + update a per-transaction epoch for each transaction we look at to - * determine if that transaction has not yet been visited during the current - * traversal's epoch. - * Algorithms using std::set can be replaced on a one by one basis. - * Both techniques are not fundamentally incompatible across the codebase. - * Generally speaking, however, the remaining use of std::set for mempool - * traversal should be viewed as a TODO for replacement with an epoch based - * traversal, rather than a preference for std::set over epochs in that - * algorithm. - */ - class EpochGuard { - const CTxMemPool& pool; - public: - explicit EpochGuard(const CTxMemPool& in); - ~EpochGuard(); - }; - // N.B. GetFreshEpoch modifies mutable state via the EpochGuard construction - // (and later destruction) - EpochGuard GetFreshEpoch() const EXCLUSIVE_LOCKS_REQUIRED(cs); - /** visited marks a CTxMemPoolEntry as having been traversed - * during the lifetime of the most recently created EpochGuard + * during the lifetime of the most recently created Epoch::Guard * and returns false if we are the first visitor, true otherwise. * - * An EpochGuard must be held when visited is called or an assert will be + * An Epoch::Guard must be held when visited is called or an assert will be * triggered. * */ - bool visited(txiter it) const EXCLUSIVE_LOCKS_REQUIRED(cs) { - assert(m_has_epoch_guard); - bool ret = it->m_epoch >= m_epoch; - it->m_epoch = std::max(it->m_epoch, m_epoch); - return ret; + bool visited(const txiter it) const EXCLUSIVE_LOCKS_REQUIRED(cs, m_epoch) + { + return m_epoch.visited(it->m_epoch_marker); } - bool visited(Optional<txiter> it) const EXCLUSIVE_LOCKS_REQUIRED(cs) { - assert(m_has_epoch_guard); + bool visited(Optional<txiter> it) const EXCLUSIVE_LOCKS_REQUIRED(cs, m_epoch) + { + assert(m_epoch.guarded()); // verify guard even when it==nullopt return !it || visited(*it); } }; diff --git a/src/txorphanage.cpp b/src/txorphanage.cpp new file mode 100644 index 0000000000..ed4783f1a5 --- /dev/null +++ b/src/txorphanage.cpp @@ -0,0 +1,202 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <txorphanage.h> + +#include <consensus/validation.h> +#include <logging.h> +#include <policy/policy.h> + +#include <cassert> + +/** Expiration time for orphan transactions in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; +/** Minimum time between orphan transactions expire time checks in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; + +RecursiveMutex g_cs_orphans; + +bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) +{ + AssertLockHeld(g_cs_orphans); + + const uint256& hash = tx->GetHash(); + if (m_orphans.count(hash)) + return false; + + // Ignore big transactions, to avoid a + // send-big-orphans memory exhaustion attack. If a peer has a legitimate + // large transaction with a missing parent then we assume + // it will rebroadcast it later, after the parent transaction(s) + // have been mined or received. + // 100 orphans, each of which is at most 100,000 bytes big is + // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): + unsigned int sz = GetTransactionWeight(*tx); + if (sz > MAX_STANDARD_TX_WEIGHT) + { + LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); + return false; + } + + auto ret = m_orphans.emplace(hash, 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::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(), + m_orphans.size(), m_outpoint_to_orphan_it.size()); + return true; +} + +int TxOrphanage::EraseTx(const uint256& txid) +{ + AssertLockHeld(g_cs_orphans); + std::map<uint256, OrphanTx>::iterator it = m_orphans.find(txid); + if (it == m_orphans.end()) + return 0; + for (const CTxIn& txin : it->second.tx->vin) + { + auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout); + if (itPrev == m_outpoint_to_orphan_it.end()) + continue; + itPrev->second.erase(it); + if (itPrev->second.empty()) + m_outpoint_to_orphan_it.erase(itPrev); + } + + size_t old_pos = it->second.list_pos; + assert(m_orphan_list[old_pos] == it); + if (old_pos + 1 != m_orphan_list.size()) { + // Unless we're deleting the last entry in m_orphan_list, move the last + // entry to the position we're deleting. + auto it_last = m_orphan_list.back(); + m_orphan_list[old_pos] = it_last; + it_last->second.list_pos = old_pos; + } + m_orphan_list.pop_back(); + m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash()); + + m_orphans.erase(it); + return 1; +} + +void TxOrphanage::EraseForPeer(NodeId peer) +{ + AssertLockHeld(g_cs_orphans); + + int nErased = 0; + std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin(); + while (iter != m_orphans.end()) + { + std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid + if (maybeErase->second.fromPeer == peer) + { + nErased += EraseTx(maybeErase->second.tx->GetHash()); + } + } + if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); +} + +unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans) +{ + AssertLockHeld(g_cs_orphans); + + unsigned int nEvicted = 0; + static int64_t nNextSweep; + int64_t nNow = GetTime(); + if (nNextSweep <= nNow) { + // Sweep out expired orphan pool entries: + int nErased = 0; + int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; + std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin(); + while (iter != m_orphans.end()) + { + std::map<uint256, OrphanTx>::iterator maybeErase = iter++; + if (maybeErase->second.nTimeExpire <= nNow) { + nErased += EraseTx(maybeErase->second.tx->GetHash()); + } else { + nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); + } + } + // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan. + nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; + if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); + } + FastRandomContext rng; + while (m_orphans.size() > max_orphans) + { + // Evict a random orphan: + size_t randompos = rng.randrange(m_orphan_list.size()); + EraseTx(m_orphan_list[randompos]->first); + ++nEvicted; + } + return nEvicted; +} + +void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const +{ + AssertLockHeld(g_cs_orphans); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i)); + if (it_by_prev != m_outpoint_to_orphan_it.end()) { + for (const auto& elem : it_by_prev->second) { + orphan_work_set.insert(elem->first); + } + } + } +} + +bool TxOrphanage::HaveTx(const GenTxid& gtxid) const +{ + LOCK(g_cs_orphans); + if (gtxid.IsWtxid()) { + return m_wtxid_to_orphan_it.count(gtxid.GetHash()); + } else { + return m_orphans.count(gtxid.GetHash()); + } +} + +std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const uint256& txid) const +{ + AssertLockHeld(g_cs_orphans); + + const auto it = m_orphans.find(txid); + if (it == m_orphans.end()) return {nullptr, -1}; + return {it->second.tx, it->second.fromPeer}; +} + +void TxOrphanage::EraseForBlock(const CBlock& block) +{ + LOCK(g_cs_orphans); + + std::vector<uint256> vOrphanErase; + + for (const CTransactionRef& ptx : block.vtx) { + const CTransaction& tx = *ptx; + + // Which orphan pool entries must we evict? + for (const auto& txin : tx.vin) { + auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout); + 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 uint256& orphanHash = orphanTx.GetHash(); + vOrphanErase.push_back(orphanHash); + } + } + } + + // Erase orphan transactions included or precluded by this block + if (vOrphanErase.size()) { + int nErased = 0; + for (const uint256& orphanHash : vOrphanErase) { + nErased += EraseTx(orphanHash); + } + LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); + } +} diff --git a/src/txorphanage.h b/src/txorphanage.h new file mode 100644 index 0000000000..df55cdb3be --- /dev/null +++ b/src/txorphanage.h @@ -0,0 +1,85 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TXORPHANAGE_H +#define BITCOIN_TXORPHANAGE_H + +#include <net.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <sync.h> + +/** Guards orphan transactions and extra txs for compact blocks */ +extern RecursiveMutex g_cs_orphans; + +/** A class to track orphan transactions (failed on TX_MISSING_INPUTS) + * Since we cannot distinguish orphans from bad transactions with + * non-existent inputs, we heavily limit the number of orphans + * we keep and the duration we keep them for. + */ +class TxOrphanage { +public: + /** Add a new orphan transaction */ + bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Check if we already have an orphan transaction (by txid or wtxid) */ + bool HaveTx(const GenTxid& gtxid) const EXCLUSIVE_LOCKS_REQUIRED(!g_cs_orphans); + + /** Get an orphan transaction and its orginating peer + * (Transaction ref will be nullptr if not found) + */ + std::pair<CTransactionRef, NodeId> GetTx(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase an orphan by txid */ + int EraseTx(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase all orphans announced by a peer (eg, after that peer disconnects) */ + void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase all orphans included in or invalidated by a new block */ + void EraseForBlock(const CBlock& block) EXCLUSIVE_LOCKS_REQUIRED(!g_cs_orphans); + + /** Limit the orphanage to the given maximum */ + unsigned int LimitOrphans(unsigned int max_orphans) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Add any orphans that list a particular tx as a parent into a peer's work set + * (ie orphans that may have found their final missing parent, and so should be reconsidered for the mempool) */ + void AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + +protected: + struct OrphanTx { + CTransactionRef tx; + NodeId fromPeer; + int64_t nTimeExpire; + size_t list_pos; + }; + + /** Map from txid to orphan transaction record. Limited by + * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ + std::map<uint256, OrphanTx> m_orphans GUARDED_BY(g_cs_orphans); + + using OrphanMap = decltype(m_orphans); + + struct IteratorComparator + { + template<typename I> + bool operator()(const I& a, const I& b) const + { + return &(*a) < &(*b); + } + }; + + /** Index from the parents' COutPoint into the m_orphans. Used + * to remove orphan transactions from the m_orphans */ + std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it GUARDED_BY(g_cs_orphans); + + /** Orphan transactions in vector for quick random eviction */ + std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(g_cs_orphans); + + /** Index from wtxid into the m_orphans to lookup orphan + * transactions using their witness ids. */ + std::map<uint256, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(g_cs_orphans); +}; + +#endif // BITCOIN_TXORPHANAGE_H diff --git a/src/util/epochguard.h b/src/util/epochguard.h new file mode 100644 index 0000000000..1570ec4eb4 --- /dev/null +++ b/src/util/epochguard.h @@ -0,0 +1,91 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_EPOCHGUARD_H +#define BITCOIN_UTIL_EPOCHGUARD_H + +#include <threadsafety.h> + +#include <cassert> + +/** Epoch: RAII-style guard for using epoch-based graph traversal algorithms. + * When walking ancestors or descendants, we generally want to avoid + * visiting the same transactions twice. Some traversal algorithms use + * std::set (or setEntries) to deduplicate the transaction we visit. + * However, use of std::set is algorithmically undesirable because it both + * adds an asymptotic factor of O(log n) to traversals cost and triggers O(n) + * more dynamic memory allocations. + * In many algorithms we can replace std::set with an internal mempool + * counter to track the time (or, "epoch") that we began a traversal, and + * check + update a per-transaction epoch for each transaction we look at to + * determine if that transaction has not yet been visited during the current + * traversal's epoch. + * Algorithms using std::set can be replaced on a one by one basis. + * Both techniques are not fundamentally incompatible across the codebase. + * Generally speaking, however, the remaining use of std::set for mempool + * traversal should be viewed as a TODO for replacement with an epoch based + * traversal, rather than a preference for std::set over epochs in that + * algorithm. + */ + +class LOCKABLE Epoch +{ +private: + uint64_t m_raw_epoch = 0; + bool m_guarded = false; + +public: + Epoch() = default; + Epoch(const Epoch&) = delete; + Epoch& operator=(const Epoch&) = delete; + + bool guarded() const { return m_guarded; } + + class Marker + { + private: + uint64_t m_marker = 0; + + // only allow modification via Epoch member functions + friend class Epoch; + Marker& operator=(const Marker&) = delete; + }; + + class SCOPED_LOCKABLE Guard + { + private: + Epoch& m_epoch; + + public: + explicit Guard(Epoch& epoch) EXCLUSIVE_LOCK_FUNCTION(epoch) : m_epoch(epoch) + { + assert(!m_epoch.m_guarded); + ++m_epoch.m_raw_epoch; + m_epoch.m_guarded = true; + } + ~Guard() UNLOCK_FUNCTION() + { + assert(m_epoch.m_guarded); + ++m_epoch.m_raw_epoch; // ensure clear separation between epochs + m_epoch.m_guarded = false; + } + }; + + bool visited(Marker& marker) const EXCLUSIVE_LOCKS_REQUIRED(*this) + { + assert(m_guarded); + if (marker.m_marker < m_raw_epoch) { + // marker is from a previous epoch, so this is its first visit + marker.m_marker = m_raw_epoch; + return false; + } else { + return true; + } + } +}; + +#define WITH_FRESH_EPOCH(epoch) const Epoch::Guard PASTE2(epoch_guard_, __COUNTER__)(epoch) + +#endif // BITCOIN_UTIL_EPOCHGUARD_H diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 1bc8d02eab..3f9ce7dce4 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -9,13 +9,17 @@ #include <util/strencodings.h> #include <util/string.h> -std::string FormatMoney(const CAmount& n) +std::string FormatMoney(const CAmount n) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. - int64_t n_abs = (n > 0 ? n : -n); - int64_t quotient = n_abs/COIN; - int64_t remainder = n_abs%COIN; + static_assert(COIN > 1); + int64_t quotient = n / COIN; + int64_t remainder = n % COIN; + if (n < 0) { + quotient = -quotient; + remainder = -remainder; + } std::string str = strprintf("%d.%08d", quotient, remainder); // Right-trim excess zeros before the decimal point: diff --git a/src/util/moneystr.h b/src/util/moneystr.h index da7f673cda..2aedbee358 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -17,7 +17,7 @@ /* Do not use these functions to represent or parse monetary amounts to or from * JSON but use AmountFromValue and ValueFromAmount for that. */ -std::string FormatMoney(const CAmount& n); +std::string FormatMoney(const CAmount n); /** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/ [[nodiscard]] bool ParseMoney(const std::string& str, CAmount& nRet); diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp new file mode 100644 index 0000000000..a45c41d367 --- /dev/null +++ b/src/util/readwritefile.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2015-2020 The Bitcoin Core developers +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <fs.h> + +#include <limits> +#include <stdio.h> +#include <string> +#include <utility> + +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) +{ + FILE *f = fsbridge::fopen(filename, "rb"); + if (f == nullptr) + return std::make_pair(false,""); + std::string retval; + char buffer[128]; + do { + const size_t n = fread(buffer, 1, sizeof(buffer), f); + // Check for reading errors so we don't return any data if we couldn't + // read the entire file (or up to maxsize) + if (ferror(f)) { + fclose(f); + return std::make_pair(false,""); + } + retval.append(buffer, buffer+n); + } while (!feof(f) && retval.size() <= maxsize); + fclose(f); + return std::make_pair(true,retval); +} + +bool WriteBinaryFile(const fs::path &filename, const std::string &data) +{ + FILE *f = fsbridge::fopen(filename, "wb"); + if (f == nullptr) + return false; + if (fwrite(data.data(), 1, data.size(), f) != data.size()) { + fclose(f); + return false; + } + if (fclose(f) != 0) { + return false; + } + return true; +} diff --git a/src/util/readwritefile.h b/src/util/readwritefile.h new file mode 100644 index 0000000000..1dab874b38 --- /dev/null +++ b/src/util/readwritefile.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_READWRITEFILE_H +#define BITCOIN_UTIL_READWRITEFILE_H + +#include <fs.h> + +#include <limits> +#include <string> +#include <utility> + +/** Read full contents of a file and return them in a std::string. + * Returns a pair <status, string>. + * If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. + * + * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data + * (with len > maxsize) will be returned. + */ +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()); + +/** Write contents of std::string to a file. + * @return true on success. + */ +bool WriteBinaryFile(const fs::path &filename, const std::string &data); + +#endif /* BITCOIN_UTIL_READWRITEFILE_H */ diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 4c65b5b680..e13c52a16a 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -4,6 +4,7 @@ #include <compat.h> #include <logging.h> +#include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> #include <util/system.h> @@ -12,12 +13,18 @@ #include <codecvt> #include <cwchar> #include <locale> +#include <stdexcept> #include <string> #ifdef USE_POLL #include <poll.h> #endif +static inline bool IOErrorIsPermanent(int err) +{ + return err != WSAEAGAIN && err != WSAEINTR && err != WSAEWOULDBLOCK && err != WSAEINPROGRESS; +} + Sock::Sock() : m_socket(INVALID_SOCKET) {} Sock::Sock(SOCKET s) : m_socket(s) {} @@ -59,7 +66,7 @@ ssize_t Sock::Recv(void* buf, size_t len, int flags) const return recv(m_socket, static_cast<char*>(buf), len, flags); } -bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const +bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { #ifdef USE_POLL pollfd fd; @@ -72,7 +79,21 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const fd.events |= POLLOUT; } - return poll(&fd, 1, count_milliseconds(timeout)) != SOCKET_ERROR; + if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (fd.revents & POLLIN) { + *occurred |= RECV; + } + if (fd.revents & POLLOUT) { + *occurred |= SEND; + } + } + + return true; #else if (!IsSelectableSocket(m_socket)) { return false; @@ -93,10 +114,167 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const timeval timeout_struct = MillisToTimeval(timeout); - return select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) != SOCKET_ERROR; + if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (FD_ISSET(m_socket, &fdset_recv)) { + *occurred |= RECV; + } + if (FD_ISSET(m_socket, &fdset_send)) { + *occurred |= SEND; + } + } + + return true; #endif /* USE_POLL */ } +void Sock::SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + size_t sent{0}; + + for (;;) { + const ssize_t ret{Send(data.data() + sent, data.size() - sent, MSG_NOSIGNAL)}; + + if (ret > 0) { + sent += static_cast<size_t>(ret); + if (sent == data.size()) { + break; + } + } else { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("send(): %s", NetworkErrorString(err))); + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Send timeout (sent only %u of %u bytes before that)", sent, data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Send interrupted (sent only %u of %u bytes before that)", sent, data.size())); + } + + // Wait for a short while (or the socket to become ready for sending) before retrying + // if nothing was sent. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, SEND); + } +} + +std::string Sock::RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + std::string data; + bool terminator_found{false}; + + // We must not consume any bytes past the terminator from the socket. + // One option is to read one byte at a time and check if we have read a terminator. + // However that is very slow. Instead, we peek at what is in the socket and only read + // as many bytes as possible without crossing the terminator. + // Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read + // one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte + // at a time is about 50 times slower. + + for (;;) { + char buf[512]; + + const ssize_t peek_ret{Recv(buf, sizeof(buf), MSG_PEEK)}; + + switch (peek_ret) { + case -1: { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); + } + break; + } + case 0: + throw std::runtime_error("Connection unexpectedly closed by peer"); + default: + auto end = buf + peek_ret; + auto terminator_pos = std::find(buf, end, terminator); + terminator_found = terminator_pos != end; + + const size_t try_len{terminator_found ? terminator_pos - buf + 1 : + static_cast<size_t>(peek_ret)}; + + const ssize_t read_ret{Recv(buf, try_len, 0)}; + + if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { + throw std::runtime_error( + strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " + "peek claimed %u bytes are available", + read_ret, try_len, peek_ret)); + } + + // Don't include the terminator in the output. + const size_t append_len{terminator_found ? try_len - 1 : try_len}; + + data.append(buf, buf + append_len); + + if (terminator_found) { + return data; + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Receive timeout (received %u bytes without terminator before that)", data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Receive interrupted (received %u bytes without terminator before that)", + data.size())); + } + + // Wait for a short while (or the socket to become ready for reading) before retrying. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, RECV); + } +} + +bool Sock::IsConnected(std::string& errmsg) const +{ + if (m_socket == INVALID_SOCKET) { + errmsg = "not connected"; + return false; + } + + char c; + switch (Recv(&c, sizeof(c), MSG_PEEK)) { + case -1: { + const int err = WSAGetLastError(); + if (IOErrorIsPermanent(err)) { + errmsg = NetworkErrorString(err); + return false; + } + return true; + } + case 0: + errmsg = "closed"; + return false; + default: + return true; + } +} + #ifdef WIN32 std::string NetworkErrorString(int err) { diff --git a/src/util/sock.h b/src/util/sock.h index 26fe60f18f..ecebb84205 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -6,11 +6,19 @@ #define BITCOIN_UTIL_SOCK_H #include <compat.h> +#include <threadinterrupt.h> +#include <util/time.h> #include <chrono> #include <string> /** + * Maximum time to wait for I/O readiness. + * It will take up until this time to break off in case of an interruption. + */ +static constexpr auto MAX_WAIT_FOR_IO = 1s; + +/** * RAII helper class that manages a socket. Mimics `std::unique_ptr`, but instead of a pointer it * contains a socket and closes it automatically when it goes out of scope. */ @@ -98,9 +106,49 @@ public: * Wait for readiness for input (recv) or output (send). * @param[in] timeout Wait this much for at least one of the requested events to occur. * @param[in] requested Wait for those events, bitwise-or of `RECV` and `SEND`. + * @param[out] occurred If not nullptr and `true` is returned, then upon return this + * indicates which of the requested events occurred. A timeout is indicated by return + * value of `true` and `occurred` being set to 0. * @return true on success and false otherwise */ - virtual bool Wait(std::chrono::milliseconds timeout, Event requested) const; + virtual bool Wait(std::chrono::milliseconds timeout, + Event requested, + Event* occurred = nullptr) const; + + /* Higher level, convenience, methods. These may throw. */ + + /** + * Send the given data, retrying on transient errors. + * @param[in] data Data to send. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @throws std::runtime_error if the operation cannot be completed. In this case only some of + * the data will be written to the socket. + */ + virtual void SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const; + + /** + * Read from socket until a terminator character is encountered. Will never consume bytes past + * the terminator from the socket. + * @param[in] terminator Character up to which to read from the socket. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @return The data that has been read, without the terminating character. + * @throws std::runtime_error if the operation cannot be completed. In this case some bytes may + * have been consumed from the socket. + */ + virtual std::string RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const; + + /** + * Check if still connected. + * @param[out] err The error string, if the socket has been disconnected. + * @return true if connected + */ + virtual bool IsConnected(std::string& errmsg) const; private: /** diff --git a/src/util/time.h b/src/util/time.h index 56131ce0fe..7ebcaaa339 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -26,9 +26,16 @@ void UninterruptibleSleep(const std::chrono::microseconds& n); * This helper is used to convert durations before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ -inline int64_t count_seconds(std::chrono::seconds t) { return t.count(); } -inline int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } -inline int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } +constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } +constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } +constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } + +using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>; + +/** + * Helper to count the seconds in any std::chrono::duration type + */ +inline double CountSecondsDouble(SecondsDouble t) { return t.count(); } /** * DEPRECATED diff --git a/src/validation.cpp b/src/validation.cpp index 0b2ca4b422..0c3aeced93 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -211,6 +211,7 @@ static FlatFileSeq UndoFileSeq(); bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags) { AssertLockHeld(cs_main); + assert(active_chain_tip); // TODO: Make active_chain_tip a reference assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*active_chain_tip)); // By convention a negative value for flags indicates that the @@ -221,12 +222,12 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i // scheduled, so no flags are set. flags = std::max(flags, 0); - // CheckFinalTx() uses ::ChainActive().Height()+1 to evaluate + // CheckFinalTx() uses active_chain_tip.Height()+1 to evaluate // nLockTime because when IsFinalTx() is called within // CBlock::AcceptBlock(), the height of the block *being* // evaluated is what is used. Thus if we want to know if a // transaction can be part of the *next* block, we need to call - // IsFinalTx() with one more than ::ChainActive().Height(). + // IsFinalTx() with one more than active_chain_tip.Height(). const int nBlockHeight = active_chain_tip->nHeight + 1; // BIP113 requires that time-locked transactions have nLockTime set to @@ -1362,16 +1363,18 @@ static void AlertNotify(const std::string& strMessage) #endif } -static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void CChainState::CheckForkWarningConditions() { AssertLockHeld(cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + // Before we get past initial download, we cannot reliably alert about forks // (we assume we don't get stuck on a fork before finishing our initial sync) - if (::ChainstateActive().IsInitialBlockDownload()) { + if (IsInitialBlockDownload()) { return; } - if (pindexBestInvalid && pindexBestInvalid->nChainWork > ::ChainActive().Tip()->nChainWork + (GetBlockProof(*::ChainActive().Tip()) * 6)) { + if (pindexBestInvalid && pindexBestInvalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) { LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n", __func__); SetfLargeWorkInvalidChainFound(true); } else { @@ -1380,21 +1383,22 @@ static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) } // Called both upon regular invalid block discovery *and* InvalidateBlock -void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void CChainState::InvalidChainFound(CBlockIndex* pindexNew) { + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork) pindexBestInvalid = pindexNew; if (pindexBestHeader != nullptr && pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) { - pindexBestHeader = ::ChainActive().Tip(); + pindexBestHeader = m_chain.Tip(); } LogPrintf("%s: invalid block=%s height=%d log2_work=%f date=%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, log(pindexNew->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(pindexNew->GetBlockTime())); - CBlockIndex *tip = ::ChainActive().Tip(); + CBlockIndex *tip = m_chain.Tip(); assert (tip); LogPrintf("%s: current best=%s height=%d log2_work=%f date=%s\n", __func__, - tip->GetBlockHash().ToString(), ::ChainActive().Height(), log(tip->nChainWork.getdouble())/log(2.0), + tip->GetBlockHash().ToString(), m_chain.Height(), log(tip->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(tip->GetBlockTime())); CheckForkWarningConditions(); } @@ -2432,7 +2436,7 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn) } /** Check warning conditions and do some notifications on new chain tip set. */ -static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams) +static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams, CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { // New best block @@ -2445,7 +2449,8 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } bilingual_str warning_messages; - if (!::ChainstateActive().IsInitialBlockDownload()) { + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (!active_chainstate.IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); @@ -2460,11 +2465,12 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } } } + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(), + GuessVerificationProgress(chainParams.TxData(), pindexNew), active_chainstate.CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), active_chainstate.CoinsTip().GetCacheSize(), !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : ""); } @@ -2520,7 +2526,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& m_chain.SetTip(pindexDelete->pprev); - UpdateTip(m_mempool, pindexDelete->pprev, chainparams); + UpdateTip(m_mempool, pindexDelete->pprev, chainparams, *this); // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: GetMainSignals().BlockDisconnected(pblock, pindexDelete); @@ -2628,7 +2634,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch disconnectpool.removeForBlock(blockConnecting.vtx); // Update m_chain & related variables. m_chain.SetTip(pindexNew); - UpdateTip(m_mempool, pindexNew, chainparams); + UpdateTip(m_mempool, pindexNew, chainparams, *this); int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal); @@ -2719,6 +2725,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai { AssertLockHeld(cs_main); AssertLockHeld(m_mempool.cs); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); const CBlockIndex* pindexOldTip = m_chain.Tip(); const CBlockIndex* pindexFork = m_chain.FindFork(pindexMostWork); @@ -2730,7 +2737,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai if (!DisconnectTip(state, chainparams, &disconnectpool)) { // This is likely a fatal error, but keep the mempool consistent, // just in case. Only remove from the mempool in this case. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); // If we're unable to disconnect a block during normal operation, // then that is a failure of our local system -- we should abort @@ -2774,7 +2781,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai // A system error occurred (disk space, database error, ...). // Make the mempool consistent with the current tip, just in case // any observers try to use it before shutdown. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); return false; } } else { @@ -2791,9 +2798,9 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai if (fBlocksDisconnected) { // If any blocks were disconnected, disconnectpool may be non empty. Add // any disconnected transactions back to the mempool. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, true); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, true); } - m_mempool.check(&CoinsTip()); + m_mempool.check(*this); CheckForkWarningConditions(); @@ -2964,9 +2971,6 @@ bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& return ActivateBestChain(state, params, std::shared_ptr<const CBlock>()); } -bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) { - return ::ChainstateActive().PreciousBlock(state, params, pindex); -} bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { @@ -3028,7 +3032,8 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam // transactions back to the mempool if disconnecting was successful, // and we're not doing a very deep invalidation (in which case // keeping the mempool up to date is probably futile anyway). - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); if (!ret) return false; assert(invalid_walk_tip->pprev == m_chain.Tip()); @@ -3104,10 +3109,6 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam return true; } -bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { - return ::ChainstateActive().InvalidateBlock(state, chainparams, pindex); -} - void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { AssertLockHeld(cs_main); @@ -3142,10 +3143,6 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { } } -void ResetBlockFailureFlags(CBlockIndex *pindex) { - return ::ChainstateActive().ResetBlockFailureFlags(pindex); -} - CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) { AssertLockHeld(cs_main); @@ -3229,7 +3226,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi } } -static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false) +static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown = false) { LOCK(cs_LastBlockFile); @@ -3244,7 +3241,8 @@ static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int n // when the undo file is keeping up with the block file, we want to flush it explicitly // when it is lagging behind (more blocks arrive than are being connected), we let the // undo block write case handle it - finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)ChainActive().Tip()->nHeight); + assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); + finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight); nFile++; if (vinfoBlockFile.size() <= nFile) { vinfoBlockFile.resize(nFile + 1); @@ -3706,12 +3704,12 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -static FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, const CChainParams& chainparams, const FlatFilePos* dbp) { +static FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp) { unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION); FlatFilePos blockPos; if (dbp != nullptr) blockPos = *dbp; - if (!FindBlockPos(blockPos, nBlockSize+8, nHeight, block.GetBlockTime(), dbp != nullptr)) { + if (!FindBlockPos(blockPos, nBlockSize+8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) { error("%s: FindBlockPos failed", __func__); return FlatFilePos(); } @@ -3789,8 +3787,9 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // Write block to history file if (fNewBlock) *fNewBlock = true; + assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { - FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp); + FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, chainparams, dbp); if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; @@ -3961,11 +3960,12 @@ void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nM } /* This function is called from the RPC code for pruneblockchain */ -void PruneBlockFilesManual(int nManualPruneHeight) +void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight) { BlockValidationState state; const CChainParams& chainparams = Params(); - if (!::ChainstateActive().FlushStateToDisk( + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (!active_chainstate.FlushStateToDisk( chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); } @@ -4140,11 +4140,12 @@ void BlockManager::Unload() { m_block_index.clear(); } -bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) { - if (!chainman.m_blockman.LoadBlockIndex( + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + if (!m_blockman.LoadBlockIndex( chainparams.GetConsensus(), *pblocktree, - ::ChainstateActive().setBlockIndexCandidates)) { + setBlockIndexCandidates)) { return false; } @@ -4168,7 +4169,7 @@ bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& ch // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) { + for (const std::pair<const uint256, CBlockIndex*>& item : m_blockman.m_block_index) { CBlockIndex* pindex = item.second; if (pindex->nStatus & BLOCK_HAVE_DATA) { setBlkDataFiles.insert(pindex->nFile); @@ -4242,15 +4243,17 @@ CVerifyDB::~CVerifyDB() uiInterface.ShowProgress("", 100, false); } -bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) +bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CChainState& active_chainstate, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) { - LOCK(cs_main); - if (::ChainActive().Tip() == nullptr || ::ChainActive().Tip()->pprev == nullptr) + AssertLockHeld(cs_main); + + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (active_chainstate.m_chain.Tip() == nullptr || active_chainstate.m_chain.Tip()->pprev == nullptr) return true; // Verify blocks in the best chain - if (nCheckDepth <= 0 || nCheckDepth > ::ChainActive().Height()) - nCheckDepth = ::ChainActive().Height(); + if (nCheckDepth <= 0 || nCheckDepth > active_chainstate.m_chain.Height()) + nCheckDepth = active_chainstate.m_chain.Height(); nCheckLevel = std::max(0, std::min(4, nCheckLevel)); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); CCoinsViewCache coins(coinsview); @@ -4260,15 +4263,15 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, BlockValidationState state; int reportDone = 0; LogPrintf("[0%%]..."); /* Continued */ - for (pindex = ::ChainActive().Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { - const int percentageDone = std::max(1, std::min(99, (int)(((double)(::ChainActive().Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); + for (pindex = active_chainstate.m_chain.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { + const int percentageDone = std::max(1, std::min(99, (int)(((double)(active_chainstate.m_chain.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); if (reportDone < percentageDone/10) { // report every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); - if (pindex->nHeight <= ::ChainActive().Height()-nCheckDepth) + if (pindex->nHeight <= active_chainstate.m_chain.Height()-nCheckDepth) break; if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { // If pruning, only go back as far as we have data. @@ -4293,9 +4296,9 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, } } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks - if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= ::ChainstateActive().m_coinstip_cache_size_bytes) { + if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + active_chainstate.CoinsTip().DynamicMemoryUsage()) <= active_chainstate.m_coinstip_cache_size_bytes) { assert(coins.GetBestBlock() == pindex->GetBlockHash()); - DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins); + DisconnectResult res = active_chainstate.DisconnectBlock(block, pindex, coins); if (res == DISCONNECT_FAILED) { return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); } @@ -4309,26 +4312,26 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (ShutdownRequested()) return true; } if (pindexFailure) - return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", ::ChainActive().Height() - pindexFailure->nHeight + 1, nGoodTransactions); + return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", active_chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions); // store block count as we move pindex at check level >= 4 - int block_count = ::ChainActive().Height() - pindex->nHeight; + int block_count = active_chainstate.m_chain.Height() - pindex->nHeight; // check level 4: try reconnecting blocks if (nCheckLevel >= 4) { - while (pindex != ::ChainActive().Tip()) { - const int percentageDone = std::max(1, std::min(99, 100 - (int)(((double)(::ChainActive().Height() - pindex->nHeight)) / (double)nCheckDepth * 50))); + while (pindex != active_chainstate.m_chain.Tip()) { + const int percentageDone = std::max(1, std::min(99, 100 - (int)(((double)(active_chainstate.m_chain.Height() - pindex->nHeight)) / (double)nCheckDepth * 50))); if (reportDone < percentageDone/10) { // report every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); - pindex = ::ChainActive().Next(pindex); + pindex = active_chainstate.m_chain.Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); - if (!::ChainstateActive().ConnectBlock(block, state, pindex, coins, chainparams)) + if (!active_chainstate.ConnectBlock(block, state, pindex, coins, chainparams)) return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); if (ShutdownRequested()) return true; } @@ -4599,7 +4602,7 @@ bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams) // Load block index from databases bool needs_init = fReindex; if (!fReindex) { - bool ret = LoadBlockIndexDB(*this, chainparams); + bool ret = ActiveChainstate().LoadBlockIndexDB(chainparams); if (!ret) return false; needs_init = m_blockman.m_block_index.empty(); } @@ -4627,9 +4630,10 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) return true; + assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { const CBlock& block = chainparams.GenesisBlock(); - FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr); + FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr); if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); @@ -4641,11 +4645,6 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) return true; } -bool LoadGenesisBlock(const CChainParams& chainparams) -{ - return ::ChainstateActive().LoadGenesisBlock(chainparams); -} - void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp) { // Map of disk positions for blocks with unknown parent (only used for reindex) @@ -5009,24 +5008,6 @@ CBlockFileInfo* GetBlockFileInfo(size_t n) return &vinfoBlockFile.at(n); } -ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsState(::ChainActive().Tip(), params, pos, versionbitscache); -} - -BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsStatistics(::ChainActive().Tip(), params, pos); -} - -int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsStateSinceHeight(::ChainActive().Tip(), params, pos, versionbitscache); -} - static const uint64_t MEMPOOL_DUMP_VERSION = 1; bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate) @@ -5444,7 +5425,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // about the snapshot_chainstate. CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB()); - if (!GetUTXOStats(snapshot_coinsdb, stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) { + if (!GetUTXOStats(snapshot_coinsdb, WITH_LOCK(::cs_main, return std::ref(m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) { LogPrintf("[snapshot] failed to generate coins stats\n"); return false; } diff --git a/src/validation.h b/src/validation.h index 4e4bdbea54..512b306219 100644 --- a/src/validation.h +++ b/src/validation.h @@ -148,8 +148,6 @@ extern const std::vector<std::string> CHECKLEVEL_DOC; FILE* OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false); /** Translation to a filesystem path */ fs::path GetBlockPosFilename(const FlatFilePos &pos); -/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ -bool LoadGenesisBlock(const CChainParams& chainparams); /** Unload database information */ void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman); /** Run instances of script checking worker threads */ @@ -183,7 +181,7 @@ uint64_t CalculateCurrentUsage(); void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune); /** Prune block files up to a given height */ -void PruneBlockFilesManual(int nManualPruneHeight); +void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight); /** * Validation result for a single transaction mempool acceptance. @@ -226,15 +224,6 @@ struct MempoolAcceptResult { MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx, bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Get the BIP9 state for a given deployment at the current tip. */ -ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos); - -/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */ -BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos); - -/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ -int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos); - /** Apply the effects of this transaction on the UTXO set represented by view */ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight); @@ -349,7 +338,7 @@ class CVerifyDB { public: CVerifyDB(); ~CVerifyDB(); - bool VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth); + bool VerifyDB(const CChainParams& chainparams, CChainState& active_chainstate, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; enum DisconnectResult @@ -721,13 +710,20 @@ public: bool DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs); // Manual block validity manipulation: + /** Mark a block as precious and reorganize. + * + * May not be called in a validationinterface callback. + */ bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + /** Mark a block as invalid. */ bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + /** Remove invalidity status from a block and its descendants. */ void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params); bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main); + /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ bool LoadGenesisBlock(const CChainParams& chainparams); void PruneBlockIndexCandidates(); @@ -776,21 +772,13 @@ private: //! Mark a block as not having block data void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - friend ChainstateManager; -}; - -/** Mark a block as precious and reorganize. - * - * May not be called in a - * validationinterface callback. - */ -bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); + void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Mark a block as invalid. */ -bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Remove invalidity status from a block and its descendants. */ -void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + friend ChainstateManager; +}; /** * Provides an interface for creating and interacting with one or two diff --git a/src/versionbits.h b/src/versionbits.h index b02f848b67..6df1db8814 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -79,8 +79,11 @@ struct VersionBitsCache void Clear(); }; +/** Get the BIP9 state for a given deployment at the current tip. */ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); +/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */ BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); +/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos); diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 5e319d4f95..08adf09df4 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -190,7 +190,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock - const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet); + const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet).first; Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors); if (res != Result::OK) { return res; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index fc530ee286..f3e24384df 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -62,7 +62,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #ifdef ENABLE_EXTERNAL_SIGNER - argsman.AddArg("-signer=<cmd>", "External signing tool, see docs/external-signer.md", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + argsman.AddArg("-signer=<cmd>", "External signing tool, see doc/external-signer.md", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 99803a91d2..60f76465a6 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -56,13 +56,13 @@ static std::string DecodeDumpString(const std::string &str) { return ret.str(); } -static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { bool fLabelFound = false; CKey key; spk_man->GetKey(keyid, key); for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { - const auto* address_book_entry = pwallet->FindAddressBookEntry(dest); + const auto* address_book_entry = wallet.FindAddressBookEntry(dest); if (address_book_entry) { if (!strAddr.empty()) { strAddr += ","; @@ -73,7 +73,7 @@ static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWall } } if (!fLabelFound) { - strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), pwallet->m_default_address_type)); + strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), wallet.m_default_address_type)); } return fLabelFound; } @@ -118,22 +118,21 @@ RPCHelpMan importprivkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); WalletRescanReserver reserver(*pwallet); bool fRescan = true; { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strSecret = request.params[0].get_str(); std::string strLabel = ""; @@ -210,9 +209,8 @@ RPCHelpMan abortrescan() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); @@ -249,9 +247,8 @@ RPCHelpMan importaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -335,9 +332,8 @@ RPCHelpMan importprunedfunds() RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { @@ -397,9 +393,8 @@ RPCHelpMan removeprunedfunds() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -445,11 +440,10 @@ RPCHelpMan importpubkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); std::string strLabel; if (!request.params[1].isNull()) @@ -527,11 +521,10 @@ RPCHelpMan importwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); if (pwallet->chain().havePruned()) { // Exit early and print an error. @@ -550,7 +543,7 @@ RPCHelpMan importwallet() { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); fsbridge::ifstream file; file.open(request.params[0].get_str(), std::ios::in | std::ios::ate); @@ -684,15 +677,14 @@ RPCHelpMan dumpprivkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress); @@ -747,7 +739,7 @@ RPCHelpMan dumpwallet() LOCK2(wallet.cs_wallet, spk_man.cs_KeyStore); - EnsureWalletIsUnlocked(&wallet); + EnsureWalletIsUnlocked(wallet); fs::path filepath = request.params[0].get_str(); filepath = fs::absolute(filepath); @@ -809,7 +801,7 @@ RPCHelpMan dumpwallet() CKey key; if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(&spk_man, &wallet, keyid, strAddr, strLabel)) { + if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; @@ -1169,7 +1161,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID return warnings; } -static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); @@ -1184,7 +1176,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; // Add to keypool only works with privkeys disabled - if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); } @@ -1206,29 +1198,29 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con } // If private keys are disabled, abort if private keys are being imported - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } // Check whether we have any work to do for (const CScript& script : script_pub_keys) { - if (pwallet->IsMine(script) & ISMINE_SPENDABLE) { + if (wallet.IsMine(script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")"); } } // All good, time to import - pwallet->MarkDirty(); - if (!pwallet->ImportScripts(import_data.import_scripts, timestamp)) { + wallet.MarkDirty(); + if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } - if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { + if (!wallet.ImportPrivKeys(privkey_map, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { + if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { + if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } @@ -1336,13 +1328,12 @@ RPCHelpMan importmulti() }, [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(mainRequest); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest); + if (!pwallet) return NullUniValue; RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); const UniValue& requests = mainRequest.params[0]; @@ -1368,7 +1359,7 @@ RPCHelpMan importmulti() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); // Verify all timestamps are present before importing any keys. CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); @@ -1380,7 +1371,7 @@ RPCHelpMan importmulti() for (const UniValue& data : requests.getValues()) { const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); - const UniValue result = ProcessImport(pwallet, data, timestamp); + const UniValue result = ProcessImport(*pwallet, data, timestamp); response.push_back(result); if (!fRescan) { @@ -1447,7 +1438,7 @@ RPCHelpMan importmulti() }; } -static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); @@ -1516,7 +1507,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // If the wallet disabled private keys, abort if private keys exist - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } @@ -1540,7 +1531,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // If private keys are enabled, check some things. - if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); } @@ -1552,7 +1543,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); // Check if the wallet already contains the descriptor - auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc); + auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); if (existing_spk_manager) { LOCK(existing_spk_manager->cs_desc_man); if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) { @@ -1561,7 +1552,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // Add descriptor to the wallet - auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label, internal); + auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal); if (spk_manager == nullptr) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); } @@ -1571,7 +1562,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& if (!w_desc.descriptor->GetOutputType()) { warnings.push_back("Unknown output type, cannot set descriptor to active."); } else { - pwallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); + wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); } } @@ -1641,9 +1632,8 @@ RPCHelpMan importdescriptors() }, [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(main_request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); + if (!pwallet) return NullUniValue; // Make sure wallet is a descriptor wallet if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { @@ -1665,7 +1655,7 @@ RPCHelpMan importdescriptors() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); @@ -1673,7 +1663,7 @@ RPCHelpMan importdescriptors() for (const UniValue& request : requests.getValues()) { // This throws an error if "timestamp" doesn't exist const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); - const UniValue result = ProcessDescriptorImport(pwallet, request, timestamp); + const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); response.push_back(result); if (lowest_timestamp > timestamp ) { @@ -1775,6 +1765,8 @@ RPCHelpMan listdescriptors() throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); } + EnsureWalletIsUnlocked(*wallet); + LOCK(wallet->cs_wallet); UniValue response(UniValue::VARR); @@ -1787,7 +1779,11 @@ RPCHelpMan listdescriptors() UniValue spk(UniValue::VOBJ); LOCK(desc_spk_man->cs_desc_man); const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); - spk.pushKV("desc", wallet_descriptor.descriptor->ToString()); + std::string descriptor; + if (!desc_spk_man->GetDescriptorString(descriptor, false)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string."); + } + spk.pushKV("desc", descriptor); spk.pushKV("timestamp", wallet_descriptor.creation_time); const bool active = active_spk_mans.count(desc_spk_man) != 0; spk.pushKV("active", active); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index bfc42ac1b0..8b0962f9ee 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -48,8 +48,8 @@ using interfaces::FoundBlock; static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"}; -static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) { - bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); +static inline bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) { + bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool(); if (avoid_reuse && !can_avoid_reuse) { @@ -64,11 +64,11 @@ static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValu * We default to true for watchonly wallets if include_watchonly isn't * explicitly set. */ -static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& pwallet) +static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet) { if (include_watchonly.isNull()) { // if include_watchonly isn't explicitly set, then check if we have a watchonly wallet - return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + return wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } // otherwise return whatever include_watchonly was set to @@ -117,9 +117,9 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } -void EnsureWalletIsUnlocked(const CWallet* pwallet) +void EnsureWalletIsUnlocked(const CWallet& wallet) { - if (pwallet->IsLocked()) { + if (wallet.IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); } } @@ -249,9 +249,8 @@ static RPCHelpMan getnewaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -299,9 +298,8 @@ static RPCHelpMan getrawchangeaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -342,9 +340,8 @@ static RPCHelpMan setlabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -396,13 +393,13 @@ void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_f } } -UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose) +UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose) { - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(wallet); // This function is only used by sendtoaddress and sendmany. // This should always try to sign, if we don't have private keys, don't try to do anything here. - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } @@ -415,11 +412,11 @@ UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std bilingual_str error; CTransactionRef tx; FeeCalculation fee_calc_out; - const bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); + const bool fCreated = wallet.CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); } - pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */); + wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */); if (verbose) { UniValue entry(UniValue::VOBJ); entry.pushKV("txid", tx->GetHash().GetHex()); @@ -480,9 +477,8 @@ static RPCHelpMan sendtoaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -507,13 +503,13 @@ static RPCHelpMan sendtoaddress() coin_control.m_signal_bip125_rbf = request.params[5].get_bool(); } - coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]); + coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(*pwallet, request.params[8]); // We also enable partial spend avoidance if reuse avoidance is set. coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse; SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); UniValue address_amounts(UniValue::VOBJ); const std::string address = request.params[0].get_str(); @@ -527,7 +523,7 @@ static RPCHelpMan sendtoaddress() ParseRecipients(address_amounts, subtractFeeFromAmount, recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - return SendMoney(pwallet, coin_control, recipients, mapValue, verbose); + return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose); }, }; } @@ -559,9 +555,8 @@ static RPCHelpMan listaddressgroupings() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -617,20 +612,19 @@ static RPCHelpMan signmessage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strAddress = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } const PKHash* pkhash = std::get_if<PKHash>(&dest); @@ -720,9 +714,8 @@ static RPCHelpMan getreceivedbyaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -759,9 +752,8 @@ static RPCHelpMan getreceivedbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -800,9 +792,8 @@ static RPCHelpMan getbalance() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -822,7 +813,7 @@ static RPCHelpMan getbalance() bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); - bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]); + bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); const auto bal = pwallet->GetBalance(min_depth, avoid_reuse); @@ -840,9 +831,8 @@ static RPCHelpMan getunconfirmedbalance() RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -911,9 +901,8 @@ static RPCHelpMan sendmany() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -945,7 +934,7 @@ static RPCHelpMan sendmany() ParseRecipients(sendTo, subtractFeeFromAmount, recipients); const bool verbose{request.params[9].isNull() ? false : request.params[9].get_bool()}; - return SendMoney(pwallet, coin_control, recipients, std::move(mapValue), verbose); + return SendMoney(*pwallet, coin_control, recipients, std::move(mapValue), verbose); }, }; } @@ -985,9 +974,8 @@ static RPCHelpMan addmultisigaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); @@ -1045,7 +1033,7 @@ struct tallyitem } }; -static UniValue ListReceived(const CWallet* const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ListReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { // Minimum confirmations int nMinDepth = 1; @@ -1059,7 +1047,7 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param isminefilter filter = ISMINE_SPENDABLE; - if (ParseIncludeWatchonly(params[2], *pwallet)) { + if (ParseIncludeWatchonly(params[2], wallet)) { filter |= ISMINE_WATCH_ONLY; } @@ -1075,10 +1063,10 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param // Tally std::map<CTxDestination, tallyitem> mapTally; - for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !pwallet->chain().checkFinalTx(*wtx.tx)) { + if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx)) { continue; } @@ -1096,7 +1084,7 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param continue; } - isminefilter mine = pwallet->IsMine(address); + isminefilter mine = wallet.IsMine(address); if(!(mine & filter)) continue; @@ -1115,11 +1103,11 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param // Create m_address_book iterator // If we aren't filtering, go from begin() to end() - auto start = pwallet->m_address_book.begin(); - auto end = pwallet->m_address_book.end(); + auto start = wallet.m_address_book.begin(); + auto end = wallet.m_address_book.end(); // If we are filtering, find() the applicable entry if (has_filtered_address) { - start = pwallet->m_address_book.find(filtered_address); + start = wallet.m_address_book.find(filtered_address); if (start != end) { end = std::next(start); } @@ -1227,9 +1215,8 @@ static RPCHelpMan listreceivedbyaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1237,7 +1224,7 @@ static RPCHelpMan listreceivedbyaddress() LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, false); + return ListReceived(*pwallet, request.params, false); }, }; } @@ -1270,9 +1257,8 @@ static RPCHelpMan listreceivedbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1280,7 +1266,7 @@ static RPCHelpMan listreceivedbylabel() LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, true); + return ListReceived(*pwallet, request.params, true); }, }; } @@ -1303,7 +1289,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; @@ -1319,20 +1305,20 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, for (const COutputEntry& s : listSent) { UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (wallet.IsMine(s.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, s.destination); entry.pushKV("category", "send"); entry.pushKV("amount", ValueFromAmount(-s.amount)); - const auto* address_book_entry = pwallet->FindAddressBookEntry(s.destination); + const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination); if (address_book_entry) { entry.pushKV("label", address_book_entry->GetLabel()); } entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(wallet.chain(), wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } @@ -1343,7 +1329,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, for (const COutputEntry& r : listReceived) { std::string label; - const auto* address_book_entry = pwallet->FindAddressBookEntry(r.destination); + const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination); if (address_book_entry) { label = address_book_entry->GetLabel(); } @@ -1351,7 +1337,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, continue; } UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (wallet.IsMine(r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); @@ -1374,7 +1360,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(wallet.chain(), wtx, entry); ret.push_back(entry); } } @@ -1451,9 +1437,8 @@ static RPCHelpMan listtransactions() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1494,7 +1479,7 @@ static RPCHelpMan listtransactions() for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(pwallet, *pwtx, 0, true, ret, filter, filter_label); + ListTransactions(*pwallet, *pwtx, 0, true, ret, filter, filter_label); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1616,7 +1601,7 @@ static RPCHelpMan listsinceblock() const CWalletTx& tx = pairWtx.second; if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { - ListTransactions(&wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); + ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1633,7 +1618,7 @@ static RPCHelpMan listsinceblock() if (it != wallet.mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(&wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); + ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } blockId = block.hashPrevBlock; @@ -1710,9 +1695,8 @@ static RPCHelpMan gettransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1749,7 +1733,7 @@ static RPCHelpMan gettransaction() WalletTxToJSON(pwallet->chain(), wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); + ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); @@ -1784,9 +1768,8 @@ static RPCHelpMan abandontransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1823,9 +1806,8 @@ static RPCHelpMan backupwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1859,9 +1841,8 @@ static RPCHelpMan keypoolrefill() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1877,7 +1858,7 @@ static RPCHelpMan keypoolrefill() kpSize = (unsigned int)request.params[0].get_int(); } - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); pwallet->TopUpKeyPool(kpSize); if (pwallet->GetKeyPoolSize() < kpSize) { @@ -2001,9 +1982,8 @@ static RPCHelpMan walletpassphrasechange() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2055,9 +2035,8 @@ static RPCHelpMan walletlock() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2100,9 +2079,8 @@ static RPCHelpMan encryptwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2174,9 +2152,8 @@ static RPCHelpMan lockunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2289,9 +2266,8 @@ static RPCHelpMan listlockunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2330,9 +2306,8 @@ static RPCHelpMan settxfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2460,9 +2435,8 @@ static RPCHelpMan getwalletinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2672,9 +2646,8 @@ static RPCHelpMan setwalletflag() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -2916,9 +2889,8 @@ static RPCHelpMan listunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -3080,11 +3052,11 @@ static RPCHelpMan listunspent() }; } -void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee) +void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee) { // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); + wallet.BlockUntilSyncedToCurrentChain(); change_position = -1; bool lockUnspents = false; @@ -3155,7 +3127,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f } const UniValue include_watching_option = options.exists("include_watching") ? options["include_watching"] : options["includeWatching"]; - coinControl.fAllowWatchOnly = ParseIncludeWatchonly(include_watching_option, *pwallet); + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(include_watching_option, wallet); if (options.exists("lockUnspents") || options.exists("lock_unspents")) { lockUnspents = (options.exists("lock_unspents") ? options["lock_unspents"] : options["lockUnspents"]).get_bool(); @@ -3181,11 +3153,11 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f if (options.exists("replaceable")) { coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool(); } - SetFeeEstimateMode(*pwallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee); + SetFeeEstimateMode(wallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee); } } else { // if options is null and not a bool - coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet); + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet); } if (tx.vout.size() == 0) @@ -3207,7 +3179,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f bilingual_str error; - if (!pwallet->FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!wallet.FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } } @@ -3283,9 +3255,8 @@ static RPCHelpMan fundrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3302,7 +3273,7 @@ static RPCHelpMan fundrawtransaction() CCoinControl coin_control; // Automatically select (additional) coins. Can be overridden by options.add_inputs. coin_control.m_add_inputs = true; - FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true); + FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(tx))); @@ -3369,9 +3340,8 @@ RPCHelpMan signrawtransactionwithwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3382,7 +3352,7 @@ RPCHelpMan signrawtransactionwithwallet() // Sign the transaction LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); // Fetch previous transactions (inputs): std::map<COutPoint, Coin> coins; @@ -3469,9 +3439,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name) }, [want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) mutable -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) { throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead."); @@ -3514,7 +3483,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name) pwallet->BlockUntilSyncedToCurrentChain(); LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + + EnsureWalletIsUnlocked(*pwallet); std::vector<bilingual_str> errors; @@ -3608,9 +3578,8 @@ static RPCHelpMan rescanblockchain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; WalletRescanReserver reserver(*pwallet); if (!reserver.reserve()) { @@ -3759,15 +3728,13 @@ public: UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } }; -static UniValue DescribeWalletAddress(const CWallet* const pwallet, const CTxDestination& dest) +static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestination& dest) { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); CScript script = GetScriptForDestination(dest); std::unique_ptr<SigningProvider> provider = nullptr; - if (pwallet) { - provider = pwallet->GetSolvingProvider(script); - } + provider = wallet.GetSolvingProvider(script); ret.pushKVs(detail); ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest)); return ret; @@ -3840,9 +3807,8 @@ RPCHelpMan getaddressinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -3887,7 +3853,7 @@ RPCHelpMan getaddressinfo() ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); - UniValue detail = DescribeWalletAddress(pwallet, dest); + UniValue detail = DescribeWalletAddress(*pwallet, dest); ret.pushKVs(detail); ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); @@ -3943,9 +3909,8 @@ static RPCHelpMan getaddressesbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -4005,9 +3970,8 @@ static RPCHelpMan listlabels() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -4130,9 +4094,8 @@ static RPCHelpMan send() }, true ); - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]}; if (options.exists("conf_target") || options.exists("estimate_mode")) { @@ -4185,7 +4148,7 @@ static RPCHelpMan send() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_add_inputs = rawTx.vin.size() == 0; - FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false); + FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false); bool add_to_wallet = true; if (options.exists("add_to_wallet")) { @@ -4258,9 +4221,8 @@ static RPCHelpMan sethdseed() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -4275,7 +4237,7 @@ static RPCHelpMan sethdseed() throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD"); } - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); bool flush_key_pool = true; if (!request.params[0].isNull()) { @@ -4336,9 +4298,8 @@ static RPCHelpMan walletprocesspsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); @@ -4448,9 +4409,8 @@ static RPCHelpMan walletcreatefundedpsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, { UniValue::VARR, @@ -4474,7 +4434,7 @@ static RPCHelpMan walletcreatefundedpsbt() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_add_inputs = rawTx.vin.size() == 0; - FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); + FundTransaction(*pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); @@ -4524,13 +4484,12 @@ static RPCHelpMan upgradewallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VNUM}, true); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); int version = 0; if (!request.params[0].isNull()) { diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 184a16e91d..b82fe1ec76 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -30,7 +30,7 @@ Span<const CRPCCommand> GetWalletRPCCommands(); */ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); -void EnsureWalletIsUnlocked(const CWallet*); +void EnsureWalletIsUnlocked(const CWallet&); WalletContext& EnsureWalletContext(const util::Ref& context); LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create = false); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 08e480225d..58ab124061 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -791,6 +791,12 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) wtx.mapValue["replaced_by_txid"] = newHash.ToString(); + // Refresh mempool status without waiting for transactionRemovedFromMempool + // notification so the wallet is in an internally consistent state and + // immediately knows the old transaction should not be considered trusted + // and is eligible to be abandoned + wtx.fInMempool = chain().isInMempool(originalHash); + WalletBatch batch(GetDatabase()); bool success = true; @@ -1611,14 +1617,15 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri return true; } -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) +// Returns pair of vsize and weight +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { - return -1; + return std::make_pair(-1, -1); } assert(input.prevout.n < mi->second.tx->vout.size()); txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); @@ -1627,13 +1634,16 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall } // txouts needs to be in the order of tx.vin -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - return -1; + return std::make_pair(-1, -1); } - return GetVirtualTransactionSize(CTransaction(txNew)); + CTransaction ctx(txNew); + int64_t vsize = GetVirtualTransactionSize(ctx); + int64_t weight = GetTransactionWeight(ctx); + return std::make_pair(vsize, weight); } int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) @@ -2779,6 +2789,7 @@ bool CWallet::CreateTransactionInternal( CMutableTransaction txNew; FeeCalculation feeCalc; CAmount nFeeNeeded; + std::pair<int64_t, int64_t> tx_sizes; int nBytes; { std::set<CInputCoin> setCoins; @@ -2962,7 +2973,8 @@ bool CWallet::CreateTransactionInternal( txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); } - nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + nBytes = tx_sizes.first; if (nBytes < 0) { error = _("Signing transaction failed"); return false; @@ -3072,7 +3084,8 @@ bool CWallet::CreateTransactionInternal( tx = MakeTransactionRef(std::move(txNew)); // Limit size - if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) + if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || + (!sign && tx_sizes.second > MAX_STANDARD_TX_WEIGHT)) { error = _("Transaction too large"); return false; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index eb797938cd..3a76163dd2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1334,8 +1334,8 @@ public: // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); //! Add wallet name to persistent configuration so it will be loaded on startup. bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); |