Skip to content

Commit 94ca99a

Browse files
committed
Merge bitcoin/bitcoin#31666: multi-peer orphan resolution followups
7426afb [p2p] assign just 1 random announcer in AddChildrenToWorkSet (glozow) 4c1fa6b test fix: make peer who sends MSG_TX announcement non-wtxidrelay (glozow) 2da46b8 pass P2PTxInvStore init args to P2PInterface init (glozow) e3bd51e [doc] how unique_parents can be empty (glozow) 32eb6dc [refactor] assign local variable for wtxid (glozow) 18820cc multi-announcer orphan handling test fixups (glozow) c4cc61d [fuzz] GetCandidatePeers (glozow) 7704139 [refactor] make GetCandidatePeers take uint256 and in-out vector (glozow) 6e4d392 [refactor] rename to OrphanResolutionCandidate to MaybeAdd* (glozow) 57221ad [refactor] move parent inv-adding to OrphanResolutionCandidate (glozow) Pull request description: Followup to #31397. Addressing (in order): bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31397 (comment) bitcoin/bitcoin#31658 (review) bitcoin/bitcoin#31397 (comment) ACKs for top commit: instagibbs: reACK 7426afb marcofleon: reACK 7426afb mzumsande: Code Review ACK 7426afb dergoegge: Code review ACK 7426afb Tree-SHA512: bca8f576873fdaa20b758e1ee9708ce94e618ff14726864b29b50f0f9a4db58136a286d2b654af569b09433a028901fe6bcdda68dcbfea71e2d1271934725503
2 parents 6f5ae1a + 7426afb commit 94ca99a

File tree

11 files changed

+105
-85
lines changed

11 files changed

+105
-85
lines changed

