@@ -524,7 +524,7 @@ class MemPoolAccept
524
524
/* m_bypass_limits */ false ,
525
525
/* m_coins_to_uncache */ coins_to_uncache,
526
526
/* m_test_accept */ false ,
527
- /* m_allow_replacement */ false ,
527
+ /* m_allow_replacement */ true ,
528
528
/* m_allow_sibling_eviction */ false ,
529
529
/* m_package_submission */ true ,
530
530
/* m_package_feerates */ true ,
@@ -602,8 +602,8 @@ class MemPoolAccept
602
602
/* *
603
603
* Submission of a subpackage.
604
604
* 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.
607
607
*
608
608
* If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs.
609
609
*
@@ -666,12 +666,13 @@ class MemPoolAccept
666
666
// only tests that are fast should be done here (to avoid CPU DoS).
667
667
bool PreChecks (ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
668
668
669
- // Run checks for mempool replace-by-fee.
669
+ // Run checks for mempool replace-by-fee, only used in AcceptSingleTransaction .
670
670
bool ReplacementChecks (Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
671
671
672
672
// 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 .
674
674
bool PackageMempoolChecks (const std::vector<CTransactionRef>& txns,
675
+ std::vector<Workspace>& workspaces,
675
676
int64_t total_vsize,
676
677
PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
677
678
@@ -949,7 +950,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
949
950
ws.m_iters_conflicting = m_pool.GetIterSet (ws.m_conflicts );
950
951
951
952
// 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.
953
954
CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts .limits ;
954
955
955
956
// Calculate in-mempool ancestors, up to a limit.
@@ -1088,10 +1089,9 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
1088
1089
// descendant transaction of a direct conflict to pay a higher feerate than the transaction that
1089
1090
// might replace them, under these rules.
1090
1091
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,
1095
1095
strprintf (" insufficient fee%s" , ws.m_sibling_eviction ? " (including sibling eviction)" : " " ), *err_string);
1096
1096
}
1097
1097
@@ -1116,16 +1116,15 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
1116
1116
}
1117
1117
if (const auto err_string{PaysForRBF (m_subpackage.m_conflicting_fees , ws.m_modified_fees , ws.m_vsize ,
1118
1118
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,
1123
1121
strprintf (" insufficient fee%s" , ws.m_sibling_eviction ? " (including sibling eviction)" : " " ), *err_string);
1124
1122
}
1125
1123
return true ;
1126
1124
}
1127
1125
1128
1126
bool MemPoolAccept::PackageMempoolChecks (const std::vector<CTransactionRef>& txns,
1127
+ std::vector<Workspace>& workspaces,
1129
1128
const int64_t total_vsize,
1130
1129
PackageValidationState& package_state)
1131
1130
{
@@ -1136,12 +1135,88 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
1136
1135
assert (std::all_of (txns.cbegin (), txns.cend (), [this ](const auto & tx)
1137
1136
{ return !m_pool.exists (GenTxid::Txid (tx->GetHash ()));}));
1138
1137
1138
+ assert (txns.size () == workspaces.size ());
1139
+
1139
1140
auto result = m_pool.CheckPackageLimits (txns, total_vsize);
1140
1141
if (!result) {
1141
1142
// This is a package-wide error, separate from an individual transaction error.
1142
1143
return package_state.Invalid (PackageValidationResult::PCKG_POLICY, " package-mempool-limits" , util::ErrorString (result).original );
1143
1144
}
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 ;
1145
1220
}
1146
1221
1147
1222
bool MemPoolAccept::PolicyScriptChecks (const ATMPArgs& args, Workspace& ws)
@@ -1215,6 +1290,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
1215
1290
const bool bypass_limits = args.m_bypass_limits ;
1216
1291
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry ;
1217
1292
1293
+ if (!m_subpackage.m_all_conflicts .empty ()) Assume (args.m_allow_replacement );
1218
1294
// Remove conflicting transactions from the mempool
1219
1295
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts )
1220
1296
{
@@ -1361,7 +1437,13 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
1361
1437
return MempoolAcceptResult::Failure (ws.m_state );
1362
1438
}
1363
1439
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
+ }
1365
1447
1366
1448
// Perform the inexpensive checks first and avoid hashing and signature verification unless
1367
1449
// those checks pass, to mitigate CPU exhaustion denial-of-service attacks.
@@ -1434,11 +1516,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
1434
1516
}
1435
1517
1436
1518
// 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.
1442
1527
m_viewmempool.PackageAddTransaction (ws.m_ptx );
1443
1528
}
1444
1529
@@ -1479,7 +1564,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
1479
1564
1480
1565
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
1481
1566
// 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)) {
1483
1568
return PackageMempoolAcceptResult (package_state, std::move (results));
1484
1569
}
1485
1570
0 commit comments