Skip to content

Commit a100c42

Browse files
committed
Merge bitcoin/bitcoin#24927: Add test util to populate mempool with random transactions, fix #24634 bug
d2f8f1b use testing setup mempool in ComplexMemPool bench (glozow) aecc332 create and use mempool transactions using real coins in MempoolCheck (glozow) 2118750 [test util] to populate mempool with random transactions/packages (glozow) 5374dfc [test util] use -checkmempool for TestingSetup mempool check ratio (glozow) d7d9c7b [test util] add chain name to TestChain100Setup ctor (glozow) Pull request description: Fixes #24634 by using the `testing_setup`'s actual mempool rather than a locally-declared mempool for running `check()`. Also creates a test utility for populating the mempool with a bunch of random transactions. I imagine this could be useful in other places as well; it was necessary here because we needed the mempool to contain transactions *spending coins available in the current chainstate*. The existing `CreateOrderedCoins()` is insufficient because it creates coins out of thin air. Also implements the separate suggestion to use the `TestingSetup` mempool in `ComplexMemPool` bench. ACKs for top commit: laanwj: Code review ACK d2f8f1b Tree-SHA512: 44ab5a9e55b126b5a5bc33f05fbad1380b9c43c84736c7cf487be025e0e3f5d75216ccf5a3088b0935da817e3dacfba99d2885f75bcb6e7eaa24cd20a82c24c8
2 parents 636991d + d2f8f1b commit a100c42

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

src/bench/mempool_stress.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ static void ComplexMemPool(benchmark::Bench& bench)
8888
}
8989
std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/1);
9090
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN);
91-
CTxMemPool pool;
91+
CTxMemPool& pool = *testing_setup.get()->m_node.mempool;
9292
LOCK2(cs_main, pool.cs);
9393
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
9494
for (auto& tx : ordered_coins) {
@@ -102,16 +102,15 @@ static void ComplexMemPool(benchmark::Bench& bench)
102102
static void MempoolCheck(benchmark::Bench& bench)
103103
{
104104
FastRandomContext det_rand{true};
105-
const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000;
106-
const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/5);
107-
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"});
108-
CTxMemPool pool;
105+
auto testing_setup = MakeNoLogFileContext<TestChain100Setup>(CBaseChainParams::REGTEST, {"-checkmempool=1"});
106+
CTxMemPool& pool = *testing_setup.get()->m_node.mempool;
109107
LOCK2(cs_main, pool.cs);
108+
testing_setup->PopulateMempool(det_rand, 400, true);
110109
const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip();
111-
for (auto& tx : ordered_coins) AddTx(tx, pool);
112110

113111
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
114-
pool.check(coins_tip, /*spendheight=*/2);
112+
// Bump up the spendheight so we don't hit premature coinbase spend errors.
113+
pool.check(coins_tip, /*spendheight=*/300);
115114
});
116115
}
117116

src/test/txvalidationcache_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
struct Dersig100Setup : public TestChain100Setup {
1717
Dersig100Setup()
18-
: TestChain100Setup{{"-testactivationheight=dersig@102"}} {}
18+
: TestChain100Setup{CBaseChainParams::REGTEST, {"-testactivationheight=dersig@102"}} {}
1919
};
2020

2121
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,

src/test/util/setup_common.cpp

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include <validationinterface.h>
4444
#include <walletinitinterface.h>
4545

46+
#include <algorithm>
4647
#include <functional>
4748
#include <stdexcept>
4849

@@ -161,7 +162,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
161162
GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler);
162163

163164
m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>();
164-
m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1);
165+
m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), m_node.args->GetIntArg("-checkmempool", 1));
165166

166167
m_cache_sizes = CalculateCacheSizes(m_args);
167168

@@ -242,8 +243,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
242243
}
243244
}
244245