src/node/txdownloadman_impl.cpp

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -179,23 +179,21 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid,
179179
// - exists in orphanage
180180
// - peer can be an orphan resolution candidate
181181
if (gtxid.IsWtxid()) {
182-
if (auto orphan_tx{m_orphanage.GetTx(Wtxid::FromUint256(gtxid.GetHash()))}) {
182+
const auto wtxid{Wtxid::FromUint256(gtxid.GetHash())};
183+
if (auto orphan_tx{m_orphanage.GetTx(wtxid)}) {
183184
auto unique_parents{GetUniqueParents(*orphan_tx)};
184185
std::erase_if(unique_parents, [&](const auto& txid){
185186
return AlreadyHaveTx(GenTxid::Txid(txid), /*include_reconsiderable=*/false);
186187
});
187188

188-
if (unique_parents.empty()) return true;
189-
190-
if (auto delay{OrphanResolutionCandidate(peer, Wtxid::FromUint256(gtxid.GetHash()), unique_parents.size())}) {
191-
m_orphanage.AddAnnouncer(Wtxid::FromUint256(gtxid.GetHash()), peer);
192-
193-
const auto& info = m_peer_info.at(peer).m_connection_info;
194-
for (const auto& parent_txid : unique_parents) {
195-
m_txrequest.ReceivedInv(peer, GenTxid::Txid(parent_txid), info.m_preferred, now + *delay);
196-
}
189+
// The missing parents may have all been rejected or accepted since the orphan was added to the orphanage.
190+
// Do not delete from the orphanage, as it may be queued for processing.
191+
if (unique_parents.empty()) {
192+
return true;
193+
}
197194

198-
LogDebug(BCLog::TXPACKAGES, "added peer=%d as a candidate for resolving orphan %s\n", peer, gtxid.GetHash().ToString());
195+
if (MaybeAddOrphanResolutionCandidate(unique_parents, wtxid, peer, now)) {
196+
m_orphanage.AddAnnouncer(orphan_tx->GetWitnessHash(), peer);
199197
}
200198

201199
// Return even if the peer isn't an orphan resolution candidate. This would be caught by AlreadyHaveTx.
@@ -231,21 +229,23 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid,
231229
return false;
232230
}
233231

234-
std::optional<std::chrono::seconds> TxDownloadManagerImpl::OrphanResolutionCandidate(NodeId nodeid, const Wtxid& orphan_wtxid, size_t num_parents)
232+
bool TxDownloadManagerImpl::MaybeAddOrphanResolutionCandidate(const std::vector<Txid>& unique_parents, const Wtxid& wtxid, NodeId nodeid, std::chrono::microseconds now)
235233
{
236-
if (m_peer_info.count(nodeid) == 0) return std::nullopt;
237-
if (m_orphanage.HaveTxFromPeer(orphan_wtxid, nodeid)) return std::nullopt;
234+
auto it_peer = m_peer_info.find(nodeid);
235+
if (it_peer == m_peer_info.end()) return false;
236+
if (m_orphanage.HaveTxFromPeer(wtxid, nodeid)) return false;
238237

239238
const auto& peer_entry = m_peer_info.at(nodeid);
240239
const auto& info = peer_entry.m_connection_info;
240+
241241
// TODO: add delays and limits based on the amount of orphan resolution we are already doing
242242
// with this peer, how much they are using the orphanage, etc.
243243
if (!info.m_relay_permissions) {
244244
// This mirrors the delaying and dropping behavior in AddTxAnnouncement in order to preserve
245245
// existing behavior: drop if we are tracking too many invs for this peer already. Each
246246
// orphan resolution involves at least 1 transaction request which may or may not be
247247
// currently tracked in m_txrequest, so we include that in the count.
248-
if (m_txrequest.Count(nodeid) + num_parents > MAX_PEER_TX_ANNOUNCEMENTS) return std::nullopt;
248+
if (m_txrequest.Count(nodeid) + unique_parents.size() > MAX_PEER_TX_ANNOUNCEMENTS) return false;
249249
}
250250

251251
std::chrono::seconds delay{0s};
@@ -258,7 +258,13 @@ std::optional<std::chrono::seconds> TxDownloadManagerImpl::OrphanResolutionCandi
258258
const bool overloaded = !info.m_relay_permissions && m_txrequest.CountInFlight(nodeid) >= MAX_PEER_TX_REQUEST_IN_FLIGHT;
259259
if (overloaded) delay += OVERLOADED_PEER_TX_DELAY;
260260

261-
return delay;
261+
// Treat finding orphan resolution candidate as equivalent to the peer announcing all missing parents.
262+
// In the future, orphan resolution may include more explicit steps
263+
for (const auto& parent_txid : unique_parents) {
264+
m_txrequest.ReceivedInv(nodeid, GenTxid::Txid(parent_txid), info.m_preferred, now + delay);
265+
}
266+
LogDebug(BCLog::TXPACKAGES, "added peer=%d as a candidate for resolving orphan %s\n", nodeid, wtxid.ToString());
267+
return true;
262268
}
263269

264270
std::vector<GenTxid> TxDownloadManagerImpl::GetRequestsToSend(NodeId nodeid, std::chrono::microseconds current_time)
@@ -327,7 +333,7 @@ void TxDownloadManagerImpl::MempoolAcceptedTx(const CTransactionRef& tx)
327333
m_txrequest.ForgetTxHash(tx->GetHash());
328334
m_txrequest.ForgetTxHash(tx->GetWitnessHash());
329335

330-
m_orphanage.AddChildrenToWorkSet(*tx);
336+
m_orphanage.AddChildrenToWorkSet(*tx, m_opts.m_rng);
331337
// If it came from the orphanage, remove it. No-op if the tx is not in txorphanage.
332338
m_orphanage.EraseTx(tx->GetWitnessHash());
333339
}
@@ -400,27 +406,19 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
400406
// means it was already added to vExtraTxnForCompact.
401407
add_extra_compact_tx &= !m_orphanage.HaveTx(wtxid);
402408

403-
auto add_orphan_reso_candidate = [&](const CTransactionRef& orphan_tx, const std::vector<Txid>& unique_parents, NodeId nodeid, std::chrono::microseconds now) {
404-
const auto& wtxid = orphan_tx->GetWitnessHash();
405-
if (auto delay{OrphanResolutionCandidate(nodeid, wtxid, unique_parents.size())}) {
406-
const auto& info = m_peer_info.at(nodeid).m_connection_info;
407-
m_orphanage.AddTx(orphan_tx, nodeid);
408-
409-
// Treat finding orphan resolution candidate as equivalent to the peer announcing all missing parents
410-
// In the future, orphan resolution may include more explicit steps
411-
for (const auto& parent_txid : unique_parents) {
412-
m_txrequest.ReceivedInv(nodeid, GenTxid::Txid(parent_txid), info.m_preferred, now + *delay);
413-
}
414-
LogDebug(BCLog::TXPACKAGES, "added peer=%d as a candidate for resolving orphan %s\n", nodeid, wtxid.ToString());
415-
}
416-
};
417-
418409
// If there is no candidate for orphan resolution, AddTx will not be called. This means
419410
// that if a peer is overloading us with invs and orphans, they will eventually not be
420411
// able to add any more transactions to the orphanage.
421-
add_orphan_reso_candidate(ptx, unique_parents, nodeid, now);
422-
for (const auto& candidate : m_txrequest.GetCandidatePeers(ptx)) {
423-
add_orphan_reso_candidate(ptx, unique_parents, candidate, now);
412+
//
413+
// Search by txid and, if the tx has a witness, wtxid
414+
std::vector<NodeId> orphan_resolution_candidates{nodeid};
415+
m_txrequest.GetCandidatePeers(ptx->GetHash().ToUint256(), orphan_resolution_candidates);
416+
if (ptx->HasWitness()) m_txrequest.GetCandidatePeers(ptx->GetWitnessHash().ToUint256(), orphan_resolution_candidates);
417+
418+
for (const auto& nodeid : orphan_resolution_candidates) {
419+
if (MaybeAddOrphanResolutionCandidate(unique_parents, ptx->GetWitnessHash(), nodeid, now)) {
420+
m_orphanage.AddTx(ptx, nodeid);
421+
}
424422
}
425423

426424
// Once added to the orphan pool, a tx is considered AlreadyHave, and we shouldn't request it anymore.

src/node/txdownloadman_impl.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ class TxDownloadManagerImpl {
194194
/** Helper for getting deduplicated vector of Txids in vin. */
195195
std::vector<Txid> GetUniqueParents(const CTransaction& tx);
196196

197-
/** Determine candidacy (and delay) for potential orphan resolution candidate.
198-
* @returns delay for orphan resolution if this peer is a good candidate for orphan resolution,
199-
* std::nullopt if this peer cannot be added because it has reached download/orphanage limits.
197+
/** If this peer is an orphan resolution candidate for this transaction, treat the unique_parents as announced by
198+
* this peer; add them as new invs to m_txrequest.
199+
* @returns whether this transaction was a valid orphan resolution candidate.
200200
* */
201-
std::optional<std::chrono::seconds> OrphanResolutionCandidate(NodeId nodeid, const Wtxid& orphan_wtxid, size_t num_parents);
201+
bool MaybeAddOrphanResolutionCandidate(const std::vector<Txid>& unique_parents, const Wtxid& wtxid, NodeId nodeid, std::chrono::microseconds now);
202202
};
203203
} // namespace node
204204
#endif // BITCOIN_NODE_TXDOWNLOADMAN_IMPL_H

src/test/fuzz/txorphan.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void initialize_orphanage()
3333
FUZZ_TARGET(txorphan, .init = initialize_orphanage)
3434
{
3535
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
36-
FastRandomContext limit_orphans_rng{/*fDeterministic=*/true};
36+
FastRandomContext orphanage_rng{/*fDeterministic=*/true};
3737
SetMockTime(ConsumeTime(fuzzed_data_provider));
3838

3939
TxOrphanage orphanage;
@@ -79,7 +79,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
7979
// previous loop and potentially the parent of this tx.
8080
if (ptx_potential_parent) {
8181
// Set up future GetTxToReconsider call.
82-
orphanage.AddChildrenToWorkSet(*ptx_potential_parent);
82+
orphanage.AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
8383

8484
// Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
8585
NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
@@ -154,7 +154,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
154154
// test mocktime and expiry
155155
SetMockTime(ConsumeTime(fuzzed_data_provider));
156156
auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
157-
orphanage.LimitOrphans(limit, limit_orphans_rng);
157+
orphanage.LimitOrphans(limit, orphanage_rng);
158158
Assert(orphanage.Size() <= limit);
159159
});
160160

src/test/fuzz/txrequest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,19 @@ class Tester
295295
tracked += m_announcements[txhash][peer].m_state != State::NOTHING;
296296
inflight += m_announcements[txhash][peer].m_state == State::REQUESTED;
297297
candidates += m_announcements[txhash][peer].m_state == State::CANDIDATE;
298+
299+
std::bitset<MAX_PEERS> expected_announcers;
300+
for (int peer = 0; peer < MAX_PEERS; ++peer) {
301+
if (m_announcements[txhash][peer].m_state == State::CANDIDATE || m_announcements[txhash][peer].m_state == State::REQUESTED) {
302+
expected_announcers[peer] = true;
303+
}
304+
}
305+
std::vector<NodeId> candidate_peers;
306+
m_tracker.GetCandidatePeers(TXHASHES[txhash], candidate_peers);
307+
assert(expected_announcers.count() == candidate_peers.size());
308+
for (const auto& peer : candidate_peers) {
309+
assert(expected_announcers[peer]);
310+
}
298311
}
299312
assert(m_tracker.Count(peer) == tracked);
300313
assert(m_tracker.CountInFlight(peer) == inflight);

src/test/orphanage_tests.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -532,19 +532,27 @@ BOOST_AUTO_TEST_CASE(peer_worksets)
532532
BOOST_CHECK(orphanage.HaveTxFromPeer(orphan_wtxid, node));
533533
}
534534

