aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.bench.include1
-rw-r--r--src/bench/disconnected_transactions.cpp128
2 files changed, 129 insertions, 0 deletions
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index 934e9a1fae..28b779a5a8 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -28,6 +28,7 @@ bench_bench_bitcoin_SOURCES = \
bench/data.cpp \
bench/data.h \
bench/descriptors.cpp \
+ bench/disconnected_transactions.cpp \
bench/duplicate_inputs.cpp \
bench/ellswift.cpp \
bench/examples.cpp \
diff --git a/src/bench/disconnected_transactions.cpp b/src/bench/disconnected_transactions.cpp
new file mode 100644
index 0000000000..77c8690ce4
--- /dev/null
+++ b/src/bench/disconnected_transactions.cpp
@@ -0,0 +1,128 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <bench/bench.h>
+#include <test/util/random.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+
+constexpr size_t BLOCK_VTX_COUNT{4000};
+constexpr size_t BLOCK_VTX_COUNT_10PERCENT{400};
+
+using BlockTxns = decltype(CBlock::vtx);
+
+/** Reorg where 1 block is disconnected and 2 blocks are connected. */
+struct ReorgTxns {
+ /** Disconnected block. */
+ BlockTxns disconnected_txns;
+ /** First connected block. */
+ BlockTxns connected_txns_1;
+ /** Second connected block, new chain tip. Has no overlap with disconnected_txns. */
+ BlockTxns connected_txns_2;
+ /** Transactions shared between disconnected_txns and connected_txns_1. */
+ size_t num_shared;
+};
+
+static BlockTxns CreateRandomTransactions(size_t num_txns)
+{
+ // Ensure every transaction has a different txid by having each one spend the previous one.
+ static uint256 prevout_hash{uint256::ZERO};
+
+ BlockTxns txns;
+ txns.reserve(num_txns);
+ // Simplest spk for every tx
+ CScript spk = CScript() << OP_TRUE;
+ for (uint32_t i = 0; i < num_txns; ++i) {
+ CMutableTransaction tx;
+ tx.vin.emplace_back(CTxIn{COutPoint{prevout_hash, 0}});
+ tx.vout.emplace_back(CTxOut{CENT, spk});
+ auto ptx{MakeTransactionRef(tx)};
+ txns.emplace_back(ptx);
+ prevout_hash = ptx->GetHash();
+ }
+ return txns;
+}
+
+/** Creates blocks for a Reorg, each with BLOCK_VTX_COUNT transactions. Between the disconnected
+ * block and the first connected block, there will be num_not_shared transactions that are
+ * different, and all other transactions the exact same. The second connected block has all unique
+ * transactions. This is to simulate a reorg in which all but num_not_shared transactions are
+ * confirmed in the new chain. */
+static ReorgTxns CreateBlocks(size_t num_not_shared)
+{
+ auto num_shared{BLOCK_VTX_COUNT - num_not_shared};
+ const auto shared_txns{CreateRandomTransactions(/*num_txns=*/num_shared)};
+
+ // Create different sets of transactions...
+ auto disconnected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
+ std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(disconnected_block_txns));
+
+ auto connected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
+ std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(connected_block_txns));
+
+ assert(disconnected_block_txns.size() == BLOCK_VTX_COUNT);
+ assert(connected_block_txns.size() == BLOCK_VTX_COUNT);
+
+ return ReorgTxns{/*disconnected_txns=*/disconnected_block_txns,
+ /*connected_txns_1=*/connected_block_txns,
+ /*connected_txns_2=*/CreateRandomTransactions(BLOCK_VTX_COUNT),
+ /*num_shared=*/num_shared};
+}
+
+static void Reorg(const ReorgTxns& reorg)
+{
+ DisconnectedBlockTransactions disconnectpool;
+ // Disconnect block
+ disconnectpool.AddTransactionsFromBlock(reorg.disconnected_txns);
+
+ // Connect first block
+ disconnectpool.removeForBlock(reorg.connected_txns_1);
+ // Connect new tip
+ disconnectpool.removeForBlock(reorg.connected_txns_2);
+
+ // Sanity Check
+ assert(disconnectpool.queuedTx.size() == BLOCK_VTX_COUNT - reorg.num_shared);
+
+ disconnectpool.clear();
+}
+
+/** Add transactions from DisconnectedBlockTransactions, remove all but one (the disconnected
+ * block's coinbase transaction) of them, and then pop from the front until empty. This is a reorg
+ * in which all of the non-coinbase transactions in the disconnected chain also exist in the new
+ * chain. */
+static void AddAndRemoveDisconnectedBlockTransactionsAll(benchmark::Bench& bench)
+{
+ const auto chains{CreateBlocks(/*num_not_shared=*/1)};
+ assert(chains.num_shared == BLOCK_VTX_COUNT - 1);
+
+ bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
+ Reorg(chains);
+ });
+}
+
+/** Add transactions from DisconnectedBlockTransactions, remove 90% of them, and then pop from the front until empty. */
+static void AddAndRemoveDisconnectedBlockTransactions90(benchmark::Bench& bench)
+{
+ const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT_10PERCENT)};
+ assert(chains.num_shared == BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT);
+
+ bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
+ Reorg(chains);
+ });
+}
+
+/** Add transactions from DisconnectedBlockTransactions, remove 10% of them, and then pop from the front until empty. */
+static void AddAndRemoveDisconnectedBlockTransactions10(benchmark::Bench& bench)
+{
+ const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT)};
+ assert(chains.num_shared == BLOCK_VTX_COUNT_10PERCENT);
+
+ bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
+ Reorg(chains);
+ });
+}
+
+BENCHMARK(AddAndRemoveDisconnectedBlockTransactionsAll, benchmark::PriorityLevel::HIGH);
+BENCHMARK(AddAndRemoveDisconnectedBlockTransactions90, benchmark::PriorityLevel::HIGH);
+BENCHMARK(AddAndRemoveDisconnectedBlockTransactions10, benchmark::PriorityLevel::HIGH);