bench: replace embedded raw block with configurable block generator #32554

pull l0rinc wants to merge 4 commits into bitcoin:master from l0rinc:l0rinc/bench-block-generator changing 11 files +458 −60
  1. l0rinc commented at 9:39 pm on May 18, 2025: contributor

    Problem

    The hardcoded benchmark block 413567 has several issues:

    • embeds 1.1MB of binary data in the repository (we can’t remove it from history, but we can, from HEAD)
    • mined in 2016, predates SegWit v0, Taproot, and modern script types
    • benchmarks cannot test different transaction patterns or script mixes
    • updating to a newer block (e.g., #32457) would still add binary bloat and would remain inflexible

    Fix

    Added configurable bench/block_generator.{h,cpp} that builds fully valid blocks at runtime providing blocks or raw serialized stream.

    Reproducer

    You can run all affected tests and benchmarks with a sanity check:

    0rm -rf build && \
    1cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCH=ON && \
    2cmake --build build -j$(nproc) && \
    3build/bin/test_bitcoin -t block_generator_deterministic_seeded_output,block_generator_serialization_roundtrip,block_generator_seed_perturbation,block_generator_multiple_seed_sanity && \
    4build/bin/bench_bitcoin -sanity-check -filter='DeserializeBlockTest|DeserializeAndCheckBlockTest|LoadExternalBlockFile|WriteBlockBench|ReadBlockBench|ReadRawBlockBench|BlockToJsonVerbosity1|BlockToJsonVerbosity2|BlockToJsonVerbosity3|BlockToJsonVerboseWrite|HexStrBench'
    

    This supersedes #32457 by eliminating the need for any hardcoded block data while enabling flexible benchmark scenarios.

  2. DrahtBot commented at 9:39 pm on May 18, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32554.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK laanwj, yuvicc, Raimo33, hodlinator

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34654 ([RFC] BlockMap and CChain Concurrency Improvement by alexanderwiederin)
    • #34448 (ci, iwyu: Fix warnings in src/util and treat them as errors by hebasto)
    • #34208 (bench: add fluent API for untimed setup steps in nanobench by l0rinc)
    • #34075 (fees: Introduce Mempool Based Fee Estimation to reduce overestimation by ismaelsadeeq)
    • #32729 (test,refactor: extract script template helpers & widen sigop count coverage by l0rinc)
    • #32427 ((RFC) kernel: Replace leveldb-based BlockTreeDB with flat-file based store by sedited)
    • #31682 ([IBD] specialize CheckBlock’s input & coinbase checks by l0rinc)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. laanwj commented at 9:18 am on May 19, 2025: member
    Concept ACK
  4. l0rinc marked this as ready for review on May 21, 2025
  5. l0rinc renamed this:
    RFC: bench: replace embedded raw block with configurable block generator
    bench: replace embedded raw block with configurable block generator
    on May 21, 2025
  6. DrahtBot added the label Tests on May 21, 2025
  7. yuvicc commented at 3:44 pm on June 29, 2025: contributor

    Concept ACK

    This one is pretty useful as I can test with up-to date test_data.

  8. Raimo33 commented at 2:37 pm on October 19, 2025: contributor
    Concept ACK for more realistic benchmarks
  9. l0rinc force-pushed on Dec 20, 2025
  10. l0rinc commented at 11:19 pm on December 20, 2025: contributor
    Rebased and applied the new block everywhere, removing the binary of block 413567 embedded in the benchmarks. Let me know what you think.
  11. in src/bench/block_generator.h:74 in 0fbbefd017 outdated
    69+DataStream GetBlockData(
    70+    const CChainParams& chain_params = *CChainParams::RegTest(CChainParams::RegTestOptions{}),
    71+    const ScriptRecipe& = kWitness,
    72+    const uint256& seed = {}
    73+);
    74+CBlock GetBlock(
    


    hodlinator commented at 8:55 pm on December 22, 2025:

    Naming should probably indicate work/computation:

    0DataStream GenerateBlockData(
    1    const CChainParams& chain_params = *CChainParams::RegTest(CChainParams::RegTestOptions{}),
    2    const ScriptRecipe& = kWitness,
    3    const uint256& seed = {}
    4);
    5CBlock GenerateBlock(
    

    l0rinc commented at 1:06 pm on February 16, 2026:
    Done, extracted this to a separate commit where we’re wrapping the previous behavior first to separate the introduction of the generator from applying it.
  12. in src/bench/load_external.cpp:38 in 0fbbefd017 outdated
    34@@ -35,17 +35,18 @@
    35  */
    36 static void LoadExternalBlockFile(benchmark::Bench& bench)
    37 {
    38-    const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN)};
    39+    const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(ChainType::REGTEST)};
    


    hodlinator commented at 9:01 pm on December 22, 2025:

    My guess is that we are changing networks in order to get a powLimit which allows for trivially finding a valid nonce. Could the commit message spell this out?

    Could also replace this assert… https://github.com/bitcoin/bitcoin/blob/0fbbefd017a817bc32b5eb4dbb7647c668c8a61f/src/bench/block_generator.cpp#L76-L78

    …with a more direct assert further down, closer to where it’s important:

    0    block.hashPrevBlock = params.GenesisBlock().GetHash();
    1    assert(params.GetConsensus().powLimit == uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}); // lowest difficulty
    2    block.nBits = UintToArith256(params.GetConsensus().powLimit).GetCompact();
    

    l0rinc commented at 1:21 pm on February 16, 2026:

    Yeah, that’s why I switched to REGTEST. GenerateBlock* does nonce search in setup, and we don’t want to do that on MAIN difficulty - updated the code and commit message to spell that out.

    I have played with your suggestions but it seemed to me the assert(chain_params.IsTestChain()) in BuildBlock should make that clear - and it’s not the place to validate GetConsensus().powLimit values here.

  13. in src/bench/block_generator.cpp:142 in 0fbbefd017 outdated
    137+    }
    138+
    139+    block.nVersion = 1 + rng.randrange<int>(VERSIONBITS_LAST_OLD_BLOCK_VERSION);
    140+    block.nTime = params.GenesisBlock().nTime;
    141+    block.hashPrevBlock.SetNull();
    142+    block.nBits = UintToArith256(params.GetConsensus().powLimit).GetCompact(); // lowest difficulty
    


    hodlinator commented at 9:21 pm on December 22, 2025:

    Maybe have genesis block as previous block to be slightly more regular, instead of being a second genesis block (due to null hashPrevBlock)?

    0    block.nTime = params.GenesisBlock().nTime + 10 * 60;
    1    block.hashPrevBlock = params.GenesisBlock().GetHash();
    2    block.nBits = params.GenesisBlock().nBits;
    

    Being the genesis block also presents a mystery as non-coinbase transactions shouldn’t be possible in genesis as outputs cannot be spent for 100 blocks. (I guess making us the second block doesn’t help too much in that respect as the first 100 blocks should only have a coinbase transaction).


    l0rinc commented at 1:22 pm on February 16, 2026:
    Added genesis as hashPrevBlock, thanks.
  14. in src/bench/strencodings.cpp:17 in 0fbbefd017 outdated
    15 static void HexStrBench(benchmark::Bench& bench)
    16 {
    17-    auto const& data = benchmark::data::block413567;
    18-    bench.batch(data.size()).unit("byte").run([&] {
    19+    FastRandomContext rng{/*fDeterministic=*/true};
    20+    auto data{rng.randbytes<uint8_t>(CPubKey::COMPRESSED_SIZE)};
    


    hodlinator commented at 9:30 pm on December 22, 2025:

    nit: Why not go for something in the same order of magnitude in size as on master, and use more modern std::bytes?

    0    auto data{rng.randbytes(MAX_BLOCK_WEIGHT)};
    

    l0rinc commented at 12:00 pm on February 16, 2026:
    Good idea, applied those and split it into a separate PR, and added you as co-author in #34598
  15. in src/bench/readwriteblock.cpp:41 in 0fbbefd017 outdated
    50-    const auto& test_block{CreateTestBlock()};
    51+    const auto& params{testing_setup->m_node.chainman->GetParams()};
    52+    const auto test_block{benchmark::GetBlock(params)};
    53     const auto& expected_hash{test_block.GetHash()};
    54-    const auto& pos{blockman.WriteBlock(test_block, 413'567)};
    55+    const auto& pos{blockman.WriteBlock(test_block, /*nHeight=*/1)};
    


    hodlinator commented at 9:35 pm on December 22, 2025:

    I think height should technically be 0 if we don’t have a prev block - then again, we have a bunch of random non-coinbase transactions, which should disqualify us from being a genesis block.

    1 would be correct if our prev is genesis though, see my other comment.


    l0rinc commented at 1:23 pm on February 16, 2026:
    we have a generis now so kept the 1 - thanks!
  16. in src/bench/block_generator.cpp:48 in 0fbbefd017 outdated
    43+auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
    44+{
    45+    std::array<std::pair<double, std::function<CScript()>>, 11> table{
    46+        std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},
    47+        std::pair{rec.multisig_prob, [&] {
    48+            const size_t keys_count{1 + rng.randrange<size_t>(15)};
    


    hodlinator commented at 9:44 pm on December 22, 2025:
    0            const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
    
  17. in src/bench/block_generator.cpp:55 in 0fbbefd017 outdated
    50+            std::vector<CPubKey> keys;
    51+            keys.reserve(keys_count);
    52+            for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
    53+            return GetScriptForMultisig(required, keys);
    54+        }},
    55+        {rec.null_data_prob, [&] {
    


    hodlinator commented at 9:49 pm on December 22, 2025:

    Not sure what’s going on here regarding the sometimes need of explicit type, but might as well stay consistent:

    0        std::pair{rec.null_data_prob, [&] {
    
  18. in src/bench/block_generator.cpp:43 in 0fbbefd017 outdated
    38+    auto pubkey{rng.randbytes<CPubKey::COMPRESSED_SIZE, unsigned char>()};
    39+    pubkey[0] = rng.randbool() ? 0x02 : 0x03;
    40+    return CPubKey(pubkey.begin(), pubkey.end());
    41+}
    42+
    43+auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
    


    hodlinator commented at 9:51 pm on December 22, 2025:

    Developer notes encourage:

    0auto CreateScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
    

    hodlinator commented at 9:54 pm on December 22, 2025:

    nit: Would prefer only mentioning namespace once at the top:

    0namespace benchmark {
    1namespace {
    2...
    
    0auto createScriptFactory(FastRandomContext& rng, const ScriptRecipe& rec)
    
  19. in src/bench/block_generator.cpp:38 in 0fbbefd017 outdated
    33+    return n;
    34+}
    35+
    36+CPubKey RandPub(FastRandomContext& rng)
    37+{
    38+    auto pubkey{rng.randbytes<CPubKey::COMPRESSED_SIZE, unsigned char>()};
    


    hodlinator commented at 9:56 pm on December 22, 2025:
    Could be uncompressed in some cases: https://learnmeabitcoin.com/technical/script/p2pk/

    l0rinc commented at 1:35 pm on February 16, 2026:
    thanks, added it with 50% probability (and changed the magic numbers to use SECP256K1_TAG_PUBKEY_UNCOMPRESSED/SECP256K1_TAG_PUBKEY_EVEN/SECP256K1_TAG_PUBKEY_ODD constants instead. This required linking secp256k1 to bench_bitcoin)
  20. in src/bench/block_generator.cpp:79 in 0fbbefd017 outdated
    74+}
    75+
    76+CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)
    77+{
    78+    assert(params.IsTestChain());
    79+    FastRandomContext rng{seed};
    


    hodlinator commented at 10:13 pm on December 22, 2025:
    Defaulting to initializing with a zero seed makes it not only deterministic, but even equal between process executions on different machines. If this design is intentional the rationale should be documented.
  21. in src/bench/block_generator.cpp:93 in 0fbbefd017 outdated
    88+    {
    89+        CMutableTransaction cb;
    90+        cb.vin = {CTxIn(COutPoint())};
    91+        cb.vin[0].scriptSig = CScript() << CScriptNum(rng.randrange(1'000'000)) << OP_0;
    92+        cb.vout = {CTxOut(rng.randrange(50 * COIN), CScript() << OP_TRUE)};
    93+        block.vtx.push_back(MakeTransactionRef(std::move(cb)));
    


    hodlinator commented at 10:18 pm on December 22, 2025:

    https://learnmeabitcoin.com/technical/mining/coinbase-transaction/#requirements

    BIP 141: If the block contains segwit transactions, the witness field must contain a 32-byte witness reserved value.

    BIP 141: If the block contains segwit transactions, one of the outputs must contain the wTXID commitment.


    l0rinc commented at 2:31 pm on February 16, 2026:
    I have added https://github.com/bitcoin/bitcoin/blob/37cc2a2d953c072a236b657bfd7de5167092a47a/src/validation.cpp#L4076 which runs CheckWitnessMalleation validation here, thanks for the comment
  22. in src/bench/block_generator.cpp:96 in 0fbbefd017 outdated
    91+        cb.vin[0].scriptSig = CScript() << CScriptNum(rng.randrange(1'000'000)) << OP_0;
    92+        cb.vout = {CTxOut(rng.randrange(50 * COIN), CScript() << OP_TRUE)};
    93+        block.vtx.push_back(MakeTransactionRef(std::move(cb)));
    94+    }
    95+
    96+    auto scriptFactory{createScriptFactory(rng, rec)};
    


    hodlinator commented at 10:20 pm on December 22, 2025:
    0    auto script_factory{createScriptFactory(rng, rec)};
    
  23. in src/bench/block_generator.cpp:82 in 0fbbefd017 outdated
    77+{
    78+    assert(params.IsTestChain());
    79+    FastRandomContext rng{seed};
    80+
    81+    assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
    82+    const auto tx_count{rec.tx_count ? rec.tx_count : 1000 + rng.randrange(2000)};
    


    hodlinator commented at 8:01 pm on December 23, 2025:

    When experimenting with changing the scripts I hit block size limits with just 2000 transactions due to large scripts.

    It seems more precise to have rec.tx_occupancy_limit between 0.0-1.0 and just fill up the block with transactions until the serialized size exceeds that fraction of the maximum size. Then pop off the last added transaction to make sure we keep under the limit.

    We could also insert an OP_RETURN output to a random tx to make it reach exactly the limit.

      0diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
      1index 2782b8d129..da7741f328 100644
      2--- a/src/bench/block_generator.cpp
      3+++ b/src/bench/block_generator.cpp
      4@@ -79,10 +79,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
      5     FastRandomContext rng{seed};
      6 
      7     assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
      8-    const auto tx_count{rec.tx_count ? rec.tx_count : 1000 + rng.randrange(2000)};
      9+    const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
     10 
     11     CBlock block{};
     12-    block.vtx.reserve(1 + tx_count);
     13+    block.vtx.reserve(1 + (4000 * tx_occupancy_limit));
     14 
     15     // coinbase
     16     {
     17@@ -95,7 +95,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
     18 
     19     auto scriptFactory{createScriptFactory(rng, rec)};
     20     auto rand_script{[&] {
     21-        double probability{rng.rand64() * (1.0 / std::numeric_limits<uint64_t>::max())};
     22+        double probability{MakeUnitDouble(rng.rand64())};
     23         for (const auto& [p, factory] : scriptFactory) {
     24             if (probability < p) return factory();
     25             probability -= p;
     26@@ -104,7 +104,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
     27         return CScript(raw.begin(), raw.end());
     28     }};
     29 
     30-    for (size_t i{0}; i < tx_count; ++i) {
     31+    // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
     32+    uint64_t block_size_no_witness{::GetSerializeSize(TX_NO_WITNESS(block)) + 2};
     33+    uint64_t block_size_with_witness{::GetSerializeSize(TX_WITH_WITNESS(block)) + 2};
     34+    const uint64_t block_size_limit(tx_occupancy_limit * MAX_BLOCK_WEIGHT);
     35+    while (block_size_no_witness * WITNESS_SCALE_FACTOR < block_size_limit
     36+           && block_size_no_witness * WITNESS_SCALE_FACTOR + (block_size_with_witness - block_size_no_witness) < block_size_limit) {
     37         CMutableTransaction tx;
     38         tx.version = 1 + rng.randrange<int>(3);
     39         tx.nLockTime = (rng.randrange<uint8_t>(100) < 90) ? 0 : rng.rand32();
     40@@ -133,8 +138,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
     41             tx_out.scriptPubKey = rand_script();
     42         }
     43 
     44+        block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
     45+        block_size_with_witness += ::GetSerializeSize(TX_WITH_WITNESS(tx));
     46         block.vtx.push_back(MakeTransactionRef(std::move(tx)));
     47     }
     48+    // Remove the transaction that had us exceed the limit.
     49+    block.vtx.pop_back();
     50 
     51     block.nVersion = 1 + rng.randrange<int>(VERSIONBITS_LAST_OLD_BLOCK_VERSION);
     52     block.nTime = params.GenesisBlock().nTime;
     53diff --git a/src/bench/block_generator.h b/src/bench/block_generator.h
     54index 4560005d53..4ce71f9c24 100644
     55--- a/src/bench/block_generator.h
     56+++ b/src/bench/block_generator.h
     57@@ -28,7 +28,7 @@ struct ScriptRecipe
     58     double nonstandard_prob{0};
     59 
     60     //! tuning
     61-    size_t tx_count{0};
     62+    double tx_occupancy_limit{0}; // 0 means random, 1.0 is a full block.
     63     double geometric_base_prob{0.5};
     64 };
     65 
     66@@ -46,7 +46,7 @@ inline constexpr ScriptRecipe kLegacy{
     67     .witness_unknown_prob = 0.00,
     68     .nonstandard_prob = 0.005,
     69 
     70-    .tx_count = 1500,
     71+    .tx_occupancy_limit = 0.8,
     72 };
     73 
     74 /* witness-heavy mix (roughly Taproot-era main chain) */
     75@@ -63,7 +63,7 @@ inline constexpr ScriptRecipe kWitness{
     76     .witness_unknown_prob = 0.02,
     77     .nonstandard_prob = 0.02,
     78 
     79-    .tx_count = 2000,
     80+    .tx_occupancy_limit = 1.0,
     81 };
     82 
     83 DataStream GetBlockData(
     84diff --git a/src/random.cpp b/src/random.cpp
     85index aeaf6baa8e..1908d45644 100644
     86--- a/src/random.cpp
     87+++ b/src/random.cpp
     88@@ -701,16 +701,22 @@ void RandomInit()
     89     ReportHardwareRand();
     90 }
     91 
     92+double MakeUnitDouble(uint64_t uniform) noexcept
     93+{
     94+    // Convert uniform into a uniformly-distributed double in range [0, 1), use the expression
     95+    // ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under
     96+    // "Generating uniform doubles in the unit interval". Call this value x.
     97+    return (uniform >> 11) * -0x1.0p-53;
     98+}
     99+
    100 double MakeExponentiallyDistributed(uint64_t uniform) noexcept
    101 {
    102     // To convert uniform into an exponentially-distributed double, we use two steps:
    103-    // - Convert uniform into a uniformly-distributed double in range [0, 1), use the expression
    104-    //   ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under
    105-    //   "Generating uniform doubles in the unit interval". Call this value x.
    106+    // - MakeUnitDouble()
    107     // - Given an x in uniformly distributed in [0, 1), we find an exponentially distributed value
    108     //   by applying the quantile function to it. For the exponential distribution with mean 1 this
    109     //   is F(x) = -log(1 - x).
    110     //
    111     // Combining the two, and using log1p(x) = log(1 + x), we obtain the following:
    112-    return -std::log1p((uniform >> 11) * -0x1.0p-53);
    113+    return -std::log1p(MakeUnitDouble(uniform));
    114 }
    115diff --git a/src/random.h b/src/random.h
    116index f0c0dcee81..ac2c5e924e 100644
    117--- a/src/random.h
    118+++ b/src/random.h
    119@@ -158,6 +158,9 @@ concept StdChronoDuration = requires {
    120         std::type_identity<T>());
    121 };
    122 
    123+/** Given a uniformly random uint64_t, return a uniformly-distributed double in range [0, 1). */
    124+double MakeUnitDouble(uint64_t uniform) noexcept;
    125+
    126 /** Given a uniformly random uint64_t, return an exponentially distributed double with mean 1. */
    127 double MakeExponentiallyDistributed(uint64_t uniform) noexcept;
    128 
    

    l0rinc commented at 2:35 pm on February 16, 2026:

    It seems more precise to have rec.tx_occupancy_limit between 0.0-1.0

    The occupancy limit would be a cool solution to make sure this always generates a valid block, but it’s not a real property of the blocks. I have also simplified generation a bit more so that a single seed generates all values now.

    I would expect users to create a block by setting exact values or just a seed and experiment until they get one that fits their needs - which should deterministically work after that. For example find heavier scripts to temporarily stress their optimizations or set the values manually.

    When experimenting with changing the scripts I hit block size limits with just 2000 transactions due to large scripts.

    I have added a block_generator_multiple_seed_sanity to help us discover these invalid setups anyway.

  24. DrahtBot added the label Needs rebase on Feb 7, 2026
  25. l0rinc force-pushed on Feb 7, 2026
  26. DrahtBot removed the label Needs rebase on Feb 7, 2026
  27. in src/bench/block_generator.cpp:149 in b5b57cdf39
    144+    block.hashMerkleRoot = BlockMerkleRoot(block);
    145+    while (!CheckProofOfWork(block.GetHash(), block.nBits, params.GetConsensus())) {
    146+        ++block.nNonce;
    147+    }
    148+
    149+    // Make sure we've generated a valid block
    


    hodlinator commented at 8:55 pm on February 9, 2026:

    Could add caveat:

    0    // Make sure we've generated a valid block (barring checks requiring context of other blocks)
    

    l0rinc commented at 2:35 pm on February 16, 2026:
    Reworded it, thanks
  28. in src/bench/block_generator.cpp:56 in b5b57cdf39
    51+            keys.reserve(keys_count);
    52+            for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
    53+            return GetScriptForMultisig(required, keys);
    54+        }},
    55+        {rec.null_data_prob, [&] {
    56+            const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
    


    hodlinator commented at 10:02 pm on February 9, 2026:
    Might want to make OP_RETURNs very large sometimes, probably using non-linear randomness?

    l0rinc commented at 3:45 pm on February 16, 2026:

    With a fixed tx_count, even a few rare “huge” OP_RETURNs can push the block overweight and make the generator fail deterministically for the default seed. But I’ve bumped it a tiny bit and added better comment:

    0const auto len{1 + rng.randrange<size_t>(100)}; // can exceed pre-v30 OP_RETURN 83-byte policy limits
    
  29. in src/bench/block_generator.cpp:153 in b5b57cdf39
    148+
    149+    // Make sure we've generated a valid block
    150+    {
    151+        BlockValidationState validationState;
    152+        const bool checked{CheckBlock(block, validationState, params.GetConsensus())};
    153+        assert(checked);
    


    hodlinator commented at 10:00 am on February 10, 2026:

    nit: Naming:

    0        const bool valid{CheckBlock(block, validationState, params.GetConsensus())};
    1        assert(valid);
    
  30. in src/bench/block_generator.h:31 in b5b57cdf39
    26+    double witness_v0_scripthash_prob{0};
    27+    double witness_unknown_prob{0};
    28+    double nonstandard_prob{0};
    29+
    30+    //! tuning
    31+    size_t tx_count{0};
    


    hodlinator commented at 10:44 am on February 10, 2026:

    nit: Could document field (unless the approach is switched to min_tx_occupancy, see other comment):

    0    size_t tx_count{0}; //!< Does not include coinbase tx. Leave at 0 to pick a random, valid number.
    
  31. in src/bench/block_generator.cpp:104 in b5b57cdf39
     99+        for (const auto& [p, factory] : scriptFactory) {
    100+            if (probability < p) return factory();
    101+            probability -= p;
    102+        }
    103+        const auto raw{rng.randbytes<uint8_t>(1 + rng.randrange(100))};
    104+        return CScript(raw.begin(), raw.end());
    


    hodlinator commented at 11:37 am on February 11, 2026:

    I think it would be better to require that ScriptRecipe probabilities add up to ~1.0 and not generate random scripts as a fallback.

    If we want to still generate random scripts it should be it’s own ScriptRecipe::random_prob field IMO.

     0diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
     1index 1bedfb5f9d..1891400d20 100644
     2--- a/src/bench/block_generator.cpp
     3+++ b/src/bench/block_generator.cpp
     4@@ -68,7 +68,9 @@ auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe&
     5 
     6     double sum{};
     7     for (const auto& p : table | std::views::keys) sum += p;
     8+    // Verify that probabilities add up to ~1.0.
     9     assert(sum <= 1);
    10+    assert(sum + 0.01 > 1.0);
    11 
    12     return table;
    13 }
    14@@ -100,8 +102,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
    15             if (probability < p) return factory();
    16             probability -= p;
    17         }
    18-        const auto raw{rng.randbytes<uint8_t>(1 + rng.randrange(100))};
    19-        return CScript(raw.begin(), raw.end());
    20+        return scriptFactory.back().second();
    21     }};
    22 
    23     // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
    24diff --git a/src/bench/block_generator.h b/src/bench/block_generator.h
    25index 4ce71f9c24..9ce7ea5ad8 100644
    26--- a/src/bench/block_generator.h
    27+++ b/src/bench/block_generator.h
    28@@ -35,16 +35,16 @@ struct ScriptRecipe
    29 /* purely legacy outputs, useful for pre-SegWit baselines */
    30 inline constexpr ScriptRecipe kLegacy{
    31     .anchor_prob = 0.00,
    32-    .multisig_prob = 0.05,
    33-    .null_data_prob = 0.005,
    34-    .pubkey_prob = 0.10,
    35-    .pubkeyhash_prob = 0.20,
    36-    .scripthash_prob = 0.10,
    37-    .witness_v1_taproot_prob = 0.00,
    38-    .witness_v0_keyhash_prob = 0.00,
    39-    .witness_v0_scripthash_prob = 0.00,
    40-    .witness_unknown_prob = 0.00,
    41-    .nonstandard_prob = 0.005,
    42+    .multisig_prob = 0.10,
    43+    .null_data_prob = 0.01,
    44+    .pubkey_prob = 0.20,
    45+    .pubkeyhash_prob = 0.43,
    46+    .scripthash_prob = 0.23,
    47+    .witness_v1_taproot_prob = 0,
    48+    .witness_v0_keyhash_prob = 0,
    49+    .witness_v0_scripthash_prob = 0,
    50+    .witness_unknown_prob = 0,
    51+    .nonstandard_prob = 0.025,
    52 
    53     .tx_occupancy_limit = 0.8,
    54 };
    55@@ -53,13 +53,13 @@ inline constexpr ScriptRecipe kLegacy{
    56 inline constexpr ScriptRecipe kWitness{
    57     .anchor_prob = 0.001,
    58     .multisig_prob = 0.005,
    59-    .null_data_prob = 0.002,
    60+    .null_data_prob = 0.01,
    61     .pubkey_prob = 0.001,
    62     .pubkeyhash_prob = 0.02,
    63     .scripthash_prob = 0.01,
    64-    .witness_v1_taproot_prob = 0.20,
    65-    .witness_v0_keyhash_prob = 0.25,
    66-    .witness_v0_scripthash_prob = 0.05,
    67+    .witness_v1_taproot_prob = 0.35,
    68+    .witness_v0_keyhash_prob = 0.40,
    69+    .witness_v0_scripthash_prob = 0.16,
    70     .witness_unknown_prob = 0.02,
    71     .nonstandard_prob = 0.02,
    72 
    

    l0rinc commented at 3:58 pm on February 16, 2026:

    I think it would be better to require that ScriptRecipe probabilities add up to ~1.0 and not generate random scripts as a fallback.

    Good idea, added random_script_prob to make this explicit.

  32. in src/bench/block_generator.cpp:117 in b5b57cdf39
    112+        const size_t in_count{GeomCount(rng, rec.geometric_base_prob)};
    113+        tx.vin.resize(in_count);
    114+        for (size_t in{0}; in < in_count; ++in) {
    115+            auto& tx_in{tx.vin[in]};
    116+            tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};
    117+            tx_in.scriptSig = rand_script();
    


    hodlinator commented at 11:49 am on February 11, 2026:

    How about generating semi-realistic script sigs / unlock scripts?

      0diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
      1index 1891400d20..4ee174f8f5 100644
      2--- a/src/bench/block_generator.cpp
      3+++ b/src/bench/block_generator.cpp
      4@@ -40,39 +40,164 @@ CPubKey RandPub(FastRandomContext& rng)
      5     return CPubKey(pubkey.begin(), pubkey.end());
      6 }
      7 
      8-auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
      9+struct FactoryEntry {
     10+    const double prob;
     11+    const std::function<CScript()> lock_script;
     12+    const std::function<CScript()> unlock_script;
     13+    const std::function<CScript()> witness_script; // TODO write + use field
     14+};
     15+
     16+auto createScriptFactory(FastRandomContext& rng, const CExtKey& xprv, const benchmark::ScriptRecipe& rec)
     17 {
     18-    std::array<std::pair<double, std::function<CScript()>>, 11> table{
     19-        std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},
     20-        std::pair{rec.multisig_prob, [&] {
     21-            const size_t keys_count{1 + rng.randrange<size_t>(15)};
     22-            const size_t required{1 + rng.randrange<size_t>(keys_count)};
     23-            std::vector<CPubKey> keys;
     24-            keys.reserve(keys_count);
     25-            for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
     26-            return GetScriptForMultisig(required, keys);
     27-        }},
     28-        {rec.null_data_prob, [&] {
     29-            const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
     30-            return CScript() << OP_RETURN << rng.randbytes<unsigned char>(len);
     31-        }},
     32-        std::pair{rec.pubkey_prob, [&] { return GetScriptForRawPubKey(RandPub(rng)); }},
     33-        std::pair{rec.pubkeyhash_prob, [&] { return GetScriptForDestination(PKHash(RandPub(rng))); }},
     34-        std::pair{rec.scripthash_prob, [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); }},
     35-        std::pair{rec.witness_v1_taproot_prob, [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); }},
     36-        std::pair{rec.witness_v0_keyhash_prob, [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); }},
     37-        std::pair{rec.witness_v0_scripthash_prob, [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); }},
     38-        std::pair{rec.witness_unknown_prob, [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); }},
     39-        std::pair{rec.nonstandard_prob, [&] { return CScript() << OP_TRUE; }},
     40+    FactoryEntry table[] = {
     41+        {
     42+            .prob = rec.anchor_prob,
     43+            .lock_script = [&] { return GetScriptForDestination(PayToAnchor{}); },
     44+            .unlock_script = {}, // TODO: confirm correctness
     45+            .witness_script = {}, // TODO: confirm correctness
     46+        },
     47+        {
     48+            .prob = rec.multisig_prob,
     49+            .lock_script = [&] {
     50+                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
     51+                const size_t required{1 + rng.randrange<size_t>(keys_count)};
     52+                std::vector<CPubKey> keys;
     53+                keys.reserve(keys_count);
     54+                for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
     55+                return GetScriptForMultisig(required, keys);
     56+            },
     57+            .unlock_script = [&] {
     58+                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
     59+                const size_t required{1 + rng.randrange<size_t>(keys_count)};
     60+                std::vector<CKey> priv_keys;
     61+                priv_keys.reserve(keys_count);
     62+                std::vector<CPubKey> pub_keys;
     63+                pub_keys.reserve(keys_count);
     64+
     65+                for (size_t i{}; i < keys_count; ++i) {
     66+                    CExtKey child_xprv;
     67+                    Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
     68+                    const CKey priv{child_xprv.key};
     69+                    priv_keys.push_back(priv);
     70+                    pub_keys.push_back(priv.GetPubKey());
     71+                }
     72+
     73+                const CScript prevout_lock_script{GetScriptForMultisig(required, pub_keys)};
     74+
     75+                CScript s{OP_0}; // Extra required dummy value for CHECKMULTISIG.
     76+                const uint8_t sighash_type{SIGHASH_ALL};
     77+                CMutableTransaction tx;
     78+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
     79+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
     80+                for (size_t i{}; i < required; ++i) {
     81+                    std::vector<uint8_t> sig;
     82+                    Assert(priv_keys[i].Sign(sighash, sig));
     83+                    sig.push_back(sighash_type);
     84+                    s << sig;
     85+                }
     86+
     87+                return s;
     88+            },
     89+            .witness_script = {}, // TODO: confirm correctness
     90+        },
     91+        {
     92+            .prob = rec.null_data_prob,
     93+            .lock_script = [&] {
     94+                const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
     95+                return CScript{} << OP_RETURN << rng.randbytes(len);
     96+            },
     97+            .unlock_script = {},  // Unspendable
     98+            .witness_script = {}, // Unspendable
     99+        },
    100+        {
    101+            .prob = rec.pubkey_prob,
    102+            .lock_script = [&] { return GetScriptForRawPubKey(RandPub(rng)); },
    103+            .unlock_script = [&] {
    104+                CExtKey child_xprv;
    105+                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
    106+                const CKey priv{child_xprv.key};
    107+                const CPubKey pub{priv.GetPubKey()};
    108+                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
    109+
    110+                const uint8_t sighash_type{SIGHASH_ALL};
    111+                CMutableTransaction tx;
    112+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
    113+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
    114+                std::vector<uint8_t> sig;
    115+                Assert(priv.Sign(sighash, sig));
    116+                sig.push_back(sighash_type);
    117+
    118+                return CScript{} << sig;
    119+            },
    120+            .witness_script = {},
    121+        },
    122+        {
    123+            .prob = rec.pubkeyhash_prob,
    124+            .lock_script = [&] { return GetScriptForDestination(PKHash(RandPub(rng))); },
    125+            .unlock_script = [&] {
    126+                CExtKey child_xprv;
    127+                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
    128+                const CKey priv{child_xprv.key};
    129+                const CPubKey pub{priv.GetPubKey()};
    130+                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
    131+
    132+                const uint8_t sighash_type{SIGHASH_ALL};
    133+                CMutableTransaction tx;
    134+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
    135+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
    136+                std::vector<uint8_t> sig;
    137+                Assert(priv.Sign(sighash, sig));
    138+                sig.push_back(sighash_type);
    139+
    140+                return CScript{} << sig << pub;
    141+            },
    142+            .witness_script = {},
    143+        },
    144+        {
    145+            .prob = rec.scripthash_prob,
    146+            .lock_script = [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); },
    147+            .unlock_script = {}, // TODO
    148+            .witness_script = {}, // TODO
    149+        },
    150+        {
    151+            .prob = rec.witness_v1_taproot_prob,
    152+            .lock_script = [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); },
    153+            .unlock_script = {}, // TODO
    154+            .witness_script = {}, // TODO
    155+        },
    156+        {
    157+            .prob = rec.witness_v0_keyhash_prob,
    158+            .lock_script = [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); },
    159+            .unlock_script = {}, // TODO
    160+            .witness_script = {}, // TODO
    161+        },
    162+        {
    163+            .prob = rec.witness_v0_scripthash_prob,
    164+            .lock_script = [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); },
    165+            .unlock_script = {}, // TODO
    166+            .witness_script = {}, // TODO
    167+        },
    168+        {
    169+            .prob = rec.witness_unknown_prob,
    170+            .lock_script = [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); },
    171+            .unlock_script = {}, // TODO
    172+            .witness_script = {}, // TODO
    173+        },
    174+        {
    175+            .prob = rec.nonstandard_prob,
    176+            .lock_script = [&] { return CScript() << OP_TRUE; },
    177+            .unlock_script = {}, // TODO
    178+            .witness_script = {}, // TODO
    179+        },
    180     };
    181 
    182     double sum{};
    183-    for (const auto& p : table | std::views::keys) sum += p;
    184+    for (const auto& e : table) sum += e.prob;
    185     // Verify that probabilities add up to ~1.0.
    186     assert(sum <= 1);
    187     assert(sum + 0.01 > 1.0);
    188 
    189-    return table;
    190+    return std::to_array(table);
    191 }
    192 
    193 CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)
    194@@ -80,6 +205,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
    195     assert(params.IsTestChain());
    196     FastRandomContext rng{seed};
    197 
    198+    CExtKey xprv;
    199+    constexpr auto xprv_seed{std::to_array({std::byte{'2'}, std::byte{'1'}})};
    200+    xprv.SetSeed(xprv_seed);
    201+
    202     assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
    203     const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
    204 
    205@@ -95,14 +224,33 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
    206         block.vtx.push_back(MakeTransactionRef(std::move(cb)));
    207     }
    208 
    209-    auto scriptFactory{createScriptFactory(rng, rec)};
    210-    auto rand_script{[&] {
    211+    auto scriptFactory{createScriptFactory(rng, xprv, rec)};
    212+    auto rand_lock_script{[&] {
    213         double probability{MakeUnitDouble(rng.rand64())};
    214-        for (const auto& [p, factory] : scriptFactory) {
    215-            if (probability < p) return factory();
    216-            probability -= p;
    217+        for (const auto& entry : scriptFactory) {
    218+            if (probability < entry.prob) return entry.lock_script();
    219+            probability -= entry.prob;
    220+        }
    221+        return scriptFactory.back().lock_script();
    222+    }};
    223+    const double unlock_script_prob{[&] {
    224+        double sum{0.0};
    225+        for (const auto& entry : scriptFactory) {
    226+            if (entry.unlock_script) sum += entry.prob;
    227+        }
    228+        return sum;
    229+    }()};
    230+    auto rand_unlock_script{[&] {
    231+        const FactoryEntry* last_unlock_entry{nullptr};
    232+        double probability{MakeUnitDouble(rng.rand64()) * unlock_script_prob};
    233+        for (const auto& entry : scriptFactory) {
    234+            if (!entry.unlock_script) continue;
    235+            last_unlock_entry = &entry;
    236+            if (probability < entry.prob) return entry.unlock_script();
    237+            probability -= entry.prob;
    238         }
    239-        return scriptFactory.back().second();
    240+        assert(last_unlock_entry);
    241+        return last_unlock_entry->unlock_script();
    242     }};
    243 
    244     // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
    245@@ -120,7 +268,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
    246         for (size_t in{0}; in < in_count; ++in) {
    247             auto& tx_in{tx.vin[in]};
    248             tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};
    249-            tx_in.scriptSig = rand_script();
    250+            tx_in.scriptSig = rand_unlock_script();
    251 
    252             const size_t witness_count{GeomCount(rng, rec.geometric_base_prob)};
    253             tx_in.scriptWitness.stack.reserve(witness_count);
    254@@ -136,7 +284,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
    255         for (size_t out{0}; out < out_count; ++out) {
    256             auto& tx_out{tx.vout[out]};
    257             tx_out.nValue = rng.randrange(GeomCount(rng, rec.geometric_base_prob) * COIN);
    258-            tx_out.scriptPubKey = rand_script();
    259+            tx_out.scriptPubKey = rand_lock_script();
    260         }
    261 
    262         block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
    263diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
    264index 2bc03818ac..23354ba39c 100644
    265--- a/src/bench/checkblock.cpp
    266+++ b/src/bench/checkblock.cpp
    267@@ -7,6 +7,7 @@
    268 #include <chainparams.h>
    269 #include <common/args.h>
    270 #include <consensus/validation.h>
    271+#include <key.h>
    272 #include <primitives/block.h>
    273 #include <primitives/transaction.h>
    274 #include <streams.h>
    275@@ -16,7 +17,6 @@
    276 #include <cassert>
    277 #include <cstddef>
    278 #include <memory>
    279-#include <vector>
    280 
    281 // These are the two major time-sinks which happen after we have fully received
    282 // a block off the wire, but before we can relay the block on to peers using
    283@@ -24,6 +24,7 @@
    284 
    285 static void DeserializeBlockTest(benchmark::Bench& bench)
    286 {
    287+    ECC_Context ec_context;
    288     auto stream{benchmark::GetBlockData()};
    289     std::byte a{0};
    290     stream.write({&a, 1}); // Prevent compaction
    291@@ -37,6 +38,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench)
    292 
    293 static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
    294 {
    295+    ECC_Context ec_context;
    296     const auto& chain_params{CChainParams::RegTest(CChainParams::RegTestOptions{})};
    297     auto stream{benchmark::GetBlockData(*chain_params)};
    298     std::byte a{0};
    

    hodlinator commented at 1:38 pm on February 11, 2026:
    I’m happy to continue working in this direction of more realistic scripts, but wanted to see what you thought before “helping” with something you might disagree with.

    l0rinc commented at 4:12 pm on February 16, 2026:
    Good point, split rand_lock_script and rand_unlock_script with very simple differentiation. But the above implementation scares me, not sure we need that at this stage. For this PR I want to keep generation self-contained and cheap, but I can imagine a bench+test helper in a follow-up where we can gradually make them more and more realistic. Thanks for spending so much time on this!
  33. hodlinator commented at 12:34 pm on February 11, 2026: contributor

    Concept ACK b5b57cdf396d8945612fcb2d51e530475db0d87d

    Strong problem A-C-K - It’s quite bad that we are only benchmarking against a pre-segwit block.

    The general direction is promising, I’d mainly prefer slightly more realistic transactions.

    Also, since removing the old block data doesn’t shrink the size of the repo, maybe we could keep it around together with some of the older benchmarks?

    Suggestions branch: https://github.com/bitcoin/bitcoin/compare/master...hodlinator:bitcoin:pr/32554_suggestions (Corresponds pretty much to the inline comments).

  34. l0rinc commented at 5:09 pm on February 16, 2026: contributor

    Thanks a lot for the excellent review (I see you started it in December already). I have applied almost every suggestion in one shape or form - let’s iterate on a few remaining ones, let’s first see if there’s an appetite for removing the pre-segwit block and having a configurable generator.

    Split the change up to smaller commits, first migrating the existing benchmarks to a common interface, followed by introducing the new generator (and the deletion as a separate commit since that huge binary is breaking the diff tools for me) Added you as coauthor.

  35. l0rinc force-pushed on Feb 16, 2026
  36. DrahtBot added the label CI failed on Feb 16, 2026
  37. maflcko commented at 7:38 am on February 20, 2026: member

    Could turn into draft while CI is red?

    https://github.com/bitcoin/bitcoin/actions/runs/22071701913/job/63777633190?pr=32554#step:11:2752

    0bench_bitcoin: bench/block_generator.cpp:136: auto (anonymous namespace)::CreateScriptFactory(FastRandomContext &, const benchmark::ScriptRecipe &): Assertion `sum <= 1.0 && sum + 0.01 > 1.0' failed.
    
  38. bench: use deterministic `HexStr` payload
    `HexStrBench` read bytes from the embedded block fixture, which coupled this string benchmark to block fixture migration work.
    Use deterministic pseudo-random `std::byte` input instead.
    Use `MAX_BLOCK_WEIGHT` so input stays in the same order of magnitude and measured work stays above harness overhead.
    This changes the baseline because input size moves from about 1 MB to 4 MB.
    
    Co-authored-by: hodlinator <172445034+hodlinator@users.noreply.github.com>
    20c2cff6e0
  39. bench: route legacy fixture through helper APIs
    Bench files loaded `block413567.raw` directly, so moving off the fixture required touching each benchmark's serialization setup.
    Add shared helper entry points backed by the current fixture and switch benchmark callsites to those helpers.
    This keeps behavior stable while moving setup to `regtest`, where the follow-up generated-block path can find a valid nonce quickly.
    
    Co-authored-by: hodlinator <hodlinator@users.noreply.github.com>
    c8a13620de
  40. bench: switch helper to generated blocks and recipes
    The helper migration still returned one fixed historical fixture block, which kept benchmark coverage narrow and blocked script-mix experiments.
    Replace the fixture-backed helper with runtime block generation and `ScriptRecipe` presets so callsites can tune output mixes.
    Add unit tests that lock in deterministic seed behavior and helper round-trip validity.
    Link `bench_bitcoin` with `secp256k1` so benchmark code can include `<secp256k1.h>` and use canonical pubkey tag constants.
    
    Co-authored-by: hodlinator <hodlinator@users.noreply.github.com>
    a3c791f7c6
  41. bench: remove embedded `block413567.raw` fixture
    After benchmarks switched to helper-generated data, the embedded `block413567.raw` fixture and its CMake raw-data wiring were no longer needed.
    
    Co-authored-by: hodlinator <hodlinator@users.noreply.github.com>
    406f143787
  42. l0rinc force-pushed on Feb 20, 2026
  43. l0rinc commented at 10:15 am on February 20, 2026: contributor

    Could turn into draft while CI is red?

    We can do even better and fix it instead!

  44. DrahtBot removed the label CI failed on Feb 20, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-03-03 03:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me