mempool_stress.cpp
1 // Copyright (c) 2011-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <bench/bench.h> 6 #include <consensus/amount.h> 7 #include <policy/policy.h> 8 #include <primitives/transaction.h> 9 #include <random.h> 10 #include <script/script.h> 11 #include <sync.h> 12 #include <test/util/setup_common.h> 13 #include <test/util/txmempool.h> 14 #include <txmempool.h> 15 #include <validation.h> 16 17 #include <cstddef> 18 #include <cstdint> 19 #include <memory> 20 #include <vector> 21 22 class CCoinsViewCache; 23 24 static void AddTx(const CTransactionRef& tx, CTxMemPool& pool, FastRandomContext& det_rand) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) 25 { 26 int64_t nTime = 0; 27 unsigned int nHeight = 1; 28 uint64_t sequence = 0; 29 bool spendsCoinbase = false; 30 unsigned int sigOpCost = 4; 31 LockPoints lp; 32 TryAddToMempool(pool, CTxMemPoolEntry(tx, det_rand.randrange(10000)+1000, nTime, nHeight, sequence, spendsCoinbase, sigOpCost, lp)); 33 } 34 35 struct Available { 36 CTransactionRef ref; 37 size_t vin_left{0}; 38 size_t tx_count; 39 Available(CTransactionRef& ref, size_t tx_count) : ref(ref), tx_count(tx_count){} 40 }; 41 42 // Create a cluster of transactions, randomly. 43 static std::vector<CTransactionRef> CreateCoinCluster(FastRandomContext& det_rand, int childTxs, int min_ancestors) 44 { 45 std::vector<Available> available_coins; 46 std::vector<CTransactionRef> ordered_coins; 47 // Create some base transactions 48 size_t tx_counter = 1; 49 for (auto x = 0; x < 10; ++x) { 50 CMutableTransaction tx = CMutableTransaction(); 51 tx.vin.resize(1); 52 tx.vin[0].prevout = COutPoint(Txid::FromUint256(GetRandHash()), 1); 53 tx.vin[0].scriptSig = CScript() << CScriptNum(tx_counter); 54 tx.vin[0].scriptWitness.stack.push_back(CScriptNum(x).getvch()); 55 tx.vout.resize(det_rand.randrange(10)+2); 56 for (auto& out : tx.vout) { 57 out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; 58 out.nValue = 10 * COIN; 59 } 60 ordered_coins.emplace_back(MakeTransactionRef(tx)); 61 available_coins.emplace_back(ordered_coins.back(), tx_counter++); 62 } 63 for (auto x = 0; x < childTxs && !available_coins.empty(); ++x) { 64 CMutableTransaction tx = CMutableTransaction(); 65 size_t n_ancestors = det_rand.randrange(10)+1; 66 for (size_t ancestor = 0; ancestor < n_ancestors && !available_coins.empty(); ++ancestor){ 67 size_t idx = det_rand.randrange(available_coins.size()); 68 Available coin = available_coins[idx]; 69 Txid hash = coin.ref->GetHash(); 70 // biased towards taking min_ancestors parents, but maybe more 71 size_t n_to_take = det_rand.randrange(2) == 0 ? 72 min_ancestors : 73 min_ancestors + det_rand.randrange(coin.ref->vout.size() - coin.vin_left); 74 for (size_t i = 0; i < n_to_take; ++i) { 75 tx.vin.emplace_back(); 76 tx.vin.back().prevout = COutPoint(hash, coin.vin_left++); 77 tx.vin.back().scriptSig = CScript() << coin.tx_count; 78 tx.vin.back().scriptWitness.stack.push_back(CScriptNum(coin.tx_count).getvch()); 79 } 80 if (coin.vin_left == coin.ref->vin.size()) { 81 coin = available_coins.back(); 82 available_coins.pop_back(); 83 } 84 tx.vout.resize(det_rand.randrange(10)+2); 85 for (auto& out : tx.vout) { 86 out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; 87 out.nValue = 10 * COIN; 88 } 89 } 90 ordered_coins.emplace_back(MakeTransactionRef(tx)); 91 available_coins.emplace_back(ordered_coins.back(), tx_counter++); 92 } 93 return ordered_coins; 94 } 95 96 static void MemPoolAddTransactions(benchmark::Bench& bench) 97 { 98 FastRandomContext det_rand{true}; 99 int childTxs = 50; 100 if (bench.complexityN() > 1) { 101 childTxs = static_cast<int>(bench.complexityN()); 102 } 103 const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN); 104 CTxMemPool& pool = *testing_setup.get()->m_node.mempool; 105 106 std::vector<CTransactionRef> transactions; 107 // Create 1000 clusters of 100 transactions each 108 for (int i=0; i<100; i++) { 109 auto new_txs = CreateCoinCluster(det_rand, childTxs, /*min_ancestors=*/ 1); 110 transactions.insert(transactions.end(), new_txs.begin(), new_txs.end()); 111 } 112 113 LOCK2(cs_main, pool.cs); 114 115 bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { 116 for (auto& tx : transactions) { 117 AddTx(tx, pool, det_rand); 118 } 119 pool.TrimToSize(0, nullptr); 120 }); 121 } 122 123 static void ComplexMemPool(benchmark::Bench& bench) 124 { 125 FastRandomContext det_rand{true}; 126 int childTxs = 50; 127 if (bench.complexityN() > 1) { 128 childTxs = static_cast<int>(bench.complexityN()); 129 } 130 const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN); 131 CTxMemPool& pool = *testing_setup.get()->m_node.mempool; 132 133 std::vector<CTransactionRef> tx_remove_for_block; 134 std::vector<Txid> hashes_remove_for_block; 135 136 LOCK2(cs_main, pool.cs); 137 138 for (int i=0; i<1000; i++) { 139 std::vector<CTransactionRef> transactions = CreateCoinCluster(det_rand, childTxs, /*min_ancestors=*/1); 140 141 // Add all transactions to the mempool. 142 // Also store the first 10 transactions from each cluster as the 143 // transactions we'll "mine" in the benchmark. 144 int tx_count = 0; 145 for (auto& tx : transactions) { 146 if (tx_count < 10) { 147 tx_remove_for_block.push_back(tx); 148 ++tx_count; 149 hashes_remove_for_block.emplace_back(tx->GetHash()); 150 } 151 AddTx(tx, pool, det_rand); 152 } 153 } 154 155 // Since the benchmark will be run repeatedly, we have to leave the mempool 156 // in the same state at the end of the function, so we benchmark both 157 // mining a block and reorging the block's contents back into the mempool. 158 bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { 159 pool.removeForBlock(tx_remove_for_block, /*nBlockHeight=*/100); 160 for (auto& tx: tx_remove_for_block) { 161 AddTx(tx, pool, det_rand); 162 } 163 pool.UpdateTransactionsFromBlock(hashes_remove_for_block); 164 }); 165 } 166 167 static void MemPoolAncestorsDescendants(benchmark::Bench& bench) 168 { 169 FastRandomContext det_rand{true}; 170 int childTxs = 50; 171 if (bench.complexityN() > 1) { 172 childTxs = static_cast<int>(bench.complexityN()); 173 } 174 const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN); 175 CTxMemPool& pool = *testing_setup.get()->m_node.mempool; 176 177 LOCK2(cs_main, pool.cs); 178 179 std::vector<CTransactionRef> transactions = CreateCoinCluster(det_rand, childTxs, /*min_ancestors=*/1); 180 for (auto& tx : transactions) { 181 AddTx(tx, pool, det_rand); 182 } 183 184 CTxMemPool::txiter first_tx = *pool.GetIter(transactions[0]->GetHash()); 185 CTxMemPool::txiter last_tx = *pool.GetIter(transactions.back()->GetHash()); 186 187 bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { 188 CTxMemPool::setEntries dummy; 189 ankerl::nanobench::doNotOptimizeAway(dummy); 190 pool.CalculateDescendants({first_tx}, dummy); 191 ankerl::nanobench::doNotOptimizeAway(pool.CalculateMemPoolAncestors(*last_tx)); 192 }); 193 } 194 195 196 static void MempoolCheck(benchmark::Bench& bench) 197 { 198 FastRandomContext det_rand{true}; 199 auto testing_setup = MakeNoLogFileContext<TestChain100Setup>(ChainType::REGTEST, {.extra_args = {"-checkmempool=1"}}); 200 CTxMemPool& pool = *testing_setup.get()->m_node.mempool; 201 LOCK2(cs_main, pool.cs); 202 testing_setup->PopulateMempool(det_rand, 400, true); 203 const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip(); 204 205 bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { 206 // Bump up the spendheight so we don't hit premature coinbase spend errors. 207 pool.check(coins_tip, /*spendheight=*/300); 208 }); 209 } 210 211 BENCHMARK(MemPoolAncestorsDescendants); 212 BENCHMARK(MemPoolAddTransactions); 213 BENCHMARK(ComplexMemPool); 214 BENCHMARK(MempoolCheck);