535-
// Parent accepted: add child to all 3 worksets.
536-
orphanage.AddChildrenToWorkSet(*tx_missing_parent);
537-
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node0), tx_orphan);
538-
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node1), tx_orphan);
539-
// Don't call GetTxToReconsider(node2) yet because it mutates the workset.
535+
// Parent accepted: child is added to 1 of 3 worksets.
536+
orphanage.AddChildrenToWorkSet(*tx_missing_parent, det_rand);
537+
int node0_reconsider = orphanage.HaveTxToReconsider(node0);
538+
int node1_reconsider = orphanage.HaveTxToReconsider(node1);
539+
int node2_reconsider = orphanage.HaveTxToReconsider(node2);
540+
BOOST_CHECK_EQUAL(node0_reconsider + node1_reconsider + node2_reconsider, 1);
541+
542+
NodeId assigned_peer;
543+
if (node0_reconsider) {
544+
assigned_peer = node0;
545+
} else if (node1_reconsider) {
546+
assigned_peer = node1;
547+
} else {
548+
BOOST_CHECK(node2_reconsider);
549+
assigned_peer = node2;
550+
}
540551

541552
// EraseForPeer also removes that tx from the workset.
542-
orphanage.EraseForPeer(node0);
553+
orphanage.EraseForPeer(assigned_peer);
543554
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node0), nullptr);
544555

