Skip to content

Commit b875516

Browse files
committed
Merge bitcoin/bitcoin#30413: p2p: Lazy init some bloom filters; fuzz version handshake
afd237b [fuzz] Harness for version handshake (dergoegge) a90ab4a scripted-diff: Rename lazily initialized bloom filters (dergoegge) 82de1bc [net processing] Lazily initialize m_recent_confirmed_transactions (dergoegge) fa0c87f [net processing] Lazily initialize m_recent_rejects_reconsiderable (dergoegge) 662e8db [net processing] Lazily initialize m_recent_rejects (dergoegge) Pull request description: This adds a fuzzing harness dedicated to the version handshake. To avoid determinism issues, the harness creates necessary components each iteration (addrman, peerman, etc). A harness like this would have easily caught https://bitcoincore.org/en/2024/07/03/disclose-timestamp-overflow/. As a performance optimization, this PR includes a change to `PeerManager` to lazily initialize various filters (to avoid large unnecessary memory allocations each iteration). ACKs for top commit: brunoerg: ACK afd237b marcofleon: Tested ACK afd237b. I compared the coverage of `net_processing` from this harness to the `process_message` and `process_messages` harnesses to see the differences. This target hits more specific parts of the version handshake. The stability looks good as well, at about 94%. glozow: utACK afd237b lazy blooms look ok mzumsande: Code Review ACK afd237b Tree-SHA512: 62bba20aec0cd220e62368354891f9790b81ad75e8adf7b22a76a6d4663bd26aedc4cae8083658a75ea9043d60aad3f0e58ad36bd7bbbf93ff1d16e317bf15cc
2 parents 66e82dc + afd237b commit b875516

File tree

6 files changed

+193
-47
lines changed

6 files changed

+193
-47
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ test_fuzz_fuzz_SOURCES = \
350350
test/fuzz/netaddress.cpp \
351351
test/fuzz/netbase_dns_lookup.cpp \
352352
test/fuzz/node_eviction.cpp \
353+
test/fuzz/p2p_handshake.cpp \
353354
test/fuzz/p2p_transport_serialization.cpp \
354355
test/fuzz/package_eval.cpp \
355356
test/fuzz/parse_hd_keypath.cpp \

src/net_processing.cpp

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ class PeerManagerImpl final : public PeerManager
584584
* @param[in] maybe_add_extra_compact_tx Whether this tx should be added to vExtraTxnForCompact.
585585
* Set to false if the tx has already been rejected before,
586586
* e.g. is an orphan, to avoid adding duplicate entries.
587-
* Updates m_txrequest, m_recent_rejects, m_recent_rejects_reconsiderable, m_orphanage, and vExtraTxnForCompact. */
587+
* Updates m_txrequest, m_lazy_recent_rejects, m_lazy_recent_rejects_reconsiderable, m_orphanage, and vExtraTxnForCompact. */
588588
void ProcessInvalidTx(NodeId nodeid, const CTransactionRef& tx, const TxValidationState& result,
589589
bool maybe_add_extra_compact_tx)
590590
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, m_tx_download_mutex);
@@ -776,9 +776,9 @@ class PeerManagerImpl final : public PeerManager
776776
/** Synchronizes tx download including TxRequestTracker, rejection filters, and TxOrphanage.
777777
* Lock invariants:
778778
* - A txhash (txid or wtxid) in m_txrequest is not also in m_orphanage.
779-
* - A txhash (txid or wtxid) in m_txrequest is not also in m_recent_rejects.
780-
* - A txhash (txid or wtxid) in m_txrequest is not also in m_recent_rejects_reconsiderable.
781-
* - A txhash (txid or wtxid) in m_txrequest is not also in m_recent_confirmed_transactions.
779+
* - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_rejects.
780+
* - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_rejects_reconsiderable.
781+
* - A txhash (txid or wtxid) in m_txrequest is not also in m_lazy_recent_confirmed_transactions.
782782
* - Each data structure's limits hold (m_orphanage max size, m_txrequest per-peer limits, etc).
783783
*/
784784
Mutex m_tx_download_mutex ACQUIRED_BEFORE(m_mempool.cs);
@@ -856,9 +856,9 @@ class PeerManagerImpl final : public PeerManager
856856
/** Check whether we already have this gtxid in:
857857
* - mempool
858858
* - orphanage
859-
* - m_recent_rejects
860-
* - m_recent_rejects_reconsiderable (if include_reconsiderable = true)
861-
* - m_recent_confirmed_transactions
859+
* - m_lazy_recent_rejects
860+
* - m_lazy_recent_rejects_reconsiderable (if include_reconsiderable = true)
861+
* - m_lazy_recent_confirmed_transactions
862862
* */
863863
bool AlreadyHaveTx(const GenTxid& gtxid, bool include_reconsiderable)
864864
EXCLUSIVE_LOCKS_REQUIRED(m_tx_download_mutex);
@@ -897,15 +897,26 @@ class PeerManagerImpl final : public PeerManager
897897
*
898898
* Memory used: 1.3 MB
899899
*/
900-
CRollingBloomFilter m_recent_rejects GUARDED_BY(m_tx_download_mutex){120'000, 0.000'001};
900+
std::unique_ptr<CRollingBloomFilter> m_lazy_recent_rejects GUARDED_BY(m_tx_download_mutex){nullptr};
901+
902+
CRollingBloomFilter& RecentRejectsFilter() EXCLUSIVE_LOCKS_REQUIRED(m_tx_download_mutex)
903+
{
904+
AssertLockHeld(m_tx_download_mutex);
905+
906+
if (!m_lazy_recent_rejects) {
907+
m_lazy_recent_rejects = std::make_unique<CRollingBloomFilter>(120'000, 0.000'001);
908+
}
909+
910+
return *m_lazy_recent_rejects;
911+
}
901912

