Bitcoin ABC  0.26.3
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 <config.h>
7 #include <consensus/amount.h>
8 #include <node/context.h>
9 #include <node/miner.h>
10 #include <primitives/transaction.h>
11 #include <script/script.h>
12 #include <txmempool.h>
13 #include <util/string.h>
14 #include <util/system.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 
27 static const CScript REDEEM_SCRIPT = CScript() << OP_DROP << OP_TRUE;
28 
29 static const CScript SCRIPT_PUB_KEY =
31  << OP_EQUAL;
32 
33 static const CScript SCRIPT_SIG = CScript() << std::vector<uint8_t>(100, 0xff)
35 
37 static 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 
54 static 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 
63 static std::vector<CTransactionRef>
64 oneInOneOutChain(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 
85 static 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 
131 static void benchATMP(const Config &config, node::NodeContext &node,
132  benchmark::Bench &bench,
133  const std::vector<CTransactionRef> chainedTxs) {
134  auto chainman = Assert(node.chainman.get());
135  Chainstate &activeChainState = chainman->ActiveChainstate();
136 
137  CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
138  assert(mempool.size() == 0);
139 
140  bench.run([&] {
141  LOCK(::cs_main);
142  for (const auto &tx : chainedTxs) {
143  MempoolAcceptResult result =
144  AcceptToMemoryPool(config, activeChainState, tx, GetTime(),
145  /*bypass_limits=*/false);
146  assert(result.m_result_type ==
148  }
149  mempool.clear();
150  });
151 }
152 
158 static void benchReorg(const Config &config, node::NodeContext &node,
159  benchmark::Bench &bench, size_t reorgDepth,
160  size_t chainSizePerBlock, bool includeMempoolTxRemoval) {
161  auto utxos = createUTXOs(config, reorgDepth, node);
162  std::vector<std::vector<CTransactionRef>> chains;
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  entry.spendsCoinbase = true;
186  LOCK2(cs_main, mempool.cs);
187  for (const auto &tx : chain) {
188  mempool.addUnchecked(entry.FromTx(tx));
189  // Setting spendCoinbase to false here assumes it's a chain of
190  // 1-in-1-out transaction chain.
191  entry.spendsCoinbase = false;
192  }
193  }
194  assert(mempool.size() == chain.size());
195  MineBlock(config, node, SCRIPT_PUB_KEY);
196  assert(mempool.size() == 0);
197 
198  assert(activeChainState.m_chain.Tip()->nTx ==
199  chain.size() + 1 /* coinbase */);
200 
201  if (blockToInvalidate == nullptr) {
202  blockToInvalidate = activeChainState.m_chain.Tip();
203  }
204  }
205  CBlockIndex *mostWorkTip = activeChainState.m_chain.Tip();
206 
207  bench.run([&] {
208  BlockValidationState state;
209 
210  // Disconnect blocks with long transaction chains
211  activeChainState.InvalidateBlock(config, state, blockToInvalidate);
212  assert(state.IsValid());
213 
214  activeChainState.ActivateBestChain(config, state);
215  assert(state.IsValid());
216  assert(activeChainState.m_chain.Tip() == tipBeforeInvalidate);
217 
218  // Transactions should be stuffed back into the mempool.
219  assert(mempool.size() == reorgDepth * chainSizePerBlock);
220 
221  if (!includeMempoolTxRemoval) {
222  // As of writing this test, removing transactions from mempool
223  // during re-connect takes significant amount of time, so we allow
224  // to test both with and without this process.
225  mempool.clear();
226  }
227 
228  // Reconnect block
229  {
230  LOCK(cs_main);
231  activeChainState.ResetBlockFailureFlags(blockToInvalidate);
232  }
233 
234  activeChainState.ActivateBestChain(config, state);
235  assert(state.IsValid());
236  assert(activeChainState.m_chain.Tip() == mostWorkTip);
237  assert(mempool.size() == 0);
238  });
239 }
240 
241 static void
243  benchmark::Bench &bench,
244  const std::vector<std::vector<CTransactionRef>> &chains) {
245  TestMemPoolEntryHelper entry;
246  entry.nFee = 1337 * SATOSHI;
247 
248  auto chainman = Assert(node.chainman.get());
249  Chainstate &activeChainState = chainman->ActiveChainstate();
250  CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
251 
252  // Fill mempool
253  size_t txCount = 0;
254  for (const auto &chain : chains) {
255  entry.spendsCoinbase = true;
256  LOCK2(cs_main, mempool.cs);
257  for (const auto &tx : chain) {
258  mempool.addUnchecked(entry.FromTx(tx));
259  // Setting spendCoinbase to false here assumes it's a chain of
260  // 1-in-1-out transaction chain.
261  entry.spendsCoinbase = false;
262  ++txCount;
263  }
264  }
265  assert(mempool.size() == txCount);
266 
267  const CScript dummy = CScript() << OP_TRUE;
268  bench.run([&] {
269  auto blocktemplate =
270  node::BlockAssembler(config, activeChainState, mempool)
271  .CreateNewBlock(dummy);
272  assert(blocktemplate);
273  // +1 for coinbase
274  assert(blocktemplate->block.vtx.size() == txCount + 1);
275  });
276 }
277 
278 static void
280  const std::vector<std::vector<CTransactionRef>> &chains,
281  bool revFee = true) {
282  std::list<CTxMemPool> pools;
283 
284  // Note: in order to isolate how long eviction takes (as opposed to add +
285  // eviction), we are forced to pre-create all the pools we will be needing
286  // up front.
287 
288  bench.epochs(2).epochIterations(1);
289 
290  for (uint64_t i = 0; i < bench.epochs() * bench.epochIterations() + 1;
291  ++i) {
292  pools.emplace_back();
293  CTxMemPool &pool = pools.back();
294  TestMemPoolEntryHelper entry;
295  // Fill mempool
296  size_t txCount = 0;
297  entry.nFee = 1337 * SATOSHI;
298  // add in order of decreasing fee if revFee, increasing otherwise
299  const Amount feeBump =
300  revFee ? int64_t(-1) * SATOSHI : int64_t(1) * SATOSHI;
301  for (const auto &chain : chains) {
302  entry.spendsCoinbase = true;
303  if (revFee) {
304  entry.nFee += int64_t(chain.size()) * SATOSHI;
305  }
306  LOCK2(cs_main, pool.cs);
307  for (const auto &tx : chain) {
308  pool.addUnchecked(entry.FromTx(tx));
309  entry.nFee += feeBump;
310  // Setting spendCoinbase to false here assumes it's a chain of
311  // 1-in-1-out transaction chain.
312  entry.spendsCoinbase = false;
313  ++txCount;
314  }
315  if (revFee) {
316  entry.nFee += int64_t(chain.size()) * SATOSHI;
317  }
318  }
319  assert(pool.size() == txCount);
320  }
321 
322  auto it = pools.begin();
323 
324  bench.run([&] {
325  assert(it != pools.end());
326  auto &pool = *it++;
327  LOCK2(cs_main, pool.cs);
328  while (auto prevSize = pool.size()) {
329  pool.TrimToSize(pool.DynamicMemoryUsage() * 99 / 100);
330  assert(pool.size() < prevSize);
331  }
332  });
333 }
334 
337  RegTestingSetup test_setup{};
338  const Config &config = GetConfig();
339  const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
340  config, createUTXOs(config, 1, test_setup.m_node).back(), 50);
341  benchATMP(config, test_setup.m_node, bench, chainedTxs);
342 }
343 
346  RegTestingSetup test_setup{};
347  const Config &config = GetConfig();
348  const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
349  config, createUTXOs(config, 1, test_setup.m_node).back(), 500);
350  benchATMP(config, test_setup.m_node, bench, chainedTxs);
351 }
352 
355  RegTestingSetup test_setup{};
356  const Config &config = GetConfig();
357  const std::vector<CTransactionRef> chainedTxs =
358  twoInOneOutTree(config, test_setup.m_node, 5);
359  assert(chainedTxs.size() == 63);
360  benchATMP(config, test_setup.m_node, bench, chainedTxs);
361 }
362 
365  RegTestingSetup test_setup{};
366  const Config &config = GetConfig();
367  const std::vector<CTransactionRef> chainedTxs =
368  twoInOneOutTree(config, test_setup.m_node, 8);
369  assert(chainedTxs.size() == 511);
370  benchATMP(config, test_setup.m_node, bench, chainedTxs);
371 }
372 
376  RegTestingSetup test_setup{};
377  const Config &config = GetConfig();
378  benchReorg(config, test_setup.m_node, bench, 10, 50, true);
379 }
380 
384  RegTestingSetup test_setup{};
385  const Config &config = GetConfig();
386  benchReorg(config, test_setup.m_node, bench, 10, 500, true);
387 }
388 
393  RegTestingSetup test_setup{};
394  const Config &config = GetConfig();
395  benchReorg(config, test_setup.m_node, bench, 10, 50, false);
396 }
397 
402  RegTestingSetup test_setup{};
403  const Config &config = GetConfig();
404  benchReorg(config, test_setup.m_node, bench, 10, 500, false);
405 }
406 
409  RegTestingSetup test_setup{};
410  const Config &config = GetConfig();
411  const CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
412  benchGenerateNewBlock(config, test_setup.m_node, bench,
413  {oneInOneOutChain(config, std::move(utxo), 50)});
414 }
415 
418  RegTestingSetup test_setup{};
419  const Config &config = GetConfig();
420  const CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
421  benchGenerateNewBlock(config, test_setup.m_node, bench,
422  {oneInOneOutChain(config, std::move(utxo), 500)});
423 }
424 
427 static void EvictChained50Tx(benchmark::Bench &bench) {
428  RegTestingSetup test_setup{};
429  const Config &config = GetConfig();
430  // create 2000 chains of 50 1-in-1-out each
431  std::vector<std::vector<CTransactionRef>> chains;
432  constexpr int NChains = 2000;
433  const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
434  for (int i = 0; i < NChains; ++i) {
435  chains.push_back(oneInOneOutChain(config, utxos[i], 50));
436  }
437  benchEviction(config, bench, chains, false);
438 }
439 
443  RegTestingSetup test_setup{};
444  const Config &config = GetConfig();
445  // create 2000 chains of 50 1-in-1-out each
446  std::vector<std::vector<CTransactionRef>> chains;
447  constexpr int NChains = 2000;
448  const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
449  for (int i = 0; i < NChains; ++i) {
450  chains.push_back(oneInOneOutChain(config, utxos[i], 50));
451  }
452  benchEviction(config, bench, chains, true);
453 }
454 
459 
464 
467 
static constexpr Amount SATOSHI
Definition: amount.h:143
static constexpr Amount COIN
Definition: amount.h:144
RecursiveMutex cs_main
Global state.
Definition: validation.cpp:113
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:427
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:383
static void MempoolAcceptance50ChainedTxs(benchmark::Bench &bench)
Tests a chain of 50 1-input-1-output transactions.
Definition: chained_tx.cpp:336
static void MempoolAcceptance511TxTree(benchmark::Bench &bench)
Test a tree of 511 2-inputs-1-output transactions.
Definition: chained_tx.cpp:364
static void MempoolAcceptance63TxTree(benchmark::Bench &bench)
Test a tree of 63 2-inputs-1-output transactions.
Definition: chained_tx.cpp:354
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:442
static std::vector< CTxIn > createUTXOs(const Config &config, size_t n, node::NodeContext &node)
Mine new utxos.
Definition: chained_tx.cpp:37
static void benchATMP(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector< CTransactionRef > chainedTxs)
Run benchmark on AcceptToMemoryPool.
Definition: chained_tx.cpp:131
static void benchGenerateNewBlock(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef >> &chains)
Definition: chained_tx.cpp:242
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:401
static void MempoolAcceptance500ChainedTxs(benchmark::Bench &bench)
Tests a chain of 500 1-input-1-output transactions.
Definition: chained_tx.cpp:345
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 void GenerateBlock50ChainedTxs(benchmark::Bench &bench)
Generate a block with 50 1-input-1-output transactions.
Definition: chained_tx.cpp:408
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:375
static void benchEviction(const Config &, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef >> &chains, bool revFee=true)
Definition: chained_tx.cpp:279
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:158
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 REDEEM_SCRIPT
This file contains benchmarks focusing on chained transactions in the mempool.
Definition: chained_tx.cpp:27
static void GenerateBlock500ChainedTxs(benchmark::Bench &bench)
Generate a block with 500 1-input-1-output transactions.
Definition: chained_tx.cpp:417
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:392
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
#define Assert(val)
Identity function.
Definition: check.h:65
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:26
unsigned int nTx
Number of transactions in this block.
Definition: blockindex.h:61
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:157
A mutable version of CTransaction.
Definition: transaction.h:280
std::vector< CTxOut > vout
Definition: transaction.h:283
std::vector< CTxIn > vin
Definition: transaction.h:282
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:22
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:431
A reference to a CScript: the Hash160 of its serialization (see script.h)
Definition: standard.h:26
An input of a transaction.
Definition: transaction.h:61
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
Definition: txmempool.h:355
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition: txmempool.h:441
unsigned long size() const
Definition: txmempool.h:662
void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(void addUnchecked(const CTxMemPoolEntry &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:509
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:647
CTxMemPool * GetMempool()
Definition: validation.h:784
CChain m_chain
The current chain of blockheaders we consult and build on.
Definition: validation.h:745
bool InvalidateBlock(const Config &config, BlockValidationState &state, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main) 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(const Config &config, BlockValidationState &state, std::shared_ptr< const CBlock > pblock=nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) LOCKS_EXCLUDED(cs_main)
Find the best known block, and make it the tip of the block chain.
Definition: config.h:17
bool IsValid() const
Definition: validation.h:120
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 & epochIterations(uint64_t numIters) noexcept
Sets exactly the number of iterations for each epoch.
Bench & epochs(size_t numEpochs) noexcept
Controls number of epochs, the number of measurements to perform.
Generate a new block, without valid proof-of-work.
Definition: miner.h:48
std::unique_ptr< CBlockTemplate > CreateNewBlock(const CScript &scriptPubKeyIn)
Construct a new block template with coinbase to scriptPubKeyIn.
Definition: miner.cpp:122
const Config & GetConfig()
Definition: config.cpp:34
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule).
Definition: consensus.h:32
Definition: init.h:28
static CTransactionRef MakeTransactionRef()
Definition: transaction.h:322
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:321
std::vector< uint8_t > ToByteVector(const T &in)
Definition: script.h:42
@ OP_EQUAL
Definition: script.h:119
@ OP_HASH160
Definition: script.h:160
@ OP_TRUE
Definition: script.h:57
@ OP_DROP
Definition: script.h:97
Definition: amount.h:19
Validation result for a single transaction mempool acceptance.
Definition: validation.h:220
const ResultType m_result_type
Definition: validation.h:230
@ VALID
Fully validated, valid.
NodeContext struct containing references to chain state and connection state.
Definition: context.h:38
#define LOCK2(cs1, cs2)
Definition: sync.h:246
#define LOCK(cs)
Definition: sync.h:243
T GetTime()
Return system time (or mocked time, if set)
Definition: time.cpp:71
MempoolAcceptResult AcceptToMemoryPool(const Config &config, 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())