connectblock.cpp
1 // Copyright (c) 2025-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 <addresstype.h> 6 #include <bench/bench.h> 7 #include <interfaces/chain.h> 8 #include <kernel/cs_main.h> 9 #include <script/interpreter.h> 10 #include <sync.h> 11 #include <test/util/setup_common.h> 12 #include <validation.h> 13 14 #include <cassert> 15 #include <vector> 16 17 /* 18 * Creates a test block containing transactions with the following properties: 19 * - Each transaction has the same number of inputs and outputs 20 * - All Taproot inputs use simple key path spends (no script path spends) 21 * - All signatures use SIGHASH_ALL (default sighash) 22 * - Each transaction spends all outputs from the previous transaction 23 */ 24 CBlock CreateTestBlock( 25 TestChain100Setup& test_setup, 26 const std::vector<CKey>& keys, 27 const std::vector<CTxOut>& outputs, 28 int num_txs = 1000) 29 { 30 Chainstate& chainstate{test_setup.m_node.chainman->ActiveChainstate()}; 31 32 const WitnessV1Taproot coinbase_taproot{XOnlyPubKey(test_setup.coinbaseKey.GetPubKey())}; 33 34 // Create the outputs that will be spent in the first transaction of the test block 35 // Doing this in a separate block excludes the validation of its inputs from the benchmark 36 auto& coinbase_to_spend{test_setup.m_coinbase_txns[0]}; 37 const auto [first_tx, _]{test_setup.CreateValidTransaction( 38 {coinbase_to_spend}, 39 {COutPoint(coinbase_to_spend->GetHash(), 0)}, 40 chainstate.m_chain.Height() + 1, keys, outputs, {}, {})}; 41 const CScript coinbase_spk{GetScriptForDestination(coinbase_taproot)}; 42 test_setup.CreateAndProcessBlock({first_tx}, coinbase_spk, &chainstate); 43 44 std::vector<CMutableTransaction> txs; 45 txs.reserve(num_txs); 46 CTransactionRef tx_to_spend{MakeTransactionRef(first_tx)}; 47 for (int i{0}; i < num_txs; i++) { 48 std::vector<COutPoint> inputs; 49 inputs.reserve(outputs.size()); 50 51 for (size_t j{0}; j < outputs.size(); j++) { 52 inputs.emplace_back(tx_to_spend->GetHash(), j); 53 } 54 55 const auto [tx, _]{test_setup.CreateValidTransaction( 56 {tx_to_spend}, inputs, chainstate.m_chain.Height() + 1, keys, outputs, {}, {})}; 57 txs.emplace_back(tx); 58 tx_to_spend = MakeTransactionRef(tx); 59 } 60 61 // Coinbase output can use any output type as it is not spent and will not change the benchmark 62 return test_setup.CreateBlock(txs, coinbase_spk, chainstate); 63 } 64 65 /* 66 * Creates key pairs and corresponding outputs for the benchmark transactions. 67 * - For Schnorr signatures: Creates simple key path spendable outputs 68 * - For Ecdsa signatures: Creates P2WPKH (native SegWit v0) outputs 69 * - All outputs have value of 1 BTC 70 */ 71 std::pair<std::vector<CKey>, std::vector<CTxOut>> CreateKeysAndOutputs(const CKey& coinbaseKey, size_t num_schnorr, size_t num_ecdsa) 72 { 73 std::vector<CKey> keys{coinbaseKey}; 74 keys.reserve(num_schnorr + num_ecdsa + 1); 75 76 std::vector<CTxOut> outputs; 77 outputs.reserve(num_schnorr + num_ecdsa); 78 79 for (size_t i{0}; i < num_ecdsa; ++i) { 80 keys.emplace_back(GenerateRandomKey()); 81 outputs.emplace_back(COIN, GetScriptForDestination(WitnessV0KeyHash{keys.back().GetPubKey()})); 82 } 83 84 for (size_t i{0}; i < num_schnorr; ++i) { 85 keys.emplace_back(GenerateRandomKey()); 86 outputs.emplace_back(COIN, GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey(keys.back().GetPubKey())})); 87 } 88 89 return {keys, outputs}; 90 } 91 92 void BenchmarkConnectBlock(benchmark::Bench& bench, std::vector<CKey>& keys, std::vector<CTxOut>& outputs, TestChain100Setup& test_setup) 93 { 94 const auto& test_block{CreateTestBlock(test_setup, keys, outputs)}; 95 bench.unit("block").run([&] { 96 LOCK(cs_main); 97 auto& chainman{test_setup.m_node.chainman}; 98 auto& chainstate{chainman->ActiveChainstate()}; 99 BlockValidationState test_block_state; 100 auto* pindex{chainman->m_blockman.AddToBlockIndex(test_block, chainman->m_best_header)}; // Doing this here doesn't impact the benchmark 101 CCoinsViewCache viewNew{&chainstate.CoinsTip()}; 102 103 assert(chainstate.ConnectBlock(test_block, test_block_state, pindex, viewNew)); 104 }); 105 } 106 107 static void ConnectBlockAllSchnorr(benchmark::Bench& bench) 108 { 109 const auto test_setup{MakeNoLogFileContext<TestChain100Setup>()}; 110 auto [keys, outputs]{CreateKeysAndOutputs(test_setup->coinbaseKey, /*num_schnorr=*/5, /*num_ecdsa=*/0)}; 111 BenchmarkConnectBlock(bench, keys, outputs, *test_setup); 112 } 113 114 static void ConnectBlockMixedEcdsaSchnorr(benchmark::Bench& bench) 115 { 116 const auto test_setup{MakeNoLogFileContext<TestChain100Setup>()}; 117 // Blocks in range 848000 to 868000 have a roughly 20 to 80 ratio of schnorr to ecdsa inputs 118 auto [keys, outputs]{CreateKeysAndOutputs(test_setup->coinbaseKey, /*num_schnorr=*/1, /*num_ecdsa=*/4)}; 119 BenchmarkConnectBlock(bench, keys, outputs, *test_setup); 120 } 121 122 static void ConnectBlockAllEcdsa(benchmark::Bench& bench) 123 { 124 const auto test_setup{MakeNoLogFileContext<TestChain100Setup>()}; 125 auto [keys, outputs]{CreateKeysAndOutputs(test_setup->coinbaseKey, /*num_schnorr=*/0, /*num_ecdsa=*/5)}; 126 BenchmarkConnectBlock(bench, keys, outputs, *test_setup); 127 } 128 129 BENCHMARK(ConnectBlockAllSchnorr); 130 BENCHMARK(ConnectBlockMixedEcdsaSchnorr); 131 BENCHMARK(ConnectBlockAllEcdsa);