test: rewrite PopulateMempool to not violate mempool policy (cluster size) limits
What changed, and why it matters
This is a test-only code change. It fixes a helper function used by Bitcoin Core's internal test suite so that the fake transactions it creates no longer accidentally break the simulated mempool's own rules. It does not change any production code that runs on real Bitcoin nodes, so it cannot affect real users or the live network.
No security action required. Treat as a normal test-quality improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit rewrites PopulateMempool in src/test/util/setup_common.cpp, a test fixture helper. Previously it could generate transactions whose ancestor/descendant clusters exceeded mempool policy limits, causing tests to fail or behave unreliably under stricter cluster-size policy. The patch reduces the random input count (1-5 instead of 1-24), keeps output count at 1-25, adds an undo buffer for consumed inputs, and only applies the mempool changeset when CheckMemPoolPolicyLimits() passes. If the policy check fails, inputs are restored and the transaction is not counted. This is purely a robustness improvement for unit/functional tests.
Changed components
src/test/util/setup_common.cppTestChain100Setup::PopulateMempoolInspect captured patch +31 / −16
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index dec985b2..775a958b 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -535,22 +535,24 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio
std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit)
{
std::vector<CTransactionRef> mempool_transactions;
- std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts;
+ std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts, undo_info;
std::transform(m_coinbase_txns.begin(), m_coinbase_txns.end(), std::back_inserter(unspent_prevouts),
[](const auto& tx){ return std::make_pair(COutPoint(tx->GetHash(), 0), tx->vout[0].nValue); });
while (num_transactions > 0 && !unspent_prevouts.empty()) {
- // The number of inputs and outputs are random, between 1 and 24.
+ // The number of inputs and outputs are randomly chosen, between 1-5
+ // and 1-25 respectively.
CMutableTransaction mtx = CMutableTransaction();
- const size_t num_inputs = det_rand.randrange(24) + 1;
+ const size_t num_inputs = det_rand.randrange(5) + 1;
CAmount total_in{0};
for (size_t n{0}; n < num_inputs; ++n) {
if (unspent_prevouts.empty()) break;
const auto& [prevout, amount] = unspent_prevouts.front();
+ undo_info.emplace_back(prevout, amount);
mtx.vin.emplace_back(prevout, CScript());
total_in += amount;
unspent_prevouts.pop_front();
}
- const size_t num_outputs = det_rand.randrange(24) + 1;
+ const size_t num_outputs = det_rand.randrange(25) + 1;
const CAmount fee = 100 * det_rand.randrange(30);
const CAmount amount_per_output = (total_in - fee) / num_outputs;
for (size_t n{0}; n < num_outputs; ++n) {
@@ -558,16 +560,7 @@ std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContex
mtx.vout.emplace_back(amount_per_output, spk);
}
CTransactionRef ptx = MakeTransactionRef(mtx);
- mempool_transactions.push_back(ptx);
- if (amount_per_output > 3000) {
- // If the value is high enough to fund another transaction + fees, keep track of it so
- // it can be used to build a more complex transaction graph. Insert randomly into
- // unspent_prevouts for extra randomness in the resulting structures.
- for (size_t n{0}; n < num_outputs; ++n) {
- unspent_prevouts.emplace_back(COutPoint(ptx->GetHash(), n), amount_per_output);
- std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]);
- }
- }
+ bool success{true};
if (submit) {
LOCK2(cs_main, m_node.mempool->cs);
LockPoints lp;
@@ -575,9 +568,31 @@ std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContex
changeset->StageAddition(ptx, /*fee=*/(total_in - num_outputs * amount_per_output),
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
/*spends_coinbase=*/false, /*sigops_cost=*/4, lp);
- changeset->Apply();
+ if (changeset->CheckMemPoolPolicyLimits()) {
+ changeset->Apply();
+ --num_transactions;
+ } else {
+ success = false;
+ // Add the inputs back to unspent prevouts
+ for (const auto& [prevout, amount] : undo_info) {
+ unspent_prevouts.emplace_back(prevout, amount);
+ std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]);
+ }
+ }
+ }
+ if (success) {
+ mempool_transactions.push_back(ptx);
+ if (amount_per_output > 3000) {
+ // If the value is high enough to fund another transaction + fees, keep track of it so
+ // it can be used to build a more complex transaction graph. Insert randomly into
+ // unspent_prevouts for extra randomness in the resulting structures.
+ for (size_t n{0}; n < num_outputs; ++n) {
+ unspent_prevouts.emplace_back(COutPoint(ptx->GetHash(), n), amount_per_output);
+ std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]);
+ }
+ }
}
- --num_transactions;
+ undo_info.clear();
}
return mempool_transactions;
}
Why this scored 15/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.