@@ -596,6 +596,45 @@ class PeerManagerImpl final : public PeerManager
596596 void ProcessValidTx (NodeId nodeid, const CTransactionRef& tx, const std::list<CTransactionRef>& replaced_transactions)
597597 EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex, cs_main);
598598
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+
599638 /* *
600639 * Reconsider orphan transactions after a parent has been accepted to the mempool.
601640 *
@@ -3198,6 +3237,117 @@ void PeerManagerImpl::ProcessValidTx(NodeId nodeid, const CTransactionRef& tx, c
31983237 }
31993238}
32003239
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+
32013351bool PeerManagerImpl::ProcessOrphanTx (Peer& peer)
32023352{
32033353 AssertLockHeld (g_msgproc_mutex);
@@ -4377,6 +4527,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43774527 RelayTransaction (tx.GetHash (), tx.GetWitnessHash ());
43784528 }
43794529 }
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+ }
43804544 // If a tx is detected by m_recent_rejects it is ignored. Because we haven't
43814545 // submitted the tx to our mempool, we won't have computed a DoS
43824546 // 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,
44184582 }
44194583 std::sort (unique_parents.begin (), unique_parents.end ());
44204584 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;
44214590 for (const uint256& parent_txid : unique_parents) {
44224591 if (m_recent_rejects.contains (parent_txid)) {
44234592 fRejectedParents = true ;
44244593 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;
44254602 }
44264603 }
44274604 if (!fRejectedParents ) {
@@ -4435,7 +4612,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
44354612 // protocol for getting all unconfirmed parents.
44364613 const auto gtxid{GenTxid::Txid (parent_txid)};
44374614 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);
44394618 }
44404619
44414620 if (m_orphanage.AddTx (ptx, pfrom.GetId ())) {
@@ -4467,6 +4646,19 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
44674646 if (state.IsInvalid ()) {
44684647 ProcessInvalidTx (pfrom.GetId (), ptx, state, /* maybe_add_extra_compact_tx=*/ true );
44694648 }
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+
44704662 return ;
44714663 }
44724664
@@ -6076,7 +6268,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
60766268 entry.second .GetHash ().ToString (), entry.first );
60776269 }
60786270 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 )) {
60806274 LogPrint (BCLog::NET, " Requesting %s %s peer=%d\n " , gtxid.IsWtxid () ? " wtx" : " tx" ,
60816275 gtxid.GetHash ().ToString (), pto->GetId ());
60826276 vGetData.emplace_back (gtxid.IsWtxid () ? MSG_WTX : (MSG_TX | GetFetchFlags (*peer)), gtxid.GetHash ());
0 commit comments