902913
/**
903914
* Filter for:
904915
* (1) wtxids of transactions that were recently rejected by the mempool but are
905916
* eligible for reconsideration if submitted with other transactions.
906917
* (2) packages (see GetPackageHash) we have already rejected before and should not retry.
907918
*
908-
* Similar to m_recent_rejects, this filter is used to save bandwidth when e.g. all of our peers
919+
* Similar to m_lazy_recent_rejects, this filter is used to save bandwidth when e.g. all of our peers
909920
* have larger mempools and thus lower minimum feerates than us.
910921
*
911922
* When a transaction's error is TxValidationResult::TX_RECONSIDERABLE (in a package or by
@@ -917,9 +928,20 @@ class PeerManagerImpl final : public PeerManager
917928
*
918929
* Reset this filter when the chain tip changes.
919930
*
920-
* Parameters are picked to be the same as m_recent_rejects, with the same rationale.
931+
* Parameters are picked to be the same as m_lazy_recent_rejects, with the same rationale.
921932
*/
922-
CRollingBloomFilter m_recent_rejects_reconsiderable GUARDED_BY(m_tx_download_mutex){120'000, 0.000'001};
933+
std::unique_ptr<CRollingBloomFilter> m_lazy_recent_rejects_reconsiderable GUARDED_BY(m_tx_download_mutex){nullptr};
934+
935+
CRollingBloomFilter& RecentRejectsReconsiderableFilter() EXCLUSIVE_LOCKS_REQUIRED(m_tx_download_mutex)
936+
{
937+
AssertLockHeld(m_tx_download_mutex);
938+
939+
if (!m_lazy_recent_rejects_reconsiderable) {
940+
m_lazy_recent_rejects_reconsiderable = std::make_unique<CRollingBloomFilter>(120'000, 0.000'001);
941+
}
942+
943+
return *m_lazy_recent_rejects_reconsiderable;
944+
}
923945

924946
/*
925947
* Filter for transactions that have been recently confirmed.
@@ -936,7 +958,18 @@ class PeerManagerImpl final : public PeerManager
936958
* transaction per day that would be inadvertently ignored (which is the
937959
* same probability that we have in the reject filter).
938960
*/
939-
CRollingBloomFilter m_recent_confirmed_transactions GUARDED_BY(m_tx_download_mutex){48'000, 0.000'001};
961+
std::unique_ptr<CRollingBloomFilter> m_lazy_recent_confirmed_transactions GUARDED_BY(m_tx_download_mutex){nullptr};
962+
963+
CRollingBloomFilter& RecentConfirmedTransactionsFilter() EXCLUSIVE_LOCKS_REQUIRED(m_tx_download_mutex)
964+
{
965+
AssertLockHeld(m_tx_download_mutex);
966+
967+
if (!m_lazy_recent_confirmed_transactions) {
968+
m_lazy_recent_confirmed_transactions = std::make_unique<CRollingBloomFilter>(48'000, 0.000'001);
969+
}
970+
971+
return *m_lazy_recent_confirmed_transactions;
972+
}
940973

