disconnected_transactions.cpp raw

   1  // Copyright (c) 2023 The Limenka 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, benchmark::PriorityLevel::HIGH);
 138  BENCHMARK(AddAndRemoveDisconnectedBlockTransactions90, benchmark::PriorityLevel::HIGH);
 139  BENCHMARK(AddAndRemoveDisconnectedBlockTransactions10, benchmark::PriorityLevel::HIGH);
 140