@@ -524,7 +524,7 @@ class MemPoolAccept
524524 /* m_bypass_limits */ false ,
525525 /* m_coins_to_uncache */ coins_to_uncache,
526526 /* m_test_accept */ false ,
527- /* m_allow_replacement */ false ,
527+ /* m_allow_replacement */ true ,
528528 /* m_allow_sibling_eviction */ false ,
529529 /* m_package_submission */ true ,
530530 /* m_package_feerates */ true ,
@@ -602,8 +602,8 @@ class MemPoolAccept
602602 /* *
603603 * Submission of a subpackage.
604604 * If subpackage size == 1, calls AcceptSingleTransaction() with adjusted ATMPArgs to avoid
605- * package policy restrictions like no CPFP carve out (PackageMempoolChecks) and disabled RBF
606- * (m_allow_replacement), and creates a PackageMempoolAcceptResult wrapping the result.
605+ * package policy restrictions like no CPFP carve out (PackageMempoolChecks)
606+ * and creates a PackageMempoolAcceptResult wrapping the result.
607607 *
608608 * If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs.
609609 *
@@ -666,12 +666,13 @@ class MemPoolAccept
666666 // only tests that are fast should be done here (to avoid CPU DoS).
667667 bool PreChecks (ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
668668
669- // Run checks for mempool replace-by-fee.
669+ // Run checks for mempool replace-by-fee, only used in AcceptSingleTransaction .
670670 bool ReplacementChecks (Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
671671
672672 // Enforce package mempool ancestor/descendant limits (distinct from individual
673- // ancestor/descendant limits done in PreChecks).
673+ // ancestor/descendant limits done in PreChecks) and run Package RBF checks .
674674 bool PackageMempoolChecks (const std::vector<CTransactionRef>& txns,
675+ std::vector<Workspace>& workspaces,
675676 int64_t total_vsize,
676677 PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
677678
@@ -949,7 +950,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
949950 ws.m_iters_conflicting = m_pool.GetIterSet (ws.m_conflicts );
950951
951952 // Note that these modifications are only applicable to single transaction scenarios;
952- // carve-outs and package RBF are disabled for multi-transaction evaluations.
953+ // carve-outs are disabled for multi-transaction evaluations.
953954 CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts .limits ;
954955
955956 // Calculate in-mempool ancestors, up to a limit.
@@ -1088,10 +1089,9 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
10881089 // descendant transaction of a direct conflict to pay a higher feerate than the transaction that
10891090 // might replace them, under these rules.
10901091 if (const auto err_string{PaysMoreThanConflicts (ws.m_iters_conflicting , newFeeRate, hash)}) {
1091- // Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
1092- // TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
1093- // This must be changed if package RBF is enabled.
1094- return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY,
1092+ // This fee-related failure is TX_RECONSIDERABLE because validating in a package may change
1093+ // the result.
1094+ return state.Invalid (TxValidationResult::TX_RECONSIDERABLE,
10951095 strprintf (" insufficient fee%s" , ws.m_sibling_eviction ? " (including sibling eviction)" : " " ), *err_string);
10961096 }
10971097
@@ -1116,16 +1116,15 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
11161116 }
11171117 if (const auto err_string{PaysForRBF (m_subpackage.m_conflicting_fees , ws.m_modified_fees , ws.m_vsize ,
11181118 m_pool.m_opts .incremental_relay_feerate , hash)}) {
1119- // Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
1120- // TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
1121- // This must be changed if package RBF is enabled.
1122- return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY,
1119+ // Result may change in a package context
1120+ return state.Invalid (TxValidationResult::TX_RECONSIDERABLE,
11231121 strprintf (" insufficient fee%s" , ws.m_sibling_eviction ? " (including sibling eviction)" : " " ), *err_string);
11241122 }
11251123 return true ;
11261124}
11271125
11281126bool MemPoolAccept::PackageMempoolChecks (const std::vector<CTransactionRef>& txns,
1127+ std::vector<Workspace>& workspaces,
11291128 const int64_t total_vsize,
11301129 PackageValidationState& package_state)
11311130{
@@ -1136,12 +1135,88 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
11361135 assert (std::all_of (txns.cbegin (), txns.cend (), [this ](const auto & tx)
11371136 { return !m_pool.exists (GenTxid::Txid (tx->GetHash ()));}));
11381137
1138+ assert (txns.size () == workspaces.size ());
1139+
11391140 auto result = m_pool.CheckPackageLimits (txns, total_vsize);
11401141 if (!result) {
11411142 // This is a package-wide error, separate from an individual transaction error.
11421143 return package_state.Invalid (PackageValidationResult::PCKG_POLICY, " package-mempool-limits" , util::ErrorString (result).original );
11431144 }
1144- return true ;
1145+
1146+ // No conflicts means we're finished. Further checks are all RBF-only.
1147+ if (!m_subpackage.m_rbf ) return true ;
1148+
1149+ // We're in package RBF context; replacement proposal must be size 2
1150+ if (workspaces.size () != 2 || !Assume (IsChildWithParents (txns))) {
1151+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY, " package RBF failed: package must be 1-parent-1-child" );
1152+ }
1153+
1154+ // If the package has in-mempool ancestors, we won't consider a package RBF
1155+ // since it would result in a cluster larger than 2.
1156+ // N.B. To relax this constraint we will need to revisit how CCoinsViewMemPool::PackageAddTransaction
1157+ // is being used inside AcceptMultipleTransactions to track available inputs while processing a package.
1158+ for (const auto & ws : workspaces) {
1159+ if (!ws.m_ancestors .empty ()) {
1160+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY, " package RBF failed: new transaction cannot have mempool ancestors" );
1161+ }
1162+ }
1163+
1164+ // Aggregate all conflicts into one set.
1165+ CTxMemPool::setEntries direct_conflict_iters;
1166+ for (Workspace& ws : workspaces) {
1167+ // Aggregate all conflicts into one set.
1168+ direct_conflict_iters.merge (ws.m_iters_conflicting );
1169+ }
1170+
1171+ const auto & parent_ws = workspaces[0 ];
1172+ const auto & child_ws = workspaces[1 ];
1173+
1174+ // Don't consider replacements that would cause us to remove a large number of mempool entries.
1175+ // This limit is not increased in a package RBF. Use the aggregate number of transactions.
1176+ if (const auto err_string{GetEntriesForConflicts (*child_ws.m_ptx , m_pool, direct_conflict_iters,
1177+ m_subpackage.m_all_conflicts )}) {
1178+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY,
1179+ " package RBF failed: too many potential replacements" , *err_string);
1180+ }
1181+
1182+ for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts ) {
1183+ m_subpackage.m_conflicting_fees += it->GetModifiedFee ();
1184+ m_subpackage.m_conflicting_size += it->GetTxSize ();
1185+ }
1186+
1187+ // Use the child as the transaction for attributing errors to.
1188+ const Txid& child_hash = child_ws.m_ptx ->GetHash ();
1189+ if (const auto err_string{PaysForRBF (/* original_fees=*/ m_subpackage.m_conflicting_fees ,
1190+ /* replacement_fees=*/ m_subpackage.m_total_modified_fees ,
1191+ /* replacement_vsize=*/ m_subpackage.m_total_vsize ,
1192+ m_pool.m_opts .incremental_relay_feerate , child_hash)}) {
1193+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY,
1194+ " package RBF failed: insufficient anti-DoS fees" , *err_string);
1195+ }
1196+
1197+ // Ensure this two transaction package is a "chunk" on its own; we don't want the child
1198+ // to be only paying anti-DoS fees
1199+ const CFeeRate parent_feerate (parent_ws.m_modified_fees , parent_ws.m_vsize );
1200+ const CFeeRate package_feerate (m_subpackage.m_total_modified_fees , m_subpackage.m_total_vsize );
1201+ if (package_feerate <= parent_feerate) {
1202+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY,
1203+ " package RBF failed: package feerate is less than parent feerate" ,
1204+ strprintf (" package feerate %s <= parent feerate is %s" , package_feerate.ToString (), parent_feerate.ToString ()));
1205+ }
1206+
1207+ // Check if it's economically rational to mine this package rather than the ones it replaces.
1208+ // This takes the place of ReplacementChecks()'s PaysMoreThanConflicts() in the package RBF setting.
1209+ if (const auto err_tup{ImprovesFeerateDiagram (m_pool, direct_conflict_iters, m_subpackage.m_all_conflicts , m_subpackage.m_total_modified_fees , m_subpackage.m_total_vsize )}) {
1210+ return package_state.Invalid (PackageValidationResult::PCKG_POLICY,
1211+ " package RBF failed: " + err_tup.value ().second , " " );
1212+ }
1213+
1214+ LogPrint (BCLog::TXPACKAGES, " package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s)\n " ,
1215+ txns.front ()->GetHash ().ToString (), txns.front ()->GetWitnessHash ().ToString (),
1216+ txns.back ()->GetHash ().ToString (), txns.back ()->GetWitnessHash ().ToString ());
1217+
1218+
1219+ return true ;
11451220}
11461221
11471222bool MemPoolAccept::PolicyScriptChecks (const ATMPArgs& args, Workspace& ws)
@@ -1215,6 +1290,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
12151290 const bool bypass_limits = args.m_bypass_limits ;
12161291 std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry ;
12171292
1293+ if (!m_subpackage.m_all_conflicts .empty ()) Assume (args.m_allow_replacement );
12181294 // Remove conflicting transactions from the mempool
12191295 for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts )
12201296 {
@@ -1361,7 +1437,13 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
13611437 return MempoolAcceptResult::Failure (ws.m_state );
13621438 }
13631439
1364- if (m_subpackage.m_rbf && !ReplacementChecks (ws)) return MempoolAcceptResult::Failure (ws.m_state );
1440+ if (m_subpackage.m_rbf && !ReplacementChecks (ws)) {
1441+ if (ws.m_state .GetResult () == TxValidationResult::TX_RECONSIDERABLE) {
1442+ // Failed for incentives-based fee reasons. Provide the effective feerate and which tx was included.
1443+ return MempoolAcceptResult::FeeFailure (ws.m_state , CFeeRate (ws.m_modified_fees , ws.m_vsize ), single_wtxid);
1444+ }
1445+ return MempoolAcceptResult::Failure (ws.m_state );
1446+ }
13651447
13661448 // Perform the inexpensive checks first and avoid hashing and signature verification unless
13671449 // those checks pass, to mitigate CPU exhaustion denial-of-service attacks.
@@ -1434,11 +1516,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
14341516 }
14351517
14361518 // Make the coins created by this transaction available for subsequent transactions in the
1437- // package to spend. Since we already checked conflicts in the package and we don't allow
1438- // replacements, we don't need to track the coins spent. Note that this logic will need to be
1439- // updated if package replace-by-fee is allowed in the future.
1440- assert (!args.m_allow_replacement );
1441- assert (!m_subpackage.m_rbf );
1519+ // package to spend. If there are no conflicts within the package, no transaction can spend a coin
1520+ // needed by another transaction in the package. We also need to make sure that no package
1521+ // tx replaces (or replaces the ancestor of) the parent of another package tx. As long as we
1522+ // check these two things, we don't need to track the coins spent.
1523+ // If a package tx conflicts with a mempool tx, PackageMempoolChecks() ensures later that any package RBF attempt
1524+ // has *no* in-mempool ancestors, so we don't have to worry about subsequent transactions in
1525+ // same package spending the same in-mempool outpoints. This needs to be revisited for general
1526+ // package RBF.
14421527 m_viewmempool.PackageAddTransaction (ws.m_ptx );
14431528 }
14441529
@@ -1479,7 +1564,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
14791564
14801565 // Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
14811566 // because it's unnecessary.
1482- if (txns.size () > 1 && !PackageMempoolChecks (txns, m_subpackage.m_total_vsize , package_state)) {
1567+ if (txns.size () > 1 && !PackageMempoolChecks (txns, workspaces, m_subpackage.m_total_vsize , package_state)) {
14831568 return PackageMempoolAcceptResult (package_state, std::move (results));
14841569 }
14851570
0 commit comments