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