Bitcoin ABC 0.32.4
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 chains.reserve(utxos.size());
163 for (auto &utxo : utxos) {
164 chains.emplace_back(
165 oneInOneOutChain(config, std::move(utxo), chainSizePerBlock));
166 }
167
168 auto chainman = Assert(node.chainman.get());
169 Chainstate &activeChainState = chainman->ActiveChainstate();
170
171 // Current tip will be last valid block.
172 CBlockIndex *tipBeforeInvalidate = activeChainState.m_chain.Tip();
173 assert(tipBeforeInvalidate != nullptr);
174
175 CBlockIndex *blockToInvalidate = nullptr;
176
177 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
178 assert(mempool.size() == 0);
179
180 // Build blocks
181 TestMemPoolEntryHelper entry;
182 entry.nFee = 1337 * SATOSHI;
183 for (const auto &chain : chains) {
184 {
185 LOCK2(cs_main, mempool.cs);
186 for (const auto &tx : chain) {
187 mempool.addUnchecked(entry.FromTx(tx));
188 }
189 }
190 assert(mempool.size() == chain.size());
191 MineBlock(config, node, SCRIPT_PUB_KEY);
192 assert(mempool.size() == 0);
193
194 assert(activeChainState.m_chain.Tip()->nTx ==
195 chain.size() + 1 /* coinbase */);
196
197 if (blockToInvalidate == nullptr) {
198 blockToInvalidate = activeChainState.m_chain.Tip();
199 }
200 }
201 CBlockIndex *mostWorkTip = activeChainState.m_chain.Tip();
202
203 bench.run([&] {
205
206 // Disconnect blocks with long transaction chains
207 activeChainState.InvalidateBlock(state, blockToInvalidate);
208 assert(state.IsValid());
209
210 activeChainState.ActivateBestChain(state);
211 assert(state.IsValid());
212 assert(activeChainState.m_chain.Tip() == tipBeforeInvalidate);
213
214 // Transactions should be stuffed back into the mempool.
215 assert(mempool.size() == reorgDepth * chainSizePerBlock);
216
217 if (!includeMempoolTxRemoval) {
218 // As of writing this test, removing transactions from mempool
219 // during re-connect takes significant amount of time, so we allow
220 // to test both with and without this process.
221 mempool.clear();
222 }
223
224 // Reconnect block
225 {
226 LOCK(cs_main);
227 activeChainState.ResetBlockFailureFlags(blockToInvalidate);
228 }
229
230 activeChainState.ActivateBestChain(state);
231 assert(state.IsValid());
232 assert(activeChainState.m_chain.Tip() == mostWorkTip);
233 assert(mempool.size() == 0);
234 });
235}
236
237static void
239 benchmark::Bench &bench,
240 const std::vector<std::vector<CTransactionRef>> &chains) {
241 TestMemPoolEntryHelper entry;
242 entry.nFee = 1337 * SATOSHI;
243
244 auto chainman = Assert(node.chainman.get());
245 Chainstate &activeChainState = chainman->ActiveChainstate();
246 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
247
248 // Fill mempool
249 size_t txCount = 0;
250 for (const auto &chain : chains) {
251 LOCK2(cs_main, mempool.cs);
252 for (const auto &tx : chain) {
253 mempool.addUnchecked(entry.FromTx(tx));
254 ++txCount;
255 }
256 }
257 assert(mempool.size() == txCount);
258
259 const CScript dummy = CScript() << OP_TRUE;
260 bench.run([&] {
261 auto blocktemplate =
262 node::BlockAssembler{config, activeChainState, &mempool}
263 .CreateNewBlock(dummy);
264 assert(blocktemplate);
265 // +1 for coinbase
266 assert(blocktemplate->block.vtx.size() == txCount + 1);
267 });
268}
269
270static void
272 const std::vector<std::vector<CTransactionRef>> &chains,
273 bool revFee = true) {
274 std::list<CTxMemPool> pools;
275
276 // Note: in order to isolate how long eviction takes (as opposed to add +
277 // eviction), we are forced to pre-create all the pools we will be needing
278 // up front.
279
280 bench.epochs(2).epochIterations(1);
281
282 for (uint64_t i = 0; i < bench.epochs() * bench.epochIterations() + 1;
283 ++i) {
284 CTxMemPool::Options mempool_opts{
285 .check_ratio = 0,
286 };
287 pools.emplace_back(config, mempool_opts);
288 CTxMemPool &pool = pools.back();
289 TestMemPoolEntryHelper entry;
290 // Fill mempool
291 size_t txCount = 0;
292 entry.nFee = 1337 * SATOSHI;
293 // add in order of decreasing fee if revFee, increasing otherwise
294 const Amount feeBump =
295 revFee ? int64_t(-1) * SATOSHI : int64_t(1) * SATOSHI;
296 for (const auto &chain : chains) {
297 if (revFee) {
298 entry.nFee += int64_t(chain.size()) * SATOSHI;
299 }
300 LOCK2(cs_main, pool.cs);
301 for (const auto &tx : chain) {
302 pool.addUnchecked(entry.FromTx(tx));
303 entry.nFee += feeBump;
304 // Setting spendCoinbase to false here assumes it's a chain of
305 // 1-in-1-out transaction chain.
306 ++txCount;
307 }
308 if (revFee) {
309 entry.nFee += int64_t(chain.size()) * SATOSHI;
310 }
311 }
312 assert(pool.size() == txCount);
313 }
314
315 auto it = pools.begin();
316
317 bench.run([&] {
318 assert(it != pools.end());
319 auto &pool = *it++;
320 LOCK2(cs_main, pool.cs);
321 while (auto prevSize = pool.size()) {
322 pool.TrimToSize(pool.DynamicMemoryUsage() * 99 / 100);
323 assert(pool.size() < prevSize);
324 }
325 });
326}
327
330 RegTestingSetup test_setup{};
331 const Config &config = test_setup.m_node.chainman->GetConfig();
332 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
333 config, createUTXOs(config, 1, test_setup.m_node).back(), 50);
334 benchATMP(test_setup.m_node, bench, chainedTxs);
335}
336
339 RegTestingSetup test_setup{};
340 const Config &config = test_setup.m_node.chainman->GetConfig();
341 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
342 config, createUTXOs(config, 1, test_setup.m_node).back(), 500);
343 benchATMP(test_setup.m_node, bench, chainedTxs);
344}
345
348 RegTestingSetup test_setup{};
349 const Config &config = test_setup.m_node.chainman->GetConfig();
350 const std::vector<CTransactionRef> chainedTxs =
351 twoInOneOutTree(config, test_setup.m_node, 5);
352 assert(chainedTxs.size() == 63);
353 benchATMP(test_setup.m_node, bench, chainedTxs);
354}
355
358 RegTestingSetup test_setup{};
359 const Config &config = test_setup.m_node.chainman->GetConfig();
360 const std::vector<CTransactionRef> chainedTxs =
361 twoInOneOutTree(config, test_setup.m_node, 8);
362 assert(chainedTxs.size() == 511);
363 benchATMP(test_setup.m_node, bench, chainedTxs);
364}
365
369 RegTestingSetup test_setup{};
370 const Config &config = test_setup.m_node.chainman->GetConfig();
371 benchReorg(config, test_setup.m_node, bench, 10, 50, true);
372}
373
377 RegTestingSetup test_setup{};
378 const Config &config = test_setup.m_node.chainman->GetConfig();
379 benchReorg(config, test_setup.m_node, bench, 10, 500, true);
380}
381
386 RegTestingSetup test_setup{};
387 const Config &config = test_setup.m_node.chainman->GetConfig();
388 benchReorg(config, test_setup.m_node, bench, 10, 50, false);
389}
390
395 RegTestingSetup test_setup{};
396 const Config &config = test_setup.m_node.chainman->GetConfig();
397 benchReorg(config, test_setup.m_node, bench, 10, 500, false);
398}
399
402 RegTestingSetup test_setup{};
403 const Config &config = test_setup.m_node.chainman->GetConfig();
404 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
405 benchGenerateNewBlock(config, test_setup.m_node, bench,
406 {oneInOneOutChain(config, std::move(utxo), 50)});
407}
408
411 RegTestingSetup test_setup{};
412 const Config &config = test_setup.m_node.chainman->GetConfig();
413 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
414 benchGenerateNewBlock(config, test_setup.m_node, bench,
415 {oneInOneOutChain(config, std::move(utxo), 500)});
416}
417
421 RegTestingSetup test_setup{};
422 const Config &config = test_setup.m_node.chainman->GetConfig();
423 // create 2000 chains of 50 1-in-1-out each
424 constexpr int NChains = 2000;
425 std::vector<std::vector<CTransactionRef>> chains;
426 chains.reserve(NChains);
427 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
428 for (int i = 0; i < NChains; ++i) {
429 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
430 }
431 benchEviction(config, bench, chains, false);
432}
433
437 RegTestingSetup test_setup{};
438 const Config &config = test_setup.m_node.chainman->GetConfig();
439 // create 2000 chains of 50 1-in-1-out each
440 constexpr int NChains = 2000;
441 std::vector<std::vector<CTransactionRef>> chains;
442 chains.reserve(NChains);
443 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
444 for (int i = 0; i < NChains; ++i) {
445 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
446 }
447 benchEviction(config, bench, chains, true);
448}
449
454
459
462
static constexpr Amount SATOSHI
Definition: amount.h:143
static constexpr Amount COIN
Definition: amount.h:144
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:420
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:376
static void MempoolAcceptance50ChainedTxs(benchmark::Bench &bench)
Tests a chain of 50 1-input-1-output transactions.
Definition: chained_tx.cpp:329
static void MempoolAcceptance511TxTree(benchmark::Bench &bench)
Test a tree of 511 2-inputs-1-output transactions.
Definition: chained_tx.cpp:357
static void MempoolAcceptance63TxTree(benchmark::Bench &bench)
Test a tree of 63 2-inputs-1-output transactions.
Definition: chained_tx.cpp:347
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:436
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:394
static void MempoolAcceptance500ChainedTxs(benchmark::Bench &bench)
Tests a chain of 500 1-input-1-output transactions.
Definition: chained_tx.cpp:338
static void benchGenerateNewBlock(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains)
Definition: chained_tx.cpp:238
static void GenerateBlock50ChainedTxs(benchmark::Bench &bench)
Generate a block with 50 1-input-1-output transactions.
Definition: chained_tx.cpp:401
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:368
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:410
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 void benchEviction(const Config &config, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains, bool revFee=true)
Definition: chained_tx.cpp:271
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:385
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:55
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:221
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition: txmempool.h:317
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:382
unsigned long size() const
Definition: txmempool.h:500
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:734
bool ActivateBestChain(BlockValidationState &state, std::shared_ptr< const CBlock > pblock=nullptr, avalanche::Processor *const avalanche=nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Find the best known block, and make it the tip of the block chain.
CChain m_chain
The current chain of blockheaders we consult and build on.
Definition: validation.h:833
CTxMemPool * GetMempool()
Definition: validation.h:873
void SetBlockFailureFlags(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(voi ResetBlockFailureFlags)(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Set invalidity status to all descendants of a block.
Definition: validation.h:1010
bool InvalidateBlock(BlockValidationState &state, CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Mark a block as invalid.
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:55
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:31
static CTransactionRef MakeTransactionRef()
Definition: transaction.h:316
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:315
@ OP_EQUAL
Definition: script.h:123
@ OP_HASH160
Definition: script.h:164
@ OP_TRUE
Definition: script.h:61
@ OP_DROP
Definition: script.h:101
std::vector< uint8_t > ToByteVector(const T &in)
Definition: script.h:46
Definition: amount.h:19
Validation result for a transaction evaluated by MemPoolAccept (single or package).
Definition: validation.h:213
const ResultType m_result_type
Result type.
Definition: validation.h:224
@ 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:48
#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:105
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())