/ src / bench / disconnected_transactions.cpp
disconnected_transactions.cpp
  1  // Copyright (c) 2023-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 <kernel/disconnected_transactions.h>
  7  #include <primitives/block.h>
  8  #include <primitives/transaction.h>
  9  #include <script/script.h>
 10  #include <test/util/setup_common.h>
 11  
 12  #include <algorithm>
 13  #include <cassert>
 14  #include <cstddef>
 15  #include <cstdint>
 16  #include <iterator>
 17  #include <memory>
 18  #include <vector>
 19  
 20  constexpr size_t BLOCK_VTX_COUNT{4000};
 21  constexpr size_t BLOCK_VTX_COUNT_10PERCENT{400};
 22  
 23  using BlockTxns = decltype(CBlock::vtx);
 24  
 25  /** Reorg where 1 block is disconnected and 2 blocks are connected. */
 26  struct ReorgTxns {
 27      /** Disconnected block. */
 28      BlockTxns disconnected_txns;
 29      /** First connected block. */
 30      BlockTxns connected_txns_1;
 31      /** Second connected block, new chain tip. Has no overlap with disconnected_txns. */
 32      BlockTxns connected_txns_2;
 33      /** Transactions shared between disconnected_txns and connected_txns_1. */
 34      size_t num_shared;
 35  };
 36  
 37  static BlockTxns CreateRandomTransactions(size_t num_txns)
 38  {
 39      // Ensure every transaction has a different txid by having each one spend the previous one.
 40      static Txid prevout_hash{};
 41  
 42      BlockTxns txns;
 43      txns.reserve(num_txns);
 44      // Simplest spk for every tx
 45      CScript spk = CScript() << OP_TRUE;
 46      for (uint32_t i = 0; i < num_txns; ++i) {
 47          CMutableTransaction tx;
 48          tx.vin.emplace_back(COutPoint{prevout_hash, 0});
 49          tx.vout.emplace_back(CENT, spk);
 50          auto ptx{MakeTransactionRef(tx)};
 51          txns.emplace_back(ptx);
 52          prevout_hash = ptx->GetHash();
 53      }
 54      return txns;
 55  }
 56  
 57  /** Creates blocks for a Reorg, each with BLOCK_VTX_COUNT transactions. Between the disconnected
 58   * block and the first connected block, there will be num_not_shared transactions that are
 59   * different, and all other transactions the exact same. The second connected block has all unique
 60   * transactions. This is to simulate a reorg in which all but num_not_shared transactions are
 61   * confirmed in the new chain. */
 62  static ReorgTxns CreateBlocks(size_t num_not_shared)
 63  {
 64      auto num_shared{BLOCK_VTX_COUNT - num_not_shared};
 65      const auto shared_txns{CreateRandomTransactions(/*num_txns=*/num_shared)};
 66  
 67      // Create different sets of transactions...
 68      auto disconnected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
 69      std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(disconnected_block_txns));
 70  
 71      auto connected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
 72      std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(connected_block_txns));
 73  
 74      assert(disconnected_block_txns.size() == BLOCK_VTX_COUNT);
 75      assert(connected_block_txns.size() == BLOCK_VTX_COUNT);
 76  
 77      return ReorgTxns{/*disconnected_txns=*/disconnected_block_txns,
 78                       /*connected_txns_1=*/connected_block_txns,
 79                       /*connected_txns_2=*/CreateRandomTransactions(BLOCK_VTX_COUNT),
 80                       /*num_shared=*/num_shared};
 81  }
 82  
 83  static void Reorg(const ReorgTxns& reorg)
 84  {
 85      DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES};
 86      // Disconnect block
 87      const auto evicted = disconnectpool.AddTransactionsFromBlock(reorg.disconnected_txns);
 88      assert(evicted.empty());
 89  
 90      // Connect first block
 91      disconnectpool.removeForBlock(reorg.connected_txns_1);
 92      // Connect new tip
 93      disconnectpool.removeForBlock(reorg.connected_txns_2);
 94  
 95      // Sanity Check
 96      assert(disconnectpool.size() == BLOCK_VTX_COUNT - reorg.num_shared);
 97  
 98      disconnectpool.clear();
 99  }
100  
101  /** Add transactions from DisconnectedBlockTransactions, remove all but one (the disconnected
102   * block's coinbase transaction) of them, and then pop from the front until empty. This is a reorg
103   * in which all of the non-coinbase transactions in the disconnected chain also exist in the new
104   * chain. */
105  static void AddAndRemoveDisconnectedBlockTransactionsAll(benchmark::Bench& bench)
106  {
107      const auto chains{CreateBlocks(/*num_not_shared=*/1)};
108      assert(chains.num_shared == BLOCK_VTX_COUNT - 1);
109  
110      bench.minEpochIterations(10).run([&]() {
111          Reorg(chains);
112      });
113  }
114  
115  /** Add transactions from DisconnectedBlockTransactions, remove 90% of them, and then pop from the front until empty. */
116  static void AddAndRemoveDisconnectedBlockTransactions90(benchmark::Bench& bench)
117  {
118      const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT_10PERCENT)};
119      assert(chains.num_shared == BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT);
120  
121      bench.minEpochIterations(10).run([&]() {
122          Reorg(chains);
123      });
124  }
125  
126  /** Add transactions from DisconnectedBlockTransactions, remove 10% of them, and then pop from the front until empty. */
127  static void AddAndRemoveDisconnectedBlockTransactions10(benchmark::Bench& bench)
128  {
129      const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT)};
130      assert(chains.num_shared == BLOCK_VTX_COUNT_10PERCENT);
131  
132      bench.minEpochIterations(10).run([&]() {
133          Reorg(chains);
134      });
135  }
136  
137  BENCHMARK(AddAndRemoveDisconnectedBlockTransactionsAll);
138  BENCHMARK(AddAndRemoveDisconnectedBlockTransactions90);
139  BENCHMARK(AddAndRemoveDisconnectedBlockTransactions10);