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

pull l0rinc wants to merge 3 commits into bitcoin:master from l0rinc:l0rinc/bench-block-generator changing 10 files +456 −57
  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:

    rm -rf build && \
    cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCH=ON && \
    cmake --build build -j$(nproc) && \
    build/bin/test_bitcoin -t block_generator_deterministic_seeded_output,block_generator_serialization_roundtrip,block_generator_seed_perturbation,block_generator_multiple_seed_sanity && \
    build/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

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    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 <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35025 (refactor: use SpanReader in deserialization benchmarks by l0rinc)
    • #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)
    • #29700 (kernel, refactor: return error status on all fatal errors by ryanofsky)

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  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:

    DataStream GenerateBlockData(
        const CChainParams& chain_params = *CChainParams::RegTest(CChainParams::RegTestOptions{}),
        const ScriptRecipe& = kWitness,
        const uint256& seed = {}
    );
    CBlock 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:

        block.hashPrevBlock = params.GenesisBlock().GetHash();
        assert(params.GetConsensus().powLimit == uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}); // lowest difficulty
        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)?

        block.nTime = params.GenesisBlock().nTime + 10 * 60;
        block.hashPrevBlock = params.GenesisBlock().GetHash();
        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?

        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:
                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:

            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:

    auto 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:

    namespace benchmark {
    namespace {
    ...
    
    auto 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:
        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.

    <details><summary>Diff</summary>

    diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
    index 2782b8d129..da7741f328 100644
    --- a/src/bench/block_generator.cpp
    +++ b/src/bench/block_generator.cpp
    @@ -79,10 +79,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
         FastRandomContext rng{seed};
     
         assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
    -    const auto tx_count{rec.tx_count ? rec.tx_count : 1000 + rng.randrange(2000)};
    +    const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
     
         CBlock block{};
    -    block.vtx.reserve(1 + tx_count);
    +    block.vtx.reserve(1 + (4000 * tx_occupancy_limit));
     
         // coinbase
         {
    @@ -95,7 +95,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
     
         auto scriptFactory{createScriptFactory(rng, rec)};
         auto rand_script{[&] {
    -        double probability{rng.rand64() * (1.0 / std::numeric_limits<uint64_t>::max())};
    +        double probability{MakeUnitDouble(rng.rand64())};
             for (const auto& [p, factory] : scriptFactory) {
                 if (probability < p) return factory();
                 probability -= p;
    @@ -104,7 +104,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
             return CScript(raw.begin(), raw.end());
         }};
     
    -    for (size_t i{0}; i < tx_count; ++i) {
    +    // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
    +    uint64_t block_size_no_witness{::GetSerializeSize(TX_NO_WITNESS(block)) + 2};
    +    uint64_t block_size_with_witness{::GetSerializeSize(TX_WITH_WITNESS(block)) + 2};
    +    const uint64_t block_size_limit(tx_occupancy_limit * MAX_BLOCK_WEIGHT);
    +    while (block_size_no_witness * WITNESS_SCALE_FACTOR < block_size_limit
    +           && block_size_no_witness * WITNESS_SCALE_FACTOR + (block_size_with_witness - block_size_no_witness) < block_size_limit) {
             CMutableTransaction tx;
             tx.version = 1 + rng.randrange<int>(3);
             tx.nLockTime = (rng.randrange<uint8_t>(100) < 90) ? 0 : rng.rand32();
    @@ -133,8 +138,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
                 tx_out.scriptPubKey = rand_script();
             }
     
    +        block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
    +        block_size_with_witness += ::GetSerializeSize(TX_WITH_WITNESS(tx));
             block.vtx.push_back(MakeTransactionRef(std::move(tx)));
         }
    +    // Remove the transaction that had us exceed the limit.
    +    block.vtx.pop_back();
     
         block.nVersion = 1 + rng.randrange<int>(VERSIONBITS_LAST_OLD_BLOCK_VERSION);
         block.nTime = params.GenesisBlock().nTime;
    diff --git a/src/bench/block_generator.h b/src/bench/block_generator.h
    index 4560005d53..4ce71f9c24 100644
    --- a/src/bench/block_generator.h
    +++ b/src/bench/block_generator.h
    @@ -28,7 +28,7 @@ struct ScriptRecipe
         double nonstandard_prob{0};
     
         //! tuning
    -    size_t tx_count{0};
    +    double tx_occupancy_limit{0}; // 0 means random, 1.0 is a full block.
         double geometric_base_prob{0.5};
     };
     
    @@ -46,7 +46,7 @@ inline constexpr ScriptRecipe kLegacy{
         .witness_unknown_prob = 0.00,
         .nonstandard_prob = 0.005,
     
    -    .tx_count = 1500,
    +    .tx_occupancy_limit = 0.8,
     };
     
     /* witness-heavy mix (roughly Taproot-era main chain) */
    @@ -63,7 +63,7 @@ inline constexpr ScriptRecipe kWitness{
         .witness_unknown_prob = 0.02,
         .nonstandard_prob = 0.02,
     
    -    .tx_count = 2000,
    +    .tx_occupancy_limit = 1.0,
     };
     
     DataStream GetBlockData(
    diff --git a/src/random.cpp b/src/random.cpp
    index aeaf6baa8e..1908d45644 100644
    --- a/src/random.cpp
    +++ b/src/random.cpp
    @@ -701,16 +701,22 @@ void RandomInit()
         ReportHardwareRand();
     }
     
    +double MakeUnitDouble(uint64_t uniform) noexcept
    +{
    +    // Convert uniform into a uniformly-distributed double in range [0, 1), use the expression
    +    // ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under
    +    // "Generating uniform doubles in the unit interval". Call this value x.
    +    return (uniform >> 11) * -0x1.0p-53;
    +}
    +
     double MakeExponentiallyDistributed(uint64_t uniform) noexcept
     {
         // To convert uniform into an exponentially-distributed double, we use two steps:
    -    // - Convert uniform into a uniformly-distributed double in range [0, 1), use the expression
    -    //   ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under
    -    //   "Generating uniform doubles in the unit interval". Call this value x.
    +    // - MakeUnitDouble()
         // - Given an x in uniformly distributed in [0, 1), we find an exponentially distributed value
         //   by applying the quantile function to it. For the exponential distribution with mean 1 this
         //   is F(x) = -log(1 - x).
         //
         // Combining the two, and using log1p(x) = log(1 + x), we obtain the following:
    -    return -std::log1p((uniform >> 11) * -0x1.0p-53);
    +    return -std::log1p(MakeUnitDouble(uniform));
     }
    diff --git a/src/random.h b/src/random.h
    index f0c0dcee81..ac2c5e924e 100644
    --- a/src/random.h
    +++ b/src/random.h
    @@ -158,6 +158,9 @@ concept StdChronoDuration = requires {
             std::type_identity<T>());
     };
     
    +/** Given a uniformly random uint64_t, return a uniformly-distributed double in range [0, 1). */
    +double MakeUnitDouble(uint64_t uniform) noexcept;
    +
     /** Given a uniformly random uint64_t, return an exponentially distributed double with mean 1. */
     double MakeExponentiallyDistributed(uint64_t uniform) noexcept;
     
    

    </details>


    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:

        // 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:

    const 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:

            const bool valid{CheckBlock(block, validationState, params.GetConsensus())};
            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):

        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.

    <details><summary>Diff which applies on top off the tx_occupancy_limit-suggestion from other comment</summary>

    diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
    index 1bedfb5f9d..1891400d20 100644
    --- a/src/bench/block_generator.cpp
    +++ b/src/bench/block_generator.cpp
    @@ -68,7 +68,9 @@ auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe&
     
         double sum{};
         for (const auto& p : table | std::views::keys) sum += p;
    +    // Verify that probabilities add up to ~1.0.
         assert(sum <= 1);
    +    assert(sum + 0.01 > 1.0);
     
         return table;
     }
    @@ -100,8 +102,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
                 if (probability < p) return factory();
                 probability -= p;
             }
    -        const auto raw{rng.randbytes<uint8_t>(1 + rng.randrange(100))};
    -        return CScript(raw.begin(), raw.end());
    +        return scriptFactory.back().second();
         }};
     
         // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
    diff --git a/src/bench/block_generator.h b/src/bench/block_generator.h
    index 4ce71f9c24..9ce7ea5ad8 100644
    --- a/src/bench/block_generator.h
    +++ b/src/bench/block_generator.h
    @@ -35,16 +35,16 @@ struct ScriptRecipe
     /* purely legacy outputs, useful for pre-SegWit baselines */
     inline constexpr ScriptRecipe kLegacy{
         .anchor_prob = 0.00,
    -    .multisig_prob = 0.05,
    -    .null_data_prob = 0.005,
    -    .pubkey_prob = 0.10,
    -    .pubkeyhash_prob = 0.20,
    -    .scripthash_prob = 0.10,
    -    .witness_v1_taproot_prob = 0.00,
    -    .witness_v0_keyhash_prob = 0.00,
    -    .witness_v0_scripthash_prob = 0.00,
    -    .witness_unknown_prob = 0.00,
    -    .nonstandard_prob = 0.005,
    +    .multisig_prob = 0.10,
    +    .null_data_prob = 0.01,
    +    .pubkey_prob = 0.20,
    +    .pubkeyhash_prob = 0.43,
    +    .scripthash_prob = 0.23,
    +    .witness_v1_taproot_prob = 0,
    +    .witness_v0_keyhash_prob = 0,
    +    .witness_v0_scripthash_prob = 0,
    +    .witness_unknown_prob = 0,
    +    .nonstandard_prob = 0.025,
     
         .tx_occupancy_limit = 0.8,
     };
    @@ -53,13 +53,13 @@ inline constexpr ScriptRecipe kLegacy{
     inline constexpr ScriptRecipe kWitness{
         .anchor_prob = 0.001,
         .multisig_prob = 0.005,
    -    .null_data_prob = 0.002,
    +    .null_data_prob = 0.01,
         .pubkey_prob = 0.001,
         .pubkeyhash_prob = 0.02,
         .scripthash_prob = 0.01,
    -    .witness_v1_taproot_prob = 0.20,
    -    .witness_v0_keyhash_prob = 0.25,
    -    .witness_v0_scripthash_prob = 0.05,
    +    .witness_v1_taproot_prob = 0.35,
    +    .witness_v0_keyhash_prob = 0.40,
    +    .witness_v0_scripthash_prob = 0.16,
         .witness_unknown_prob = 0.02,
         .nonstandard_prob = 0.02,
     
    

    </details>


    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?

    <details><summary>WIP diff</summary>

    diff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp
    index 1891400d20..4ee174f8f5 100644
    --- a/src/bench/block_generator.cpp
    +++ b/src/bench/block_generator.cpp
    @@ -40,39 +40,164 @@ CPubKey RandPub(FastRandomContext& rng)
         return CPubKey(pubkey.begin(), pubkey.end());
     }
     
    -auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)
    +struct FactoryEntry {
    +    const double prob;
    +    const std::function<CScript()> lock_script;
    +    const std::function<CScript()> unlock_script;
    +    const std::function<CScript()> witness_script; // TODO write + use field
    +};
    +
    +auto createScriptFactory(FastRandomContext& rng, const CExtKey& xprv, const benchmark::ScriptRecipe& rec)
     {
    -    std::array<std::pair<double, std::function<CScript()>>, 11> table{
    -        std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},
    -        std::pair{rec.multisig_prob, [&] {
    -            const size_t keys_count{1 + rng.randrange<size_t>(15)};
    -            const size_t required{1 + rng.randrange<size_t>(keys_count)};
    -            std::vector<CPubKey> keys;
    -            keys.reserve(keys_count);
    -            for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
    -            return GetScriptForMultisig(required, keys);
    -        }},
    -        {rec.null_data_prob, [&] {
    -            const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
    -            return CScript() << OP_RETURN << rng.randbytes<unsigned char>(len);
    -        }},
    -        std::pair{rec.pubkey_prob, [&] { return GetScriptForRawPubKey(RandPub(rng)); }},
    -        std::pair{rec.pubkeyhash_prob, [&] { return GetScriptForDestination(PKHash(RandPub(rng))); }},
    -        std::pair{rec.scripthash_prob, [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); }},
    -        std::pair{rec.witness_v1_taproot_prob, [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); }},
    -        std::pair{rec.witness_v0_keyhash_prob, [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); }},
    -        std::pair{rec.witness_v0_scripthash_prob, [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); }},
    -        std::pair{rec.witness_unknown_prob, [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); }},
    -        std::pair{rec.nonstandard_prob, [&] { return CScript() << OP_TRUE; }},
    +    FactoryEntry table[] = {
    +        {
    +            .prob = rec.anchor_prob,
    +            .lock_script = [&] { return GetScriptForDestination(PayToAnchor{}); },
    +            .unlock_script = {}, // TODO: confirm correctness
    +            .witness_script = {}, // TODO: confirm correctness
    +        },
    +        {
    +            .prob = rec.multisig_prob,
    +            .lock_script = [&] {
    +                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
    +                const size_t required{1 + rng.randrange<size_t>(keys_count)};
    +                std::vector<CPubKey> keys;
    +                keys.reserve(keys_count);
    +                for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));
    +                return GetScriptForMultisig(required, keys);
    +            },
    +            .unlock_script = [&] {
    +                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};
    +                const size_t required{1 + rng.randrange<size_t>(keys_count)};
    +                std::vector<CKey> priv_keys;
    +                priv_keys.reserve(keys_count);
    +                std::vector<CPubKey> pub_keys;
    +                pub_keys.reserve(keys_count);
    +
    +                for (size_t i{}; i < keys_count; ++i) {
    +                    CExtKey child_xprv;
    +                    Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
    +                    const CKey priv{child_xprv.key};
    +                    priv_keys.push_back(priv);
    +                    pub_keys.push_back(priv.GetPubKey());
    +                }
    +
    +                const CScript prevout_lock_script{GetScriptForMultisig(required, pub_keys)};
    +
    +                CScript s{OP_0}; // Extra required dummy value for CHECKMULTISIG.
    +                const uint8_t sighash_type{SIGHASH_ALL};
    +                CMutableTransaction tx;
    +                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
    +                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
    +                for (size_t i{}; i < required; ++i) {
    +                    std::vector<uint8_t> sig;
    +                    Assert(priv_keys[i].Sign(sighash, sig));
    +                    sig.push_back(sighash_type);
    +                    s << sig;
    +                }
    +
    +                return s;
    +            },
    +            .witness_script = {}, // TODO: confirm correctness
    +        },
    +        {
    +            .prob = rec.null_data_prob,
    +            .lock_script = [&] {
    +                const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules
    +                return CScript{} << OP_RETURN << rng.randbytes(len);
    +            },
    +            .unlock_script = {},  // Unspendable
    +            .witness_script = {}, // Unspendable
    +        },
    +        {
    +            .prob = rec.pubkey_prob,
    +            .lock_script = [&] { return GetScriptForRawPubKey(RandPub(rng)); },
    +            .unlock_script = [&] {
    +                CExtKey child_xprv;
    +                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
    +                const CKey priv{child_xprv.key};
    +                const CPubKey pub{priv.GetPubKey()};
    +                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
    +
    +                const uint8_t sighash_type{SIGHASH_ALL};
    +                CMutableTransaction tx;
    +                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
    +                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
    +                std::vector<uint8_t> sig;
    +                Assert(priv.Sign(sighash, sig));
    +                sig.push_back(sighash_type);
    +
    +                return CScript{} << sig;
    +            },
    +            .witness_script = {},
    +        },
    +        {
    +            .prob = rec.pubkeyhash_prob,
    +            .lock_script = [&] { return GetScriptForDestination(PKHash(RandPub(rng))); },
    +            .unlock_script = [&] {
    +                CExtKey child_xprv;
    +                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));
    +                const CKey priv{child_xprv.key};
    +                const CPubKey pub{priv.GetPubKey()};
    +                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};
    +
    +                const uint8_t sighash_type{SIGHASH_ALL};
    +                CMutableTransaction tx;
    +                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());
    +                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};
    +                std::vector<uint8_t> sig;
    +                Assert(priv.Sign(sighash, sig));
    +                sig.push_back(sighash_type);
    +
    +                return CScript{} << sig << pub;
    +            },
    +            .witness_script = {},
    +        },
    +        {
    +            .prob = rec.scripthash_prob,
    +            .lock_script = [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
    +        {
    +            .prob = rec.witness_v1_taproot_prob,
    +            .lock_script = [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
    +        {
    +            .prob = rec.witness_v0_keyhash_prob,
    +            .lock_script = [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
    +        {
    +            .prob = rec.witness_v0_scripthash_prob,
    +            .lock_script = [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
    +        {
    +            .prob = rec.witness_unknown_prob,
    +            .lock_script = [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
    +        {
    +            .prob = rec.nonstandard_prob,
    +            .lock_script = [&] { return CScript() << OP_TRUE; },
    +            .unlock_script = {}, // TODO
    +            .witness_script = {}, // TODO
    +        },
         };
     
         double sum{};
    -    for (const auto& p : table | std::views::keys) sum += p;
    +    for (const auto& e : table) sum += e.prob;
         // Verify that probabilities add up to ~1.0.
         assert(sum <= 1);
         assert(sum + 0.01 > 1.0);
     
    -    return table;
    +    return std::to_array(table);
     }
     
     CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)
    @@ -80,6 +205,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
         assert(params.IsTestChain());
         FastRandomContext rng{seed};
     
    +    CExtKey xprv;
    +    constexpr auto xprv_seed{std::to_array({std::byte{'2'}, std::byte{'1'}})};
    +    xprv.SetSeed(xprv_seed);
    +
         assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);
         const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};
     
    @@ -95,14 +224,33 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
             block.vtx.push_back(MakeTransactionRef(std::move(cb)));
         }
     
    -    auto scriptFactory{createScriptFactory(rng, rec)};
    -    auto rand_script{[&] {
    +    auto scriptFactory{createScriptFactory(rng, xprv, rec)};
    +    auto rand_lock_script{[&] {
             double probability{MakeUnitDouble(rng.rand64())};
    -        for (const auto& [p, factory] : scriptFactory) {
    -            if (probability < p) return factory();
    -            probability -= p;
    +        for (const auto& entry : scriptFactory) {
    +            if (probability < entry.prob) return entry.lock_script();
    +            probability -= entry.prob;
    +        }
    +        return scriptFactory.back().lock_script();
    +    }};
    +    const double unlock_script_prob{[&] {
    +        double sum{0.0};
    +        for (const auto& entry : scriptFactory) {
    +            if (entry.unlock_script) sum += entry.prob;
    +        }
    +        return sum;
    +    }()};
    +    auto rand_unlock_script{[&] {
    +        const FactoryEntry* last_unlock_entry{nullptr};
    +        double probability{MakeUnitDouble(rng.rand64()) * unlock_script_prob};
    +        for (const auto& entry : scriptFactory) {
    +            if (!entry.unlock_script) continue;
    +            last_unlock_entry = &entry;
    +            if (probability < entry.prob) return entry.unlock_script();
    +            probability -= entry.prob;
             }
    -        return scriptFactory.back().second();
    +        assert(last_unlock_entry);
    +        return last_unlock_entry->unlock_script();
         }};
     
         // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.
    @@ -120,7 +268,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
             for (size_t in{0}; in < in_count; ++in) {
                 auto& tx_in{tx.vin[in]};
                 tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};
    -            tx_in.scriptSig = rand_script();
    +            tx_in.scriptSig = rand_unlock_script();
     
                 const size_t witness_count{GeomCount(rng, rec.geometric_base_prob)};
                 tx_in.scriptWitness.stack.reserve(witness_count);
    @@ -136,7 +284,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec
             for (size_t out{0}; out < out_count; ++out) {
                 auto& tx_out{tx.vout[out]};
                 tx_out.nValue = rng.randrange(GeomCount(rng, rec.geometric_base_prob) * COIN);
    -            tx_out.scriptPubKey = rand_script();
    +            tx_out.scriptPubKey = rand_lock_script();
             }
     
             block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));
    diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
    index 2bc03818ac..23354ba39c 100644
    --- a/src/bench/checkblock.cpp
    +++ b/src/bench/checkblock.cpp
    @@ -7,6 +7,7 @@
     #include <chainparams.h>
     #include <common/args.h>
     #include <consensus/validation.h>
    +#include <key.h>
     #include <primitives/block.h>
     #include <primitives/transaction.h>
     #include <streams.h>
    @@ -16,7 +17,6 @@
     #include <cassert>
     #include <cstddef>
     #include <memory>
    -#include <vector>
     
     // These are the two major time-sinks which happen after we have fully received
     // a block off the wire, but before we can relay the block on to peers using
    @@ -24,6 +24,7 @@
     
     static void DeserializeBlockTest(benchmark::Bench& bench)
     {
    +    ECC_Context ec_context;
         auto stream{benchmark::GetBlockData()};
         std::byte a{0};
         stream.write({&a, 1}); // Prevent compaction
    @@ -37,6 +38,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench)
     
     static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
     {
    +    ECC_Context ec_context;
         const auto& chain_params{CChainParams::RegTest(CChainParams::RegTestOptions{})};
         auto stream{benchmark::GetBlockData(*chain_params)};
         std::byte a{0};
    

    </details>


    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

    bench_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. l0rinc force-pushed on Feb 20, 2026
  39. 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!

  40. DrahtBot removed the label CI failed on Feb 20, 2026
  41. sedited requested review from hodlinator on Mar 16, 2026
  42. sedited requested review from davidgumberg on Mar 19, 2026
  43. sedited referenced this in commit 667e081a2a on Mar 24, 2026
  44. l0rinc force-pushed on Mar 25, 2026
  45. DrahtBot added the label Needs rebase on Apr 6, 2026
  46. 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>
    Co-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>
    e0d989b96d
  47. 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>
    bb0ef0bfc0
  48. 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>
    e20886998c
  49. l0rinc force-pushed on Apr 7, 2026
  50. l0rinc commented at 1:01 PM on April 7, 2026: contributor

    Rebased after the nanobench setup PR and applied #34208 (review) to checkblock benchmarks to make them even simpler - ready for review again.

  51. DrahtBot removed the label Needs rebase on Apr 7, 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-04-13 18:12 UTC

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