Bitcoin ABC 0.30.5
P2P Digital Currency
chained_tx.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 The Bitcoin 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 <common/system.h>
7#include <config.h>
8#include <consensus/amount.h>
9#include <node/context.h>
10#include <node/miner.h>
12#include <script/script.h>
13#include <txmempool.h>
14#include <util/string.h>
15#include <validation.h>
16
17#include <test/util/mining.h>
18#include <test/util/setup_common.h>
19
20#include <list>
21#include <queue>
22#include <vector>
23
26
27static const CScript REDEEM_SCRIPT = CScript() << OP_DROP << OP_TRUE;
28
29static const CScript SCRIPT_PUB_KEY =
31 << OP_EQUAL;
32
33static const CScript SCRIPT_SIG = CScript() << std::vector<uint8_t>(100, 0xff)
35
37static std::vector<CTxIn> createUTXOs(const Config &config, size_t n,
39 std::vector<CTxIn> utxos;
40 utxos.reserve(n);
41
42 for (size_t i = 0; i < n; ++i) {
43 utxos.emplace_back(MineBlock(config, node, SCRIPT_PUB_KEY));
44 }
45
46 for (size_t i = 0; i < COINBASE_MATURITY + 1; ++i) {
47 MineBlock(config, node, SCRIPT_PUB_KEY);
48 }
49
50 return utxos;
51}
52
54static CTransactionRef toTx(const Config &config, CTxIn txin) {
56 tx.vin.emplace_back(txin);
57 tx.vin.back().scriptSig = SCRIPT_SIG;
58 tx.vout.emplace_back(25 * COIN - 1337 * SATOSHI, SCRIPT_PUB_KEY);
59 return MakeTransactionRef(tx);
60}
61
63static std::vector<CTransactionRef>
64oneInOneOutChain(const Config &config, CTxIn utxo, const size_t chainLength) {
65 auto firstTx = toTx(config, std::move(utxo));
66
67 // Build the chain
68 std::vector<CTransactionRef> chain = {firstTx};
69 chain.reserve(chainLength);
70 while (chain.size() < chainLength) {
71 const COutPoint parent(chain.back()->GetId(), 0);
72 const Amount inAmount(chain.back()->vout[0].nValue);
74 tx.vin.emplace_back(CTxIn(parent, SCRIPT_SIG));
75 tx.vout.emplace_back(inAmount - 1337 * SATOSHI, SCRIPT_PUB_KEY);
76 chain.emplace_back(MakeTransactionRef(tx));
77 }
78 assert(chain.size() == chainLength);
79 return chain;
80}
81
85static std::vector<CTransactionRef> twoInOneOutTree(const Config &config,
87 const size_t treeDepth) {
88 // Total number of txs is the sum of nodes at each depth of a binary tree.
89 size_t txs = 0;
90 for (size_t i = 0; i <= treeDepth; ++i) {
91 txs += std::pow(2, i);
92 }
93 const size_t leafs = std::pow(2, treeDepth);
94
95 std::vector<CTransactionRef> chain;
96 chain.reserve(txs);
97
98 std::queue<CTransactionRef> queue;
99 for (auto txin : createUTXOs(config, leafs, node)) {
100 auto tx = toTx(config, std::move(txin));
101 queue.push(tx);
102 chain.emplace_back(tx);
103 }
104
105 while (true) {
107
108 const CTransactionRef txin1 = queue.front();
109 queue.pop();
110 const CTransactionRef txin2 = queue.front();
111 queue.pop();
112
113 const Amount inAmount = txin1->vout[0].nValue + txin2->vout[0].nValue;
114
115 tx.vin.emplace_back(CTxIn(COutPoint(txin1->GetId(), 0), SCRIPT_SIG));
116 tx.vin.emplace_back(CTxIn(COutPoint(txin2->GetId(), 0), SCRIPT_SIG));
117 tx.vout.emplace_back(inAmount - 1337 * SATOSHI, SCRIPT_PUB_KEY);
118
120 chain.push_back(txref);
121 if (queue.empty()) {
122 break;
123 }
124 queue.emplace(txref);
125 }
126 assert(chain.size() == txs);
127 return chain;
128}
129
132 const std::vector<CTransactionRef> chainedTxs) {
133 auto chainman = Assert(node.chainman.get());
134 Chainstate &activeChainState = chainman->ActiveChainstate();
135
136 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
137 assert(mempool.size() == 0);
138
139 bench.run([&] {
141 for (const auto &tx : chainedTxs) {
142 MempoolAcceptResult result =
143 AcceptToMemoryPool(activeChainState, tx, GetTime(),
144 /*bypass_limits=*/false);
145 assert(result.m_result_type ==
147 }
148 mempool.clear();
149 });
150}
151
157static void benchReorg(const Config &config, node::NodeContext &node,
158 benchmark::Bench &bench, size_t reorgDepth,
159 size_t chainSizePerBlock, bool includeMempoolTxRemoval) {
160 auto utxos = createUTXOs(config, reorgDepth, node);
161 std::vector<std::vector<CTransactionRef>> chains;
162 for (auto &utxo : utxos) {
163 chains.emplace_back(
164 oneInOneOutChain(config, std::move(utxo), chainSizePerBlock));
165 }
166
167 auto chainman = Assert(node.chainman.get());
168 Chainstate &activeChainState = chainman->ActiveChainstate();
169
170 // Current tip will be last valid block.
171 CBlockIndex *tipBeforeInvalidate = activeChainState.m_chain.Tip();
172 assert(tipBeforeInvalidate != nullptr);
173
174 CBlockIndex *blockToInvalidate = nullptr;
175
176 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
177 assert(mempool.size() == 0);
178
179 // Build blocks
180 TestMemPoolEntryHelper entry;
181 entry.nFee = 1337 * SATOSHI;
182 for (const auto &chain : chains) {
183 {
184 LOCK2(cs_main, mempool.cs);
185 for (const auto &tx : chain) {
186 mempool.addUnchecked(entry.FromTx(tx));
187 }
188 }
189 assert(mempool.size() == chain.size());
190 MineBlock(config, node, SCRIPT_PUB_KEY);
191 assert(mempool.size() == 0);
192
193 assert(activeChainState.m_chain.Tip()->nTx ==
194 chain.size() + 1 /* coinbase */);
195
196 if (blockToInvalidate == nullptr) {
197 blockToInvalidate = activeChainState.m_chain.Tip();
198 }
199 }
200 CBlockIndex *mostWorkTip = activeChainState.m_chain.Tip();
201
202 bench.run([&] {
204
205 // Disconnect blocks with long transaction chains
206 activeChainState.InvalidateBlock(state, blockToInvalidate);
207 assert(state.IsValid());
208
209 activeChainState.ActivateBestChain(state);
210 assert(state.IsValid());
211 assert(activeChainState.m_chain.Tip() == tipBeforeInvalidate);
212
213 // Transactions should be stuffed back into the mempool.
214 assert(mempool.size() == reorgDepth * chainSizePerBlock);
215
216 if (!includeMempoolTxRemoval) {
217 // As of writing this test, removing transactions from mempool
218 // during re-connect takes significant amount of time, so we allow
219 // to test both with and without this process.
220 mempool.clear();
221 }
222
223 // Reconnect block
224 {
225 LOCK(cs_main);
226 activeChainState.ResetBlockFailureFlags(blockToInvalidate);
227 }
228
229 activeChainState.ActivateBestChain(state);
230 assert(state.IsValid());
231 assert(activeChainState.m_chain.Tip() == mostWorkTip);
232 assert(mempool.size() == 0);
233 });
234}
235
236static void
238 benchmark::Bench &bench,
239 const std::vector<std::vector<CTransactionRef>> &chains) {
240 TestMemPoolEntryHelper entry;
241 entry.nFee = 1337 * SATOSHI;
242
243 auto chainman = Assert(node.chainman.get());
244 Chainstate &activeChainState = chainman->ActiveChainstate();
245 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
246
247 // Fill mempool
248 size_t txCount = 0;
249 for (const auto &chain : chains) {
250 LOCK2(cs_main, mempool.cs);
251 for (const auto &tx : chain) {
252 mempool.addUnchecked(entry.FromTx(tx));
253 ++txCount;
254 }
255 }
256 assert(mempool.size() == txCount);
257
258 const CScript dummy = CScript() << OP_TRUE;
259 bench.run([&] {
260 auto blocktemplate =
261 node::BlockAssembler{config, activeChainState, &mempool}
262 .CreateNewBlock(dummy);
263 assert(blocktemplate);
264 // +1 for coinbase
265 assert(blocktemplate->block.vtx.size() == txCount + 1);
266 });
267}
268
269static void
271 const std::vector<std::vector<CTransactionRef>> &chains,
272 bool revFee = true) {
273 std::list<CTxMemPool> pools;
274
275 // Note: in order to isolate how long eviction takes (as opposed to add +
276 // eviction), we are forced to pre-create all the pools we will be needing
277 // up front.
278
279 bench.epochs(2).epochIterations(1);
280
281 for (uint64_t i = 0; i < bench.epochs() * bench.epochIterations() + 1;
282 ++i) {
283 CTxMemPool::Options mempool_opts{
284 .check_ratio = 0,
285 };
286 pools.emplace_back(mempool_opts);
287 CTxMemPool &pool = pools.back();
288 TestMemPoolEntryHelper entry;
289 // Fill mempool
290 size_t txCount = 0;
291 entry.nFee = 1337 * SATOSHI;
292 // add in order of decreasing fee if revFee, increasing otherwise
293 const Amount feeBump =
294 revFee ? int64_t(-1) * SATOSHI : int64_t(1) * SATOSHI;
295 for (const auto &chain : chains) {
296 if (revFee) {
297 entry.nFee += int64_t(chain.size()) * SATOSHI;
298 }
299 LOCK2(cs_main, pool.cs);
300 for (const auto &tx : chain) {
301 pool.addUnchecked(entry.FromTx(tx));
302 entry.nFee += feeBump;
303 // Setting spendCoinbase to false here assumes it's a chain of
304 // 1-in-1-out transaction chain.
305 ++txCount;
306 }
307 if (revFee) {
308 entry.nFee += int64_t(chain.size()) * SATOSHI;
309 }
310 }
311 assert(pool.size() == txCount);
312 }
313
314 auto it = pools.begin();
315
316 bench.run([&] {
317 assert(it != pools.end());
318 auto &pool = *it++;
319 LOCK2(cs_main, pool.cs);
320 while (auto prevSize = pool.size()) {
321 pool.TrimToSize(pool.DynamicMemoryUsage() * 99 / 100);
322 assert(pool.size() < prevSize);
323 }
324 });
325}
326
329 RegTestingSetup test_setup{};
330 const Config &config = test_setup.m_node.chainman->GetConfig();
331 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
332 config, createUTXOs(config, 1, test_setup.m_node).back(), 50);
333 benchATMP(test_setup.m_node, bench, chainedTxs);
334}
335
338 RegTestingSetup test_setup{};
339 const Config &config = test_setup.m_node.chainman->GetConfig();
340 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
341 config, createUTXOs(config, 1, test_setup.m_node).back(), 500);
342 benchATMP(test_setup.m_node, bench, chainedTxs);
343}
344
347 RegTestingSetup test_setup{};
348 const Config &config = test_setup.m_node.chainman->GetConfig();
349 const std::vector<CTransactionRef> chainedTxs =
350 twoInOneOutTree(config, test_setup.m_node, 5);
351 assert(chainedTxs.size() == 63);
352 benchATMP(test_setup.m_node, bench, chainedTxs);
353}
354
357 RegTestingSetup test_setup{};
358 const Config &config = test_setup.m_node.chainman->GetConfig();
359 const std::vector<CTransactionRef> chainedTxs =
360 twoInOneOutTree(config, test_setup.m_node, 8);
361 assert(chainedTxs.size() == 511);
362 benchATMP(test_setup.m_node, bench, chainedTxs);
363}
364
368 RegTestingSetup test_setup{};
369 const Config &config = test_setup.m_node.chainman->GetConfig();
370 benchReorg(config, test_setup.m_node, bench, 10, 50, true);
371}
372
376 RegTestingSetup test_setup{};
377 const Config &config = test_setup.m_node.chainman->GetConfig();
378 benchReorg(config, test_setup.m_node, bench, 10, 500, true);
379}
380
385 RegTestingSetup test_setup{};
386 const Config &config = test_setup.m_node.chainman->GetConfig();
387 benchReorg(config, test_setup.m_node, bench, 10, 50, false);
388}
389
394 RegTestingSetup test_setup{};
395 const Config &config = test_setup.m_node.chainman->GetConfig();
396 benchReorg(config, test_setup.m_node, bench, 10, 500, false);
397}
398
401 RegTestingSetup test_setup{};
402 const Config &config = test_setup.m_node.chainman->GetConfig();
403 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
404 benchGenerateNewBlock(config, test_setup.m_node, bench,
405 {oneInOneOutChain(config, std::move(utxo), 50)});
406}
407
410 RegTestingSetup test_setup{};
411 const Config &config = test_setup.m_node.chainman->GetConfig();
412 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
413 benchGenerateNewBlock(config, test_setup.m_node, bench,
414 {oneInOneOutChain(config, std::move(utxo), 500)});
415}
416
420 RegTestingSetup test_setup{};
421 const Config &config = test_setup.m_node.chainman->GetConfig();
422 // create 2000 chains of 50 1-in-1-out each
423 std::vector<std::vector<CTransactionRef>> chains;
424 constexpr int NChains = 2000;
425 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
426 for (int i = 0; i < NChains; ++i) {
427 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
428 }
429 benchEviction(config, bench, chains, false);
430}
431
435 RegTestingSetup test_setup{};
436 const Config &config = test_setup.m_node.chainman->GetConfig();
437 // create 2000 chains of 50 1-in-1-out each
438 std::vector<std::vector<CTransactionRef>> chains;
439 constexpr int NChains = 2000;
440 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
441 for (int i = 0; i < NChains; ++i) {
442 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
443 }
444 benchEviction(config, bench, chains, true);
445}
446
451
456
459
static constexpr Amount SATOSHI
Definition: amount.h:143
static constexpr Amount COIN
Definition: amount.h:144
static void benchEviction(const Config &, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains, bool revFee=true)
Definition: chained_tx.cpp:270
static void EvictChained50Tx(benchmark::Bench &bench)
Fill a mempool then evict 2000 x 50 1-input-1-output transactions, CTxMemPool version,...
Definition: chained_tx.cpp:419
static void Reorg10BlocksWith500TxChain(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 500 tx 1-input-1-output chain.
Definition: chained_tx.cpp:375
static void MempoolAcceptance50ChainedTxs(benchmark::Bench &bench)
Tests a chain of 50 1-input-1-output transactions.
Definition: chained_tx.cpp:328
static void MempoolAcceptance511TxTree(benchmark::Bench &bench)
Test a tree of 511 2-inputs-1-output transactions.
Definition: chained_tx.cpp:356
static void MempoolAcceptance63TxTree(benchmark::Bench &bench)
Test a tree of 63 2-inputs-1-output transactions.
Definition: chained_tx.cpp:346
static void EvictChained50TxRev(benchmark::Bench &bench)
Fill a mempool then evict 2000 x 50 1-input-1-output transactions, CTxMemPool version,...
Definition: chained_tx.cpp:434
static void Reorg10BlocksWith500TxChainSkipMempool(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 500 tx 1-input-1-output chain,...
Definition: chained_tx.cpp:393
static void MempoolAcceptance500ChainedTxs(benchmark::Bench &bench)
Tests a chain of 500 1-input-1-output transactions.
Definition: chained_tx.cpp:337
static void benchGenerateNewBlock(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains)
Definition: chained_tx.cpp:237
static void GenerateBlock50ChainedTxs(benchmark::Bench &bench)
Generate a block with 50 1-input-1-output transactions.
Definition: chained_tx.cpp:400
static void Reorg10BlocksWith50TxChain(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 50 tx 1-input-1-output chain.
Definition: chained_tx.cpp:367
static void benchReorg(const Config &config, node::NodeContext &node, benchmark::Bench &bench, size_t reorgDepth, size_t chainSizePerBlock, bool includeMempoolTxRemoval)
Run benchmark that reorganizes blocks with one-input-one-output transaction chains in them.
Definition: chained_tx.cpp:157
static const CScript REDEEM_SCRIPT
This file contains benchmarks focusing on chained transactions in the mempool.
Definition: chained_tx.cpp:27
static std::vector< CTxIn > createUTXOs(const Config &config, size_t n, node::NodeContext &node)
Mine new utxos.
Definition: chained_tx.cpp:37
static void GenerateBlock500ChainedTxs(benchmark::Bench &bench)
Generate a block with 500 1-input-1-output transactions.
Definition: chained_tx.cpp:409
static std::vector< CTransactionRef > twoInOneOutTree(const Config &config, node::NodeContext &node, const size_t treeDepth)
Creates a tree of transactions with 2-inputs-1-output.
Definition: chained_tx.cpp:85
static std::vector< CTransactionRef > oneInOneOutChain(const Config &config, CTxIn utxo, const size_t chainLength)
Creates a chain of transactions with 1-input-1-output.
Definition: chained_tx.cpp:64
static const CScript SCRIPT_PUB_KEY
Definition: chained_tx.cpp:29
static void Reorg10BlocksWith50TxChainSkipMempool(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 50 tx 1-input-1-output chain,...
Definition: chained_tx.cpp:384
BENCHMARK(MempoolAcceptance50ChainedTxs)
static CTransactionRef toTx(const Config &config, CTxIn txin)
Create a transaction spending a coinbase utxo.
Definition: chained_tx.cpp:54
static const CScript SCRIPT_SIG
Definition: chained_tx.cpp:33
static void benchATMP(node::NodeContext &node, benchmark::Bench &bench, const std::vector< CTransactionRef > chainedTxs)
Run benchmark on AcceptToMemoryPool.
Definition: chained_tx.cpp:131
#define Assert(val)
Identity function.
Definition: check.h:84
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
unsigned int nTx
Number of transactions in this block.
Definition: blockindex.h:60
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:150
A mutable version of CTransaction.
Definition: transaction.h:274
std::vector< CTxOut > vout
Definition: transaction.h:277
std::vector< CTxIn > vin
Definition: transaction.h:276
A reference to a CScript: the Hash160 of its serialization (see script.h)
Definition: standard.h:24
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
Definition: txmempool.h:212
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition: txmempool.h:307
void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(void addUnchecked(CTxMemPoolEntryRef entry) EXCLUSIVE_LOCKS_REQUIRED(cs
If sanity-checking is turned on, check makes sure the pool is consistent (does not contain two transa...
Definition: txmempool.h:372
unsigned long size() const
Definition: txmempool.h:488
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:699
CChain m_chain
The current chain of blockheaders we consult and build on.
Definition: validation.h:808
CTxMemPool * GetMempool()
Definition: validation.h:847
bool InvalidateBlock(BlockValidationState &state, CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Mark a block as invalid.
void ResetBlockFailureFlags(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Remove invalidity status from a block and its descendants.
bool ActivateBestChain(BlockValidationState &state, std::shared_ptr< const CBlock > pblock=nullptr, avalanche::Processor *const avalanche=nullptr, bool skip_checkblockindex=false) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Find the best known block, and make it the tip of the block chain.
Definition: config.h:19
bool IsValid() const
Definition: validation.h:119
Main entry point to nanobench's benchmarking facility.
Definition: nanobench.h:616
Bench & run(char const *benchmarkName, Op &&op)
Repeatedly calls op() based on the configuration, and performs measurements.
Definition: nanobench.h:1183
Bench & epochs(size_t numEpochs) noexcept
Controls number of epochs, the number of measurements to perform.
Bench & epochIterations(uint64_t numIters) noexcept
Sets exactly the number of iterations for each epoch.
Generate a new block, without valid proof-of-work.
Definition: miner.h:53
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule).
Definition: consensus.h:32
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
Definition: init.h:28
static CTransactionRef MakeTransactionRef()
Definition: transaction.h:316
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:315
@ OP_EQUAL
Definition: script.h:119
@ OP_HASH160
Definition: script.h:160
@ OP_TRUE
Definition: script.h:57
@ OP_DROP
Definition: script.h:97
std::vector< uint8_t > ToByteVector(const T &in)
Definition: script.h:42
Definition: amount.h:19
Validation result for a transaction evaluated by MemPoolAccept (single or package).
Definition: validation.h:207
const ResultType m_result_type
Result type.
Definition: validation.h:218
@ VALID
Fully validated, valid.
Options struct containing options for constructing a CTxMemPool.
int check_ratio
The ratio used to determine how often sanity checks will run.
NodeContext struct containing references to chain state and connection state.
Definition: context.h:43
#define LOCK2(cs1, cs2)
Definition: sync.h:309
#define LOCK(cs)
Definition: sync.h:306
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition: time.cpp:109
MempoolAcceptResult AcceptToMemoryPool(Chainstate &active_chainstate, const CTransactionRef &tx, int64_t accept_time, bool bypass_limits, bool test_accept, unsigned int heightOverride)
Try to add a transaction to the mempool.
assert(!tx.IsCoinBase())