941974
/**
942975
* For sending `inv`s to inbound peers, we use a single (exponentially
@@ -2080,8 +2113,8 @@ void PeerManagerImpl::ActiveTipChange(const CBlockIndex& new_tip, bool is_ibd)
20802113
// If the chain tip has changed, previously rejected transactions might now be valid, e.g. due
20812114
// to a timelock. Reset the rejection filters to give those transactions another chance if we
20822115
// see them again.
2083-
m_recent_rejects.reset();
2084-
m_recent_rejects_reconsiderable.reset();
2116+
RecentRejectsFilter().reset();
2117+
RecentRejectsReconsiderableFilter().reset();
20852118
}
20862119
}
20872120

@@ -2119,9 +2152,9 @@ void PeerManagerImpl::BlockConnected(
21192152
m_orphanage.EraseForBlock(*pblock);
21202153

21212154
for (const auto& ptx : pblock->vtx) {
2122-
m_recent_confirmed_transactions.insert(ptx->GetHash().ToUint256());
2155+
RecentConfirmedTransactionsFilter().insert(ptx->GetHash().ToUint256());
21232156
if (ptx->HasWitness()) {
2124-
m_recent_confirmed_transactions.insert(ptx->GetWitnessHash().ToUint256());
2157+
RecentConfirmedTransactionsFilter().insert(ptx->GetWitnessHash().ToUint256());
21252158
}
21262159
m_txrequest.ForgetTxHash(ptx->GetHash());
21272160
m_txrequest.ForgetTxHash(ptx->GetWitnessHash());
@@ -2139,7 +2172,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo
21392172
// presumably the most common case of relaying a confirmed transaction
21402173
// should be just after a new block containing it is found.
21412174
LOCK(m_tx_download_mutex);
2142-
m_recent_confirmed_transactions.reset();
2175+
RecentConfirmedTransactionsFilter().reset();
21432176
}
21442177

21452178
/**
@@ -2300,11 +2333,11 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_reconside
23002333
if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
23012334
}
23022335

2303-
if (include_reconsiderable && m_recent_rejects_reconsiderable.contains(hash)) return true;
2336+
if (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) return true;
23042337

2305-
if (m_recent_confirmed_transactions.contains(hash)) return true;
2338+
if (RecentConfirmedTransactionsFilter().contains(hash)) return true;
23062339

2307-
return m_recent_rejects.contains(hash) || m_mempool.exists(gtxid);
2340+
return RecentRejectsFilter().contains(hash) || m_mempool.exists(gtxid);
23082341
}
23092342

23102343
bool PeerManagerImpl::AlreadyHaveBlock(const uint256& block_hash)
@@ -3192,12 +3225,12 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx
31923225
// for concerns around weakening security of unupgraded nodes
31933226
// if we start doing this too early.
31943227
if (state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) {
3195-
// If the result is TX_RECONSIDERABLE, add it to m_recent_rejects_reconsiderable
3228+
// If the result is TX_RECONSIDERABLE, add it to m_lazy_recent_rejects_reconsiderable
31963229
// because we should not download or submit this transaction by itself again, but may
31973230
// submit it as part of a package later.
3198-
m_recent_rejects_reconsiderable.insert(ptx->GetWitnessHash().ToUint256());
3231+
RecentRejectsReconsiderableFilter().insert(ptx->GetWitnessHash().ToUint256());
31993232
} else {
3200-
m_recent_rejects.insert(ptx->GetWitnessHash().ToUint256());
3233+
RecentRejectsFilter().insert(ptx->GetWitnessHash().ToUint256());
32013234
}
32023235
m_txrequest.ForgetTxHash(ptx->GetWitnessHash());
32033236
// If the transaction failed for TX_INPUTS_NOT_STANDARD,
@@ -3211,7 +3244,7 @@ void PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx
32113244
// We only add the txid if it differs from the wtxid, to avoid wasting entries in the
32123245
// rolling bloom filter.
32133246
if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && ptx->HasWitness()) {
3214-
m_recent_rejects.insert(ptx->GetHash().ToUint256());
3247+
RecentRejectsFilter().insert(ptx->GetHash().ToUint256());
32153248
m_txrequest.ForgetTxHash(ptx->GetHash());
32163249
}
32173250
if (maybe_add_extra_compact_tx && RecursiveDynamicUsage(*ptx) < 100000) {
@@ -3266,7 +3299,7 @@ void PeerManagerImpl::ProcessPackageResult(const PackageToValidate& package_to_v
32663299
const auto& senders = package_to_validate.m_senders;
32673300

32683301
if (package_result.m_state.IsInvalid()) {
3269-
m_recent_rejects_reconsiderable.insert(GetPackageHash(package));
3302+
RecentRejectsReconsiderableFilter().insert(GetPackageHash(package));
32703303
}
32713304
// We currently only expect to process 1-parent-1-child packages. Remove if this changes.
32723305
if (!Assume(package.size() == 2)) return;
@@ -3320,7 +3353,7 @@ std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::Find1P1CPacka
33203353

33213354
const auto& parent_wtxid{ptx->GetWitnessHash()};
33223355

3323-
Assume(m_recent_rejects_reconsiderable.contains(parent_wtxid.ToUint256()));
3356+
Assume(RecentRejectsReconsiderableFilter().contains(parent_wtxid.ToUint256()));
33243357

33253358
// Prefer children from this peer. This helps prevent censorship attempts in which an attacker
33263359
// sends lots of fake children for the parent, and we (unluckily) keep selecting the fake
@@ -3332,7 +3365,7 @@ std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::Find1P1CPacka
33323365
// most recent) one efficiently.
33333366
for (const auto& child : cpfp_candidates_same_peer) {
33343367
Package maybe_cpfp_package{ptx, child};
3335-
if (!m_recent_rejects_reconsiderable.contains(GetPackageHash(maybe_cpfp_package))) {
3368+
if (!RecentRejectsReconsiderableFilter().contains(GetPackageHash(maybe_cpfp_package))) {
33363369
return PeerManagerImpl::PackageToValidate{ptx, child, nodeid, nodeid};
33373370
}
33383371
}
@@ -3356,10 +3389,10 @@ std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::Find1P1CPacka
33563389

33573390
for (const auto index : tx_indices) {
33583391
// If we already tried a package and failed for any reason, the combined hash was
3359-
// cached in m_recent_rejects_reconsiderable.
3392+
// cached in m_lazy_recent_rejects_reconsiderable.
33603393
const auto [child_tx, child_sender] = cpfp_candidates_different_peer.at(index);
33613394
Package maybe_cpfp_package{ptx, child_tx};
3362-
if (!m_recent_rejects_reconsiderable.contains(GetPackageHash(maybe_cpfp_package))) {
3395+
if (!RecentRejectsReconsiderableFilter().contains(GetPackageHash(maybe_cpfp_package))) {
33633396
return PeerManagerImpl::PackageToValidate{ptx, child_tx, nodeid, child_sender};
33643397
}
33653398
}
@@ -4551,8 +4584,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
45514584
}
45524585
}
45534586

4554-
if (m_recent_rejects_reconsiderable.contains(wtxid)) {
4555-
// When a transaction is already in m_recent_rejects_reconsiderable, we shouldn't submit
4587+
if (RecentRejectsReconsiderableFilter().contains(wtxid)) {
4588+
// When a transaction is already in m_lazy_recent_rejects_reconsiderable, we shouldn't submit
45564589
// it by itself again. However, look for a matching child in the orphanage, as it is
45574590
// possible that they succeed as a package.
45584591
LogPrint(BCLog::TXPACKAGES, "found tx %s (wtxid=%s) in reconsiderable rejects, looking for child in orphanage\n",
@@ -4564,20 +4597,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
45644597
ProcessPackageResult(package_to_validate.value(), package_result);
45654598
}
45664599
}
4567-
// If a tx is detected by m_recent_rejects it is ignored. Because we haven't
4600+
// If a tx is detected by m_lazy_recent_rejects it is ignored. Because we haven't
45684601
// submitted the tx to our mempool, we won't have computed a DoS
45694602
// score for it or determined exactly why we consider it invalid.
45704603
//
45714604
// This means we won't penalize any peer subsequently relaying a DoSy
45724605
// tx (even if we penalized the first peer who gave it to us) because
4573-
// we have to account for m_recent_rejects showing false positives. In
4606+
// we have to account for m_lazy_recent_rejects showing false positives. In
45744607
// other words, we shouldn't penalize a peer if we aren't *sure* they
45754608
// submitted a DoSy tx.
45764609
//
4577-
// Note that m_recent_rejects doesn't just record DoSy or invalid
4610+
// Note that m_lazy_recent_rejects doesn't just record DoSy or invalid
45784611
// transactions, but any tx not accepted by the mempool, which may be
45794612
// due to node policy (vs. consensus). So we can't blanket penalize a
4580-
// peer simply for relaying a tx that our m_recent_rejects has caught,
4613+
// peer simply for relaying a tx that our m_lazy_recent_rejects has caught,
45814614
// regardless of false positives.
45824615
return;
45834616
}
@@ -4604,16 +4637,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
46044637
std::sort(unique_parents.begin(), unique_parents.end());
46054638
unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end());
46064639

4607-
// Distinguish between parents in m_recent_rejects and m_recent_rejects_reconsiderable.
4608-
// We can tolerate having up to 1 parent in m_recent_rejects_reconsiderable since we
4609-
// submit 1p1c packages. However, fail immediately if any are in m_recent_rejects.
4640+
// Distinguish between parents in m_lazy_recent_rejects and m_lazy_recent_rejects_reconsiderable.
4641+
// We can tolerate having up to 1 parent in m_lazy_recent_rejects_reconsiderable since we
4642+
// submit 1p1c packages. However, fail immediately if any are in m_lazy_recent_rejects.
46104643
std::optional<uint256> rejected_parent_reconsiderable;
46114644
for (const uint256& parent_txid : unique_parents) {
4612-
if (m_recent_rejects.contains(parent_txid)) {
4645+
if (RecentRejectsFilter().contains(parent_txid)) {
46134646
fRejectedParents = true;
46144647
break;
4615-
} else if (m_recent_rejects_reconsiderable.contains(parent_txid) && !m_mempool.exists(GenTxid::Txid(parent_txid))) {
4616-
// More than 1 parent in m_recent_rejects_reconsiderable: 1p1c will not be
4648+
} else if (RecentRejectsReconsiderableFilter().contains(parent_txid) && !m_mempool.exists(GenTxid::Txid(parent_txid))) {
4649+
// More than 1 parent in m_lazy_recent_rejects_reconsiderable: 1p1c will not be
46174650
// sufficient to accept this package, so just give up here.
46184651
if (rejected_parent_reconsiderable.has_value()) {
46194652
fRejectedParents = true;
@@ -4633,7 +4666,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
46334666
// protocol for getting all unconfirmed parents.
46344667
const auto gtxid{GenTxid::Txid(parent_txid)};
46354668
AddKnownTx(*peer, parent_txid);
4636-
// Exclude m_recent_rejects_reconsiderable: the missing parent may have been
4669+
// Exclude m_lazy_recent_rejects_reconsiderable: the missing parent may have been
46374670
// previously rejected for being too low feerate. This orphan might CPFP it.
46384671
if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) AddTxAnnouncement(pfrom, gtxid, current_time);
46394672
}
@@ -4658,8 +4691,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
46584691
// regardless of what witness is provided, we will not accept
46594692
// this, so we don't need to allow for redownload of this txid
46604693
// from any of our non-wtxidrelay peers.
4661-
m_recent_rejects.insert(tx.GetHash().ToUint256());
4662-
m_recent_rejects.insert(tx.GetWitnessHash().ToUint256());
4694+
RecentRejectsFilter().insert(tx.GetHash().ToUint256());
4695+
RecentRejectsFilter().insert(tx.GetWitnessHash().ToUint256());
46634696
m_txrequest.ForgetTxHash(tx.GetHash());
46644697
m_txrequest.ForgetTxHash(tx.GetWitnessHash());
46654698
}
@@ -6306,7 +6339,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
63066339
entry.second.GetHash().ToString(), entry.first);
63076340
}
63086341
for (const GenTxid& gtxid : requestable) {
6309-
// Exclude m_recent_rejects_reconsiderable: we may be requesting a missing parent
6342+
// Exclude m_lazy_recent_rejects_reconsiderable: we may be requesting a missing parent
63106343
// that was previously rejected for being too low feerate.
63116344
if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) {
63126345
LogPrint(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx",

0 commit comments

Comments
 (0)