Skip to content

Commit 17a8ffd

Browse files
committed
[packages/policy] use package feerate in package validation
This allows CPFP within a package prior to submission to mempool.
1 parent 09f32cf commit 17a8ffd

File tree

3 files changed

+52
-15
lines changed

3 files changed

+52
-15
lines changed

src/test/fuzz/tx_pool.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,18 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
234234
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
235235
::fRequireStandard = fuzzed_data_provider.ConsumeBool();
236236

237-
// Make sure ProcessNewPackage on one transaction works and always fully validates the transaction.
237+
// Make sure ProcessNewPackage on one transaction works.
238238
// The result is not guaranteed to be the same as what is returned by ATMP.
239239
const auto result_package = WITH_LOCK(::cs_main,
240240
return ProcessNewPackage(chainstate, tx_pool, {tx}, true));
241-
auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
242-
Assert(it != result_package.m_tx_results.end());
243-
Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
244-
it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
241+
// If something went wrong due to a package-specific policy, it might not return a
242+
// validation result for the transaction.
243+
if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
244+
auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
245+
Assert(it != result_package.m_tx_results.end());
246+
Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
247+
it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
248+
}
245249

246250
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
247251
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;

src/validation.cpp

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,10 @@ class MemPoolAccept
460460
* partially submitted.
461461
*/
462462
const bool m_package_submission;
463+
/** When true, use package feerates instead of individual transaction feerates for fee-based
464+
* policies such as mempool min fee and min relay fee.
465+
*/
466+
const bool m_package_feerates;
463467

464468
/** Parameters for single transaction mempool validation. */
465469
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
@@ -472,6 +476,7 @@ class MemPoolAccept
472476
/* m_test_accept */ test_accept,
473477
/* m_allow_bip125_replacement */ true,
474478
/* m_package_submission */ false,
479+
/* m_package_feerates */ false,
475480
};
476481
}
477482

@@ -485,6 +490,7 @@ class MemPoolAccept
485490
/* m_test_accept */ true,
486491
/* m_allow_bip125_replacement */ false,
487492
/* m_package_submission */ false, // not submitting to mempool
493+
/* m_package_feerates */ false,
488494
};
489495
}
490496

@@ -498,6 +504,7 @@ class MemPoolAccept
498504
/* m_test_accept */ false,
499505
/* m_allow_bip125_replacement */ false,
500506
/* m_package_submission */ true,
507+
/* m_package_feerates */ true,
501508
};
502509
}
503510

@@ -510,14 +517,16 @@ class MemPoolAccept
510517
std::vector<COutPoint>& coins_to_uncache,
511518
bool test_accept,
512519
bool allow_bip125_replacement,
513-
bool package_submission)
520+
bool package_submission,
521+
bool package_feerates)
514522
: m_chainparams{chainparams},
515523
m_accept_time{accept_time},
516524
m_bypass_limits{bypass_limits},
517525
m_coins_to_uncache{coins_to_uncache},
518526
m_test_accept{test_accept},
519527
m_allow_bip125_replacement{allow_bip125_replacement},
520-
m_package_submission{package_submission}
528+
m_package_submission{package_submission},
529+
m_package_feerates{package_feerates}
521530
{
522531
}
523532
};
@@ -819,9 +828,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
819828
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
820829
strprintf("%d", nSigOpsCost));
821830

822-
// No transactions are allowed below minRelayTxFee except from disconnected
823-
// blocks
824-
if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
831+
// No individual transactions are allowed below minRelayTxFee and mempool min fee except from
832+
// disconnected blocks and transactions in a package. Package transactions will be checked using
833+
// package feerate later.
834+
if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
825835

826836
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
827837
// Calculate in-mempool ancestors, up to a limit.
@@ -1205,20 +1215,35 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
12051215
m_viewmempool.PackageAddTransaction(ws.m_ptx);
12061216
}
12071217

1218+
// Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee.
1219+
// For transactions consisting of exactly one child and its parents, it suffices to use the
1220+
// package feerate (total modified fees / total virtual size) to check this requirement.
1221+
const auto m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0},
1222+
[](int64_t sum, auto& ws) { return sum + ws.m_vsize; });
1223+
const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0},
1224+
[](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; });
1225+
const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize);
1226+
TxValidationState placeholder_state;
1227+
if (args.m_package_feerates &&
1228+
!CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) {
1229+
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low");
1230+
return PackageMempoolAcceptResult(package_state, package_feerate, {});
1231+
}
1232+
12081233
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
12091234
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
12101235
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
12111236
std::string err_string;
12121237
if (txns.size() > 1 && !PackageMempoolChecks(txns, package_state)) {
1213-
return PackageMempoolAcceptResult(package_state, std::move(results));
1238+
return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
12141239
}
12151240

12161241
for (Workspace& ws : workspaces) {
12171242
if (!PolicyScriptChecks(args, ws)) {
12181243
// Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished.
12191244
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
12201245
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1221-
return PackageMempoolAcceptResult(package_state, std::move(results));
1246+
return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
12221247
}
12231248
if (args.m_test_accept) {
12241249
// When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are
@@ -1229,14 +1254,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
12291254
}
12301255
}
12311256

1232-
if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results));
1257+
if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
12331258

12341259
if (!SubmitPackage(args, workspaces, package_state, results)) {
12351260
// PackageValidationState filled in by SubmitPackage().
1236-
return PackageMempoolAcceptResult(package_state, std::move(results));
1261+
return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
12371262
}
12381263

1239-
return PackageMempoolAcceptResult(package_state, std::move(results));
1264+
return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
12401265
}
12411266

12421267
PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)

src/validation.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,19 @@ struct PackageMempoolAcceptResult
234234
* was a package-wide error (see result in m_state), m_tx_results will be empty.
235235
*/
236236
std::map<const uint256, const MempoolAcceptResult> m_tx_results;
237+
/** Package feerate, defined as the aggregated modified fees divided by the total virtual size
238+
* of all transactions in the package. May be unavailable if some inputs were not available or
239+
* a transaction failure caused validation to terminate early. */
240+
std::optional<CFeeRate> m_package_feerate;
237241

238242
explicit PackageMempoolAcceptResult(PackageValidationState state,
239243
std::map<const uint256, const MempoolAcceptResult>&& results)
240244
: m_state{state}, m_tx_results(std::move(results)) {}
241245

246+
explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate,
247+
std::map<const uint256, const MempoolAcceptResult>&& results)
248+
: m_state{state}, m_tx_results(std::move(results)), m_package_feerate{feerate} {}
249+
242250
/** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */
243251
explicit PackageMempoolAcceptResult(const uint256& wtxid, const MempoolAcceptResult& result)
244252
: m_tx_results{ {wtxid, result} } {}

0 commit comments

Comments
 (0)