545-
// However, the other peers' worksets are not touched.
546-
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node2), tx_orphan);
547-
548556
// Delete this tx, clearing the orphanage.
549557
BOOST_CHECK_EQUAL(orphanage.EraseTx(orphan_wtxid), 1);
550558
BOOST_CHECK_EQUAL(orphanage.Size(), 0);

src/txorphanage.cpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,23 +152,29 @@ void TxOrphanage::LimitOrphans(unsigned int max_orphans, FastRandomContext& rng)
152152
if (nEvicted > 0) LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
153153
}
154154

155-
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx)
155+
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
156156
{
157157
for (unsigned int i = 0; i < tx.vout.size(); i++) {
158158
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
159159
if (it_by_prev != m_outpoint_to_orphan_it.end()) {
160160
for (const auto& elem : it_by_prev->second) {
161161
// Belt and suspenders, each orphan should always have at least 1 announcer.
162162
if (!Assume(!elem->second.announcers.empty())) continue;
163-
for (const auto announcer: elem->second.announcers) {
164-
// Get this source peer's work set, emplacing an empty set if it didn't exist
165-
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
166-
std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(announcer).first->second;
167-
// Add this tx to the work set
168-
orphan_work_set.insert(elem->first);
169-
LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
170-
tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), announcer);
171-
}
163+
164+
// Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing
165+
// inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us
166+
// from processing the orphan by disconnecting.
167+
auto announcer_iter = std::begin(elem->second.announcers);
168+
std::advance(announcer_iter, rng.randrange(elem->second.announcers.size()));
169+
auto announcer = *(announcer_iter);
170+
171+
// Get this source peer's work set, emplacing an empty set if it didn't exist
172+
// (note: if this peer wasn't still connected, we would have removed the orphan tx already)
173+
std::set<Wtxid>& orphan_work_set = m_peer_work_set.try_emplace(announcer).first->second;
174+
// Add this tx to the work set
175+
orphan_work_set.insert(elem->first);
176+
LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
177+
tx.GetHash().ToString(), tx.GetWitnessHash().ToString(), announcer);
172178
}
173179
}
174180
}