245-
TestChain100Setup::TestChain100Setup(const std::vector<const char*>& extra_args)
246-
: TestingSetup{CBaseChainParams::REGTEST, extra_args}
246+
TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args)
247+
: TestingSetup{chain_name, extra_args}
247248
{
248249
SetMockTime(1598887952);
249250
constexpr std::array<unsigned char, 32> vchKey = {
@@ -357,6 +358,52 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio
357358
return mempool_txn;
358359
}
359360

361+
std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit)
362+
{
363+
std::vector<CTransactionRef> mempool_transactions;
364+
std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts;
365+
std::transform(m_coinbase_txns.begin(), m_coinbase_txns.end(), std::back_inserter(unspent_prevouts),
366+
[](const auto& tx){ return std::make_pair(COutPoint(tx->GetHash(), 0), tx->vout[0].nValue); });
367+
while (num_transactions > 0 && !unspent_prevouts.empty()) {
368+
// The number of inputs and outputs are random, between 1 and 24.
369+
CMutableTransaction mtx = CMutableTransaction();
370+
const size_t num_inputs = det_rand.randrange(24) + 1;
371+
CAmount total_in{0};
372+
for (size_t n{0}; n < num_inputs; ++n) {
373+
if (unspent_prevouts.empty()) break;
374+
const auto& [prevout, amount] = unspent_prevouts.front();
375+
mtx.vin.push_back(CTxIn(prevout, CScript()));
376+
total_in += amount;
377+
unspent_prevouts.pop_front();
378+
}
379+
const size_t num_outputs = det_rand.randrange(24) + 1;
380+
// Approximately 1000sat "fee," equal output amounts.
381+
const CAmount amount_per_output = (total_in - 1000) / num_outputs;
382+
for (size_t n{0}; n < num_outputs; ++n) {
383+
CScript spk = CScript() << CScriptNum(num_transactions + n);
384+
mtx.vout.push_back(CTxOut(amount_per_output, spk));
385+
}
386+
CTransactionRef ptx = MakeTransactionRef(mtx);
387+
mempool_transactions.push_back(ptx);
388+
if (amount_per_output > 2000) {
389+
// If the value is high enough to fund another transaction + fees, keep track of it so
390+
// it can be used to build a more complex transaction graph. Insert randomly into
391+
// unspent_prevouts for extra randomness in the resulting structures.
392+
for (size_t n{0}; n < num_outputs; ++n) {
393+
unspent_prevouts.push_back(std::make_pair(COutPoint(ptx->GetHash(), n), amount_per_output));
394+
std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]);
395+
}
396+
}
397+
if (submit) {
398+
LOCK2(m_node.mempool->cs, cs_main);
399+
LockPoints lp;
400+
m_node.mempool->addUnchecked(CTxMemPoolEntry(ptx, 1000, 0, 1, false, 4, lp));
401+
}
402+
--num_transactions;
403+
}
404+
return mempool_transactions;
405+
}
406+
360407
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const
361408
{
362409
return FromTx(MakeTransactionRef(tx));

src/test/util/setup_common.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ class CScript;
122122
* Testing fixture that pre-creates a 100-block REGTEST-mode block chain
123123
*/
124124
struct TestChain100Setup : public TestingSetup {
125-
TestChain100Setup(const std::vector<const char*>& extra_args = {});
125+
TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST,
126+
const std::vector<const char*>& extra_args = {});
126127

127128
/**
128129
* Create a new block with just given transactions, coinbase paying to
@@ -164,6 +165,19 @@ struct TestChain100Setup : public TestingSetup {
164165
CAmount output_amount = CAmount(1 * COIN),
165166
bool submit = true);
166167

168+
/** Create transactions spending from m_coinbase_txns. These transactions will only spend coins
169+
* that exist in the current chain, but may be premature coinbase spends, have missing
170+
* signatures, or violate some other consensus rules. They should only be used for testing
171+
* mempool consistency. All transactions will have some random number of inputs and outputs
172+
* (between 1 and 24). Transactions may or may not be dependent upon each other; if dependencies
173+
* exit, every parent will always be somewhere in the list before the child so each transaction
174+
* can be submitted in the same order they appear in the list.
175+
* @param[in] submit When true, submit transactions to the mempool.
176+
* When false, return them but don't submit them.
177+
* @returns A vector of transactions that can be submitted to the mempool.
178+
*/
179+
std::vector<CTransactionRef> PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit);
180+
167181
std::vector<CTransactionRef> m_coinbase_txns; // For convenience, coinbase transactions
168182
CKey coinbaseKey; // private/public key needed to spend coinbase transactions
169183
};

0 commit comments

Comments
 (0)