@@ -596,6 +596,45 @@ class PeerManagerImpl final : public PeerManager
596
596
void ProcessValidTx (NodeId nodeid, const CTransactionRef& tx, const std::list<CTransactionRef>& replaced_transactions)
597
597
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main);
598
598
599
+ /* * Handle the results of package validation: calls ProcessValidTx and ProcessInvalidTx for
600
+ * individual transactions, and caches rejection for the package as a group.
601
+ * @param[in] senders Must contain the nodeids of the peers that provided each transaction
602
+ * in package, in the same order.
603
+ * */
604
+ void ProcessPackageResult (const Package& package, const PackageMempoolAcceptResult& package_result, const std::vector<NodeId>& senders)
605
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main);
606
+
607
+ /* * A package to validate */
608
+ struct PackageToValidate {
609
+ const Package m_txns;
610
+ const std::vector<NodeId> m_senders;
611
+ /* * Construct a 1-parent-1-child package. */
612
+ explicit PackageToValidate (const CTransactionRef& parent,
613
+ const CTransactionRef& child,
614
+ NodeId parent_sender,
615
+ NodeId child_sender) :
616
+ m_txns{parent, child},
617
+ m_senders {parent_sender, child_sender}
618
+ {}
619
+
620
+ std::string ToString () const {
621
+ Assume (m_txns.size () == 2 );
622
+ return strprintf (" parent %s (wtxid=%s, sender=%d) + child %s (wtxid=%s, sender=%d)" ,
623
+ m_txns.front ()->GetHash ().ToString (),
624
+ m_txns.front ()->GetWitnessHash ().ToString (),
625
+ m_senders.front (),
626
+ m_txns.back ()->GetHash ().ToString (),
627
+ m_txns.back ()->GetWitnessHash ().ToString (),
628
+ m_senders.back ());
629
+ }
630
+ };
631
+
632
+ /* * Look for a child of this transaction in the orphanage to form a 1-parent-1-child package,
633
+ * skipping any combinations that have already been tried. Return the resulting package along with
634
+ * the senders of its respective transactions, or std::nullopt if no package is found. */
635
+ std::optional<PackageToValidate> Find1P1CPackage (const CTransactionRef& ptx, NodeId nodeid)
636
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main);
637
+
599
638
/* *
600
639
* Reconsider orphan transactions after a parent has been accepted to the mempool.
601
640
*
@@ -3198,6 +3237,117 @@ void PeerManagerImpl::ProcessValidTx(NodeId nodeid, const CTransactionRef& tx, c
3198
3237
}
3199
3238
}
3200
3239
3240
+ void PeerManagerImpl::ProcessPackageResult (const Package& package, const PackageMempoolAcceptResult& package_result, const std::vector<NodeId>& senders)
3241
+ {
3242
+ AssertLockNotHeld (m_peer_mutex);
3243
+ AssertLockHeld (g_msgproc_mutex);
3244
+ AssertLockHeld (cs_main);
3245
+
3246
+ if (package_result.m_state .IsInvalid ()) {
3247
+ m_recent_rejects_reconsiderable.insert (GetPackageHash (package));
3248
+ }
3249
+ // We currently only expect to process 1-parent-1-child packages. Remove if this changes.
3250
+ if (!Assume (package.size () == 2 )) return ;
3251
+
3252
+ // No package results to look through for PCKG_POLICY or PCKG_MEMPOOL_ERROR
3253
+ if (package_result.m_state .GetResult () == PackageValidationResult::PCKG_POLICY ||
3254
+ package_result.m_state .GetResult () == PackageValidationResult::PCKG_MEMPOOL_ERROR) return ;
3255
+
3256
+ // Iterate backwards to erase in-package descendants from the orphanage before they become
3257
+ // relevant in AddChildrenToWorkSet.
3258
+ auto package_iter = package.rbegin ();
3259
+ auto senders_iter = senders.rbegin ();
3260
+ while (package_iter != package.rend ()) {
3261
+ const auto & tx = *package_iter;
3262
+ const NodeId nodeid = *senders_iter;
3263
+ const auto it_result{package_result.m_tx_results .find (tx->GetWitnessHash ())};
3264
+ if (Assume (it_result != package_result.m_tx_results .end ())) {
3265
+ const auto & tx_result = it_result->second ;
3266
+ switch (tx_result.m_result_type ) {
3267
+ case MempoolAcceptResult::ResultType::VALID:
3268
+ {
3269
+ Assume (tx_result.m_replaced_transactions .has_value ());
3270
+ std::list<CTransactionRef> empty_replacement_list;
3271
+ ProcessValidTx (nodeid, tx, tx_result.m_replaced_transactions .value_or (empty_replacement_list));
3272
+ break ;
3273
+ }
3274
+ case MempoolAcceptResult::ResultType::INVALID:
3275
+ case MempoolAcceptResult::ResultType::DIFFERENT_WITNESS:
3276
+ {
3277
+ // Don't add to vExtraTxnForCompact, as these transactions should have already been
3278
+ // added there when added to the orphanage or rejected for TX_RECONSIDERABLE.
3279
+ // This should be updated if package submission is ever used for transactions
3280
+ // that haven't already been validated before.
3281
+ ProcessInvalidTx (nodeid, tx, tx_result.m_state , /* maybe_add_extra_compact_tx=*/ false );
3282
+ break ;
3283
+ }
3284
+ case MempoolAcceptResult::ResultType::MEMPOOL_ENTRY:
3285
+ {
3286
+ // AlreadyHaveTx() should be catching transactions that are already in mempool.
3287
+ Assume (false );
3288
+ break ;
3289
+ }
3290
+ }
3291
+ }
3292
+ package_iter++;
3293
+ senders_iter++;
3294
+ }
3295
+ }
3296
+
3297
+ std::optional<PeerManagerImpl::PackageToValidate> PeerManagerImpl::Find1P1CPackage (const CTransactionRef& ptx, NodeId nodeid)
3298
+ {
3299
+ AssertLockNotHeld (m_peer_mutex);
3300
+ AssertLockHeld (g_msgproc_mutex);
3301
+ AssertLockHeld (cs_main);
3302
+
3303
+ const auto & parent_wtxid{ptx->GetWitnessHash ()};
3304
+
3305
+ Assume (m_recent_rejects_reconsiderable.contains (parent_wtxid.ToUint256 ()));
3306
+
3307
+ // Prefer children from this peer. This helps prevent censorship attempts in which an attacker
3308
+ // sends lots of fake children for the parent, and we (unluckily) keep selecting the fake
3309
+ // children instead of the real one provided by the honest peer.
3310
+ const auto cpfp_candidates_same_peer{m_orphanage.GetChildrenFromSamePeer (ptx, nodeid)};
3311
+
3312
+ // These children should be sorted from newest to oldest. In the (probably uncommon) case
3313
+ // of children that replace each other, this helps us accept the highest feerate (probably the
3314
+ // most recent) one efficiently.
3315
+ for (const auto & child : cpfp_candidates_same_peer) {
3316
+ Package maybe_cpfp_package{ptx, child};
3317
+ if (!m_recent_rejects_reconsiderable.contains (GetPackageHash (maybe_cpfp_package))) {
3318
+ return PeerManagerImpl::PackageToValidate{ptx, child, nodeid, nodeid};
3319
+ }
3320
+ }
3321
+
3322
+ // If no suitable candidate from the same peer is found, also try children that were provided by
3323
+ // a different peer. This is useful because sometimes multiple peers announce both transactions
3324
+ // to us, and we happen to download them from different peers (we wouldn't have known that these
3325
+ // 2 transactions are related). We still want to find 1p1c packages then.
3326
+ //
3327
+ // If we start tracking all announcers of orphans, we can restrict this logic to parent + child
3328
+ // pairs in which both were provided by the same peer, i.e. delete this step.
3329
+ const auto cpfp_candidates_different_peer{m_orphanage.GetChildrenFromDifferentPeer (ptx, nodeid)};
3330
+
3331
+ // Find the first 1p1c that hasn't already been rejected. We randomize the order to not
3332
+ // create a bias that attackers can use to delay package acceptance.
3333
+ //
3334
+ // Create a random permutation of the indices.
3335
+ std::vector<size_t > tx_indices (cpfp_candidates_different_peer.size ());
3336
+ std::iota (tx_indices.begin (), tx_indices.end (), 0 );
3337
+ Shuffle (tx_indices.begin (), tx_indices.end (), m_rng);
3338
+
3339
+ for (const auto index : tx_indices) {
3340
+ // If we already tried a package and failed for any reason, the combined hash was
3341
+ // cached in m_recent_rejects_reconsiderable.
3342
+ const auto [child_tx, child_sender] = cpfp_candidates_different_peer.at (index);
3343
+ Package maybe_cpfp_package{ptx, child_tx};
3344
+ if (!m_recent_rejects_reconsiderable.contains (GetPackageHash (maybe_cpfp_package))) {
3345
+ return PeerManagerImpl::PackageToValidate{ptx, child_tx, nodeid, child_sender};
3346
+ }
3347
+ }
3348
+ return std::nullopt;
3349
+ }
3350
+
3201
3351
bool PeerManagerImpl::ProcessOrphanTx (Peer& peer)
3202
3352
{
3203
3353
AssertLockHeld (g_msgproc_mutex);
@@ -4377,6 +4527,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
4377
4527
RelayTransaction (tx.GetHash (), tx.GetWitnessHash ());
4378
4528
}
4379
4529
}
4530
+
4531
+ if (m_recent_rejects_reconsiderable.contains (wtxid)) {
4532
+ // When a transaction is already in m_recent_rejects_reconsiderable, we shouldn't submit
4533
+ // it by itself again. However, look for a matching child in the orphanage, as it is
4534
+ // possible that they succeed as a package.
4535
+ LogPrint (BCLog::TXPACKAGES, " found tx %s (wtxid=%s) in reconsiderable rejects, looking for child in orphanage\n " ,
4536
+ txid.ToString (), wtxid.ToString ());
4537
+ if (auto package_to_validate{Find1P1CPackage (ptx, pfrom.GetId ())}) {
4538
+ const auto package_result{ProcessNewPackage (m_chainman.ActiveChainstate (), m_mempool, package_to_validate->m_txns , /* test_accept=*/ false , /* client_maxfeerate=*/ std::nullopt)};
4539
+ LogDebug (BCLog::TXPACKAGES, " package evaluation for %s: %s\n " , package_to_validate->ToString (),
4540
+ package_result.m_state .IsValid () ? " package accepted" : " package rejected" );
4541
+ ProcessPackageResult (package_to_validate->m_txns , package_result, package_to_validate->m_senders );
4542
+ }
4543
+ }
4380
4544
// If a tx is detected by m_recent_rejects it is ignored. Because we haven't
4381
4545
// submitted the tx to our mempool, we won't have computed a DoS
4382
4546
// score for it or determined exactly why we consider it invalid.
@@ -4418,10 +4582,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
4418
4582
}
4419
4583
std::sort (unique_parents.begin (), unique_parents.end ());
4420
4584
unique_parents.erase (std::unique (unique_parents.begin (), unique_parents.end ()), unique_parents.end ());
4585
+
4586
+ // Distinguish between parents in m_recent_rejects and m_recent_rejects_reconsiderable.
4587
+ // We can tolerate having up to 1 parent in m_recent_rejects_reconsiderable since we
4588
+ // submit 1p1c packages. However, fail immediately if any are in m_recent_rejects.
4589
+ std::optional<uint256> rejected_parent_reconsiderable;
4421
4590
for (const uint256& parent_txid : unique_parents) {
4422
4591
if (m_recent_rejects.contains (parent_txid)) {
4423
4592
fRejectedParents = true ;
4424
4593
break ;
4594
+ } else if (m_recent_rejects_reconsiderable.contains (parent_txid) && !m_mempool.exists (GenTxid::Txid (parent_txid))) {
4595
+ // More than 1 parent in m_recent_rejects_reconsiderable: 1p1c will not be
4596
+ // sufficient to accept this package, so just give up here.
4597
+ if (rejected_parent_reconsiderable.has_value ()) {
4598
+ fRejectedParents = true ;
4599
+ break ;
4600
+ }
4601
+ rejected_parent_reconsiderable = parent_txid;
4425
4602
}
4426
4603
}
4427
4604
if (!fRejectedParents ) {
@@ -4435,7 +4612,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
4435
4612
// protocol for getting all unconfirmed parents.
4436
4613
const auto gtxid{GenTxid::Txid (parent_txid)};
4437
4614
AddKnownTx (*peer, parent_txid);
4438
- if (!AlreadyHaveTx (gtxid, /* include_reconsiderable=*/ true )) AddTxAnnouncement (pfrom, gtxid, current_time);
4615
+ // Exclude m_recent_rejects_reconsiderable: the missing parent may have been
4616
+ // previously rejected for being too low feerate. This orphan might CPFP it.
4617
+ if (!AlreadyHaveTx (gtxid, /* include_reconsiderable=*/ false )) AddTxAnnouncement (pfrom, gtxid, current_time);
4439
4618
}
4440
4619
4441
4620
if (m_orphanage.AddTx (ptx, pfrom.GetId ())) {
@@ -4467,6 +4646,19 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
4467
4646
if (state.IsInvalid ()) {
4468
4647
ProcessInvalidTx (pfrom.GetId (), ptx, state, /* maybe_add_extra_compact_tx=*/ true );
4469
4648
}
4649
+ // When a transaction fails for TX_RECONSIDERABLE, look for a matching child in the
4650
+ // orphanage, as it is possible that they succeed as a package.
4651
+ if (state.GetResult () == TxValidationResult::TX_RECONSIDERABLE) {
4652
+ LogPrint (BCLog::TXPACKAGES, " tx %s (wtxid=%s) failed but reconsiderable, looking for child in orphanage\n " ,
4653
+ txid.ToString (), wtxid.ToString ());
4654
+ if (auto package_to_validate{Find1P1CPackage (ptx, pfrom.GetId ())}) {
4655
+ const auto package_result{ProcessNewPackage (m_chainman.ActiveChainstate (), m_mempool, package_to_validate->m_txns , /* test_accept=*/ false , /* client_maxfeerate=*/ std::nullopt)};
4656
+ LogDebug (BCLog::TXPACKAGES, " package evaluation for %s: %s\n " , package_to_validate->ToString (),
4657
+ package_result.m_state .IsValid () ? " package accepted" : " package rejected" );
4658
+ ProcessPackageResult (package_to_validate->m_txns , package_result, package_to_validate->m_senders );
4659
+ }
4660
+ }
4661
+
4470
4662
return ;
4471
4663
}
4472
4664
@@ -6076,7 +6268,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
6076
6268
entry.second .GetHash ().ToString (), entry.first );
6077
6269
}
6078
6270
for (const GenTxid& gtxid : requestable) {
6079
- if (!AlreadyHaveTx (gtxid, /* include_reconsiderable=*/ true )) {
6271
+ // Exclude m_recent_rejects_reconsiderable: we may be requesting a missing parent
6272
+ // that was previously rejected for being too low feerate.
6273
+ if (!AlreadyHaveTx (gtxid, /* include_reconsiderable=*/ false )) {
6080
6274
LogPrint (BCLog::NET, " Requesting %s %s peer=%d\n " , gtxid.IsWtxid () ? " wtx" : " tx" ,
6081
6275
gtxid.GetHash ().ToString (), pto->GetId ());
6082
6276
vGetData.emplace_back (gtxid.IsWtxid () ? MSG_WTX : (MSG_TX | GetFetchFlags (*peer)), gtxid.GetHash ());
0 commit comments