src/txorphanage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class TxOrphanage {
6262
void LimitOrphans(unsigned int max_orphans, FastRandomContext& rng);
6363

6464
/** Add any orphans that list a particular tx as a parent into the from peer's work set */
65-
void AddChildrenToWorkSet(const CTransaction& tx);
65+
void AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng);
6666

6767
/** Does this peer have any work to do? */
6868
bool HaveTxToReconsider(NodeId peer);

src/txrequest.cpp

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -574,21 +574,13 @@ class TxRequestTracker::Impl {
574574
}
575575
}
576576

577-
std::vector<NodeId> GetCandidatePeers(const CTransactionRef& tx) const
577+
void GetCandidatePeers(const uint256& txhash, std::vector<NodeId>& result_peers) const
578578
{
579-
// Search by txid and, if the tx has a witness, wtxid
580-
std::vector<uint256> hashes{tx->GetHash().ToUint256()};
581-
if (tx->HasWitness()) hashes.emplace_back(tx->GetWitnessHash().ToUint256());
582-
583-
std::vector<NodeId> result_peers;
584-
for (const uint256& txhash : hashes) {
585-
auto it = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});
586-
while (it != m_index.get<ByTxHash>().end() && it->m_txhash == txhash && it->GetState() != State::COMPLETED) {
587-
result_peers.push_back(it->m_peer);
588-
++it;
589-
}
579+
auto it = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});
580+
while (it != m_index.get<ByTxHash>().end() && it->m_txhash == txhash && it->GetState() != State::COMPLETED) {
581+
result_peers.push_back(it->m_peer);
582+
++it;
590583
}
591-
return result_peers;
592584
}
593585

594586
void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,
@@ -738,7 +730,7 @@ size_t TxRequestTracker::CountInFlight(NodeId peer) const { return m_impl->Count
738730
size_t TxRequestTracker::CountCandidates(NodeId peer) const { return m_impl->CountCandidates(peer); }
739731
size_t TxRequestTracker::Count(NodeId peer) const { return m_impl->Count(peer); }
740732
size_t TxRequestTracker::Size() const { return m_impl->Size(); }
741-
std::vector<NodeId> TxRequestTracker::GetCandidatePeers(const CTransactionRef& tx) const { return m_impl->GetCandidatePeers(tx); }
733+
void TxRequestTracker::GetCandidatePeers(const uint256& txhash, std::vector<NodeId>& result_peers) const { return m_impl->GetCandidatePeers(txhash, result_peers); }
742734
void TxRequestTracker::SanityCheck() const { m_impl->SanityCheck(); }
743735

744736
void TxRequestTracker::PostGetRequestableSanityCheck(std::chrono::microseconds now) const

src/txrequest.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ class TxRequestTracker {
195195
/** Count how many announcements are being tracked in total across all peers and transaction hashes. */
196196
size_t Size() const;
197197

198-
/** For some tx return all peers with non-COMPLETED announcements for its txid or wtxid. The resulting vector may contain duplicate NodeIds. */
199-
std::vector<NodeId> GetCandidatePeers(const CTransactionRef& tx) const;
198+
/** For some txhash (txid or wtxid), finds all peers with non-COMPLETED announcements and appends them to
199+
* result_peers. Does not try to ensure that result_peers contains no duplicates. */
200+
void GetCandidatePeers(const uint256& txhash, std::vector<NodeId>& result_peers) const;
200201

201202
/** Access to the internal priority computation (testing only) */
202203
uint64_t ComputePriority(const uint256& txhash, NodeId peer, bool preferred) const;

0 commit comments

Comments
 (0)