@@ -174,9 +174,37 @@ void TxDownloadManagerImpl::DisconnectedPeer(NodeId nodeid)
174
174
175
175
bool TxDownloadManagerImpl::AddTxAnnouncement (NodeId peer, const GenTxid& gtxid, std::chrono::microseconds now, bool p2p_inv)
176
176
{
177
+ // If this is an orphan we are trying to resolve, consider this peer as a orphan resolution candidate instead.
178
+ // - received as an p2p inv
179
+ // - is wtxid matching something in orphanage
180
+ // - exists in orphanage
181
+ // - peer can be an orphan resolution candidate
182
+ if (p2p_inv && gtxid.IsWtxid ()) {
183
+ if (auto orphan_tx{m_orphanage.GetTx (Wtxid::FromUint256 (gtxid.GetHash ()))}) {
184
+ auto unique_parents{GetUniqueParents (*orphan_tx)};
185
+ std::erase_if (unique_parents, [&](const auto & txid){
186
+ return AlreadyHaveTx (GenTxid::Txid (txid), /* include_reconsiderable=*/ false );
187
+ });
188
+
189
+ if (unique_parents.empty ()) return true ;
190
+
191
+ if (auto delay{OrphanResolutionCandidate (peer, Wtxid::FromUint256 (gtxid.GetHash ()), unique_parents.size ())}) {
192
+ m_orphanage.AddAnnouncer (Wtxid::FromUint256 (gtxid.GetHash ()), peer);
193
+
194
+ const auto & info = m_peer_info.at (peer).m_connection_info ;
195
+ for (const auto & parent_txid : unique_parents) {
196
+ m_txrequest.ReceivedInv (peer, GenTxid::Txid (parent_txid), info.m_preferred , now + *delay);
197
+ }
198
+
199
+ LogDebug (BCLog::TXPACKAGES, " added peer=%d as a candidate for resolving orphan %s\n " , peer, gtxid.GetHash ().ToString ());
200
+ }
201
+
202
+ // Return even if the peer isn't an orphan resolution candidate. This would be caught by AlreadyHaveTx.
203
+ return true ;
204
+ }
205
+ }
206
+
177
207
// If this is an inv received from a peer and we already have it, we can drop it.
178
- // If this is a request for the parent of an orphan, we don't drop transactions that we already have. In particular,
179
- // we *do* want to request parents that are in m_lazy_recent_rejects_reconsiderable, since they can be CPFP'd.
180
208
if (p2p_inv && AlreadyHaveTx (gtxid, /* include_reconsiderable=*/ true )) return true ;
181
209
182
210
auto it = m_peer_info.find (peer);
@@ -204,6 +232,36 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid,
204
232
return false ;
205
233
}
206
234
235
+ std::optional<std::chrono::seconds> TxDownloadManagerImpl::OrphanResolutionCandidate (NodeId nodeid, const Wtxid& orphan_wtxid, size_t num_parents)
236
+ {
237
+ if (m_peer_info.count (nodeid) == 0 ) return std::nullopt;
238
+ if (m_orphanage.HaveTxFromPeer (orphan_wtxid, nodeid)) return std::nullopt;
239
+
240
+ const auto & peer_entry = m_peer_info.at (nodeid);
241
+ const auto & info = peer_entry.m_connection_info ;
242
+ // TODO: add delays and limits based on the amount of orphan resolution we are already doing
243
+ // with this peer, how much they are using the orphanage, etc.
244
+ if (!info.m_relay_permissions ) {
245
+ // This mirrors the delaying and dropping behavior in AddTxAnnouncement in order to preserve
246
+ // existing behavior: drop if we are tracking too many invs for this peer already. Each
247
+ // orphan resolution involves at least 1 transaction request which may or may not be
248
+ // currently tracked in m_txrequest, so we include that in the count.
249
+ if (m_txrequest.Count (nodeid) + num_parents > MAX_PEER_TX_ANNOUNCEMENTS) return std::nullopt;
250
+ }
251
+
252
+ std::chrono::seconds delay{0s};
253
+ if (!info.m_preferred ) delay += NONPREF_PEER_TX_DELAY;
254
+ // The orphan wtxid is used, but resolution entails requesting the parents by txid. Sometimes
255
+ // parent and child are announced and thus requested around the same time, and we happen to
256
+ // receive child sooner. Waiting a few seconds may allow us to cancel the orphan resolution
257
+ // request if the parent arrives in that time.
258
+ if (m_num_wtxid_peers > 0 ) delay += TXID_RELAY_DELAY;
259
+ const bool overloaded = !info.m_relay_permissions && m_txrequest.CountInFlight (nodeid) >= MAX_PEER_TX_REQUEST_IN_FLIGHT;
260
+ if (overloaded) delay += OVERLOADED_PEER_TX_DELAY;
261
+
262
+ return delay;
263
+ }
264
+
207
265
std::vector<GenTxid> TxDownloadManagerImpl::GetRequestsToSend (NodeId nodeid, std::chrono::microseconds current_time)
208
266
{
209
267
std::vector<GenTxid> requests;
@@ -363,26 +421,42 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
363
421
std::erase_if (unique_parents, [&](const auto & txid){
364
422
return AlreadyHaveTx (GenTxid::Txid (txid), /* include_reconsiderable=*/ false );
365
423
});
366
- const auto current_time{GetTime<std::chrono::microseconds>()};
367
-
368
- for (const uint256& parent_txid : unique_parents) {
369
- // Here, we only have the txid (and not wtxid) of the
370
- // inputs, so we only request in txid mode, even for
371
- // wtxidrelay peers.
372
- // Eventually we should replace this with an improved
373
- // protocol for getting all unconfirmed parents.
374
- const auto gtxid{GenTxid::Txid (parent_txid)};
375
- AddTxAnnouncement (nodeid, gtxid, current_time, /* p2p_inv=*/ false );
424
+ const auto now{GetTime<std::chrono::microseconds>()};
425
+ const auto & wtxid = ptx->GetWitnessHash ();
426
+ // Potentially flip add_extra_compact_tx to false if tx is already in orphanage, which
427
+ // means it was already added to vExtraTxnForCompact.
428
+ add_extra_compact_tx &= !m_orphanage.HaveTx (wtxid);
429
+
430
+ auto add_orphan_reso_candidate = [&](const CTransactionRef& orphan_tx, const std::vector<Txid>& unique_parents, NodeId nodeid, std::chrono::microseconds now) {
431
+ const auto & wtxid = orphan_tx->GetWitnessHash ();
432
+ if (auto delay{OrphanResolutionCandidate (nodeid, wtxid, unique_parents.size ())}) {
433
+ const auto & info = m_peer_info.at (nodeid).m_connection_info ;
434
+ m_orphanage.AddTx (orphan_tx, nodeid);
435
+
436
+ // Treat finding orphan resolution candidate as equivalent to the peer announcing all missing parents
437
+ // In the future, orphan resolution may include more explicit steps
438
+ for (const auto & parent_txid : unique_parents) {
439
+ m_txrequest.ReceivedInv (nodeid, GenTxid::Txid (parent_txid), info.m_preferred , now + *delay);
440
+ }
441
+ LogDebug (BCLog::TXPACKAGES, " added peer=%d as a candidate for resolving orphan %s\n " , nodeid, wtxid.ToString ());
442
+ }
443
+ };
444
+
445
+ // If there is no candidate for orphan resolution, AddTx will not be called. This means
446
+ // that if a peer is overloading us with invs and orphans, they will eventually not be
447
+ // able to add any more transactions to the orphanage.
448
+ add_orphan_reso_candidate (ptx, unique_parents, nodeid, now);
449
+ for (const auto & candidate : m_txrequest.GetCandidatePeers (ptx)) {
450
+ add_orphan_reso_candidate (ptx, unique_parents, candidate, now);
376
451
}
377
452
378
- // Potentially flip add_extra_compact_tx to false if AddTx returns false because the tx was already there
379
- add_extra_compact_tx &= m_orphanage.AddTx (ptx, nodeid);
380
-
381
453
// Once added to the orphan pool, a tx is considered AlreadyHave, and we shouldn't request it anymore.
382
454
m_txrequest.ForgetTxHash (tx.GetHash ());
383
455
m_txrequest.ForgetTxHash (tx.GetWitnessHash ());
384
456
385
457
// DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
458
+ // Note that, if the orphanage reaches capacity, it's possible that we immediately evict
459
+ // the transaction we just added.
386
460
m_orphanage.LimitOrphans (m_opts.m_max_orphan_txs , m_opts.m_rng );
387
461
} else {
388
462
unique_parents.clear ();
0 commit comments