Avoid violating mempool policy limits in tests
What changed, and why it matters
This commit fixes Bitcoin Core's own internal test and fuzzing helpers so they stop trying to add fake transactions to the test mempool in ways that break the real mempool policy rules. It does not change production network code, wallet behavior, or consensus rules. The change only affects test utilities and one fuzz target, making the tests more realistic and preventing false fuzz failures.
No production action required. Treat as routine test infrastructure maintenance. Ensure fuzz corpus regeneration or CI fuzz runs pass after the change.
Security signals we found
Test-only change with no production code path modified
Prevents test/fuzz harness from constructing invalid mempool states
Aligns fuzz target with mempool policy limit behavior
Evidence from the diff
The patch modifies AddToMempool() in src/test/util/txmempool.cpp to call CheckMemPoolPolicyLimits() before Apply() on a mempool changeset, so test code only applies a transaction when it respects mempool policy limits. It also updates the package_rbf fuzz target to handle the now-possible failure case (cluster size limits) by checking whether the transaction actually entered the mempool and bailing out of that iteration if not. Additionally, it switches the replacement size metric from GetTxSize() to GetAdjustedWeight() to align with current RBF diagram logic. These are test-only correctness improvements.
Changed components
src/test/util/txmempool.cppsrc/test/util/txmempool.hsrc/test/fuzz/rbf.cppInspect captured patch +21 / −7
diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp
index 2ca57341..02ec3cb0 100644
--- a/src/test/fuzz/rbf.cpp
+++ b/src/test/fuzz/rbf.cpp
@@ -120,8 +120,8 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
replacement_tx->vin[0].prevout = g_outpoints.at(iter++);
CTransaction replacement_tx_final{*replacement_tx};
auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);
- int32_t replacement_vsize = replacement_entry.GetTxSize();
- int64_t running_vsize_total{replacement_vsize};
+ int32_t replacement_weight = replacement_entry.GetAdjustedWeight();
+ int64_t running_vsize_total{replacement_entry.GetTxSize()};
LOCK2(cs_main, pool.cs);
@@ -144,6 +144,14 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
}
assert(!pool.GetIter(parent_entry.GetTx().GetHash()));
AddToMempool(pool, parent_entry);
+
+ // It's possible that adding this to the mempool failed due to cluster
+ // size limits; if so bail out.
+ if(!pool.GetIter(parent_entry.GetTx().GetHash())) {
+ mempool_txs.pop_back();
+ continue;
+ }
+
child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
mempool_txs.emplace_back(child);
const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
@@ -155,6 +163,12 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
}
if (!pool.GetIter(child_entry.GetTx().GetHash())) {
AddToMempool(pool, child_entry);
+ // Adding this transaction to the mempool may fail due to cluster
+ // size limits; if so bail out.
+ if(!pool.GetIter(child_entry.GetTx().GetHash())) {
+ mempool_txs.pop_back();
+ continue;
+ }
}
if (fuzzed_data_provider.ConsumeBool()) {
@@ -165,7 +179,7 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
// Pick some transactions at random to be the direct conflicts
CTxMemPool::setEntries direct_conflicts;
for (auto& tx : mempool_txs) {
- if (fuzzed_data_provider.ConsumeBool()) {
+ if (fuzzed_data_provider.ConsumeBool() && pool.GetIter(tx.GetHash())) {
direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
}
}
@@ -206,11 +220,11 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
FeeFrac replaced;
for (auto txiter : all_conflicts) {
replaced.fee += txiter->GetModifiedFee();
- replaced.size += txiter->GetTxSize();
+ replaced.size += txiter->GetAdjustedWeight();
}
// The total fee & size of the new diagram minus replaced fee & size should be the total
// fee & size of the old diagram minus replacement fee & size.
- assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
+ assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_weight}));
}
// If internals report error, wrapper should too
diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp
index e85c496d..980621de 100644
--- a/src/test/util/txmempool.cpp
+++ b/src/test/util/txmempool.cpp
@@ -218,5 +218,5 @@ void AddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry)
changeset->StageAddition(entry.GetSharedTx(), entry.GetFee(),
entry.GetTime().count(), entry.GetHeight(), entry.GetSequence(),
entry.GetSpendsCoinbase(), entry.GetSigOpCost(), entry.GetLockPoints());
- changeset->Apply();
+ if (changeset->CheckMemPoolPolicyLimits()) changeset->Apply();
}
diff --git a/src/test/util/txmempool.h b/src/test/util/txmempool.h
index 36caad2a..2ed498a9 100644
--- a/src/test/util/txmempool.h
+++ b/src/test/util/txmempool.h
@@ -64,7 +64,7 @@ void CheckMempoolEphemeralInvariants(const CTxMemPool& tx_pool);
void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool);
/** One-line wrapper for creating a mempool changeset with a single transaction
- * and applying it. */
+ * and applying it if the policy limits are respected. */
void AddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry);
#endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H
Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.