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