{
 "number": 32554,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/32554",
 "title": "bench: replace embedded raw block with configurable block generator",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2025-05-18T21:39:14Z",
 "updated_at": "2026-09-16T02:59:03Z",
 "age_days": 486,
 "draft": false,
 "labels": [
  "Tests",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
 "head_ref": "l0rinc/bench-block-generator",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [
  {
   "t": "2025-12-20T23:18:52Z",
   "sha": "0fbbefd017a817bc32b5eb4dbb7647c668c8a61f"
  },
  {
   "t": "2026-02-07T19:16:08Z",
   "sha": "b5b57cdf396d8945612fcb2d51e530475db0d87d"
  },
  {
   "t": "2026-02-16T17:09:53Z",
   "sha": "ea001f66246597a050cfe1f080592cc0da17ebc9"
  },
  {
   "t": "2026-02-20T10:14:18Z",
   "sha": "406f1437872471a467c51e491095358163739840"
  },
  {
   "t": "2026-03-25T11:35:38Z",
   "sha": "3793ae94c6dc98ab7eb73acff2cef34a590b8357"
  },
  {
   "t": "2026-04-07T12:36:25Z",
   "sha": "e20886998c89fc1176d407600373ee709fd36ab0"
  },
  {
   "t": "2026-04-23T07:21:01Z",
   "sha": "d97466727ac702e483333b965f58d74efb37c588"
  },
  {
   "t": "2026-06-09T10:32:22Z",
   "sha": "a18780860927a0c43313109707e13d6b15396517"
  },
  {
   "t": "2026-06-09T11:22:49Z",
   "sha": "31bae5edfe49ba454d4d18f2940ce71d63377054"
  },
  {
   "t": "2026-06-15T21:15:10Z",
   "sha": "64f9ac690e70e853a923cdb7991e6ae4312da291"
  },
  {
   "t": "2026-06-15T22:28:24Z",
   "sha": "9bd4f187d145fc798215e821b8fd4719e58dbdc3"
  },
  {
   "t": "2026-07-23T01:14:40Z",
   "sha": "83c1df6e59980ec8871467a8ef9670e13688cc32"
  },
  {
   "t": "2026-08-21T19:57:42Z",
   "sha": "7b37612fc9e6dad15941c36879e2fb5ee5804d22"
  }
 ],
 "additions": 460,
 "deletions": 45,
 "changed_files": 10,
 "commit_count": 3,
 "size_bucket": "L",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "laanwj",
      "url": "https://github.com/bitcoin/bitcoin/pull/32554#issuecomment-2890272937"
     },
     {
      "login": "yuvicc",
      "url": "https://github.com/bitcoin/bitcoin/pull/32554#issuecomment-3016802388"
     },
     {
      "login": "Raimo33",
      "url": "https://github.com/bitcoin/bitcoin/pull/32554#issuecomment-3419716580"
     },
     {
      "login": "hodlinator",
      "url": "https://github.com/bitcoin/bitcoin/pull/32554#pullrequestreview-3605702499"
     },
     {
      "login": "0xB10C",
      "url": "https://github.com/bitcoin/bitcoin/pull/32554#issuecomment-5691379383"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36181,
     "title": "ci: Upgrade IWYU to 0.27 compatible with Clang 23",
     "author": "hebasto"
    },
    {
     "number": 36171,
     "title": "blockstorage: Defer error notifications to callers",
     "author": "w0xlt"
    },
    {
     "number": 35646,
     "title": "RFC: Separate out runtime errors from BlockValidationState using `util::Expected`",
     "author": "yuvicc"
    },
    {
     "number": 35591,
     "title": "[DO NOT MERGE] Erlay: bandwidth-efficient transaction relay protocol (Full implementation)",
     "author": "sr-gi"
    }
   ]
  }
 },
 "acks_parsed": {
  "laanwj": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-05-19T09:18:41Z",
   "stale": false
  },
  "yuvicc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-06-29T15:44:33Z",
   "stale": false
  },
  "Raimo33": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-10-19T14:37:15Z",
   "stale": false
  },
  "hodlinator": {
   "kind": "concept_ack",
   "hash": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "t": "2026-02-11T12:34:48Z",
   "stale": false
  },
  "0xB10C": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-16T02:59:00Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 5,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "0xB10C",
   "Raimo33",
   "hodlinator",
   "laanwj",
   "maflcko",
   "yuvicc"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-08-21T19:57:42Z",
  "last_reviewer_activity": "2026-09-16T02:59:00Z",
  "last_reviewer": "0xB10C",
  "author_silent_days": 26,
  "waiting_on_author_days": 1,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   32457,
   34598
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 32457,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "bench: replace benchmark block with more representative one (413567 \u2192 784588)"
   },
   {
    "number": 34598,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-03-24",
    "title": "bench: use larger payload in HexStrBench"
   }
  ],
  "conflicts": [
   36181,
   36171,
   35646,
   35591
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/bench/block_generator.cpp",
  "src/bench/block_generator.h",
  "src/bench/load_external.cpp",
  "src/bench/readwriteblock.cpp",
  "src/bench/strencodings.cpp"
 ],
 "body": "**Problem:** The [hardcoded benchmark block 413567](https://mempool.space/block/413567) has several issues:\n- embeds ~1MB of binary data in the repository (we can't remove it from history, but we can from `HEAD`)\n- mined in 2016, predates SegWit v0, Taproot, and modern script types\n- benchmarks cannot test different transaction patterns or script mixes\n- updating to a newer block (e.g., [#32457](https://github.com/bitcoin/bitcoin/pull/32457)) would still add binary bloat and would remain inflexible\n\n**Fix:** Added configurable `bench/block_generator.{h,cpp}` that builds fully valid blocks at runtime, providing blocks or raw serialized streams.\n\n**Reproducer:** You can run all affected tests and benchmarks with a sanity check:\n```bash\nrm -rf build && \\\ncmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCH=ON && \\\ncmake --build build -j$(nproc) && \\\nbuild/bin/test_bitcoin -t block_generator_deterministic_seeded_output,block_generator_serialization_roundtrip,block_generator_seed_perturbation,block_generator_multiple_seed_sanity && \\\nbuild/bin/bench_bitcoin -sanity-check -filter='DeserializeBlockTest|CheckBlockTest|LoadExternalBlockFile|WriteBlockBench|ReadBlockBench|ReadRawBlockBench|BlockToJsonVerbosity1|BlockToJsonVerbosity2|BlockToJsonVerbosity3|BlockToJsonVerboseWrite|HexStrBench'\n```\n---\n\nThis supersedes #32457. New benchmark scenarios can vary transaction and script mixes without adding another embedded block.",
 "commits": [
  {
   "sha": "a28c0201bf32a83be5e8d2f0e63e61a28943258c",
   "date": "2026-08-21T19:56:54Z",
   "message": "bench: route legacy fixture through helper APIs\n\nBench files loaded `block413567.raw` directly, so moving off the fixture required touching each benchmark's serialization setup.\nAdd shared helper entry points backed by the current fixture and switch benchmark callsites to those helpers.\nThis keeps behavior stable while moving setup to `regtest`, where the follow-up generated-block path can find a valid nonce quickly.\n\nCo-authored-by: hodlinator <hodlinator@users.noreply.github.com>\nCo-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>"
  },
  {
   "sha": "c27c6ff63444dd571e2d7d670520bbfd0c7a59af",
   "date": "2026-08-21T19:57:22Z",
   "message": "bench: switch helper to generated blocks and recipes\n\nThe helper migration still returned one fixed historical fixture block, which kept benchmark coverage narrow and blocked script-mix experiments.\nReplace the fixture-backed helper with runtime block generation and `ScriptRecipe` presets so callsites can tune output mixes.\nAdd unit tests that lock in deterministic seed behavior and helper round-trip validity.\nLink `bench_bitcoin` with `secp256k1` so benchmark code can include `<secp256k1.h>` and use canonical pubkey tag constants.\n\nCo-authored-by: hodlinator <hodlinator@users.noreply.github.com>"
  },
  {
   "sha": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "date": "2026-08-21T19:57:27Z",
   "message": "bench: remove embedded `block413567.raw` fixture\n\nAfter benchmarks switched to helper-generated data, the embedded `block413567.raw` fixture and its cmake raw-data wiring were no longer needed.\n\nCo-authored-by: hodlinator <hodlinator@users.noreply.github.com>"
  }
 ],
 "timeline": [
  {
   "t": "2025-05-19T09:18:41Z",
   "kind": "comment",
   "who": "laanwj",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2025-06-29T15:44:33Z",
   "kind": "comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK\n\nThis one is pretty useful as I can test with up-to date test_data."
  },
  {
   "t": "2025-10-19T14:37:15Z",
   "kind": "comment",
   "who": "Raimo33",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK for more realistic benchmarks"
  },
  {
   "t": "2025-12-20T23:18:52Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "0fbbefd017a817bc32b5eb4dbb7647c668c8a61f"
  },
  {
   "t": "2025-12-20T23:19:52Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Rebased and applied the new block everywhere, removing the binary of block 413567 embedded in the benchmarks.\nLet me know what you think."
  },
  {
   "t": "2025-12-22T20:55:46Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.h",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Naming should probably indicate work/computation:\n```suggestion\nDataStream GenerateBlockData(\n    const CChainParams& chain_params = *CChainParams::RegTest(CChainParams::RegTestOptions{}),\n    const ScriptRecipe& = kWitness,\n    const uint256& seed = {}\n);\nCBlock GenerateBlock(\n```"
  },
  {
   "t": "2025-12-22T21:01:28Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/load_external.cpp",
   "commit": "3793ae94c6dc98ab7eb73acff2cef34a590b8357",
   "in_reply_to": null,
   "text": "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?\n\nCould also replace this assert...\nhttps://github.com/bitcoin/bitcoin/blob/0fbbefd017a817bc32b5eb4dbb7647c668c8a61f/src/bench/block_generator.cpp#L76-L78\n\n...with a more direct assert further down, closer to where it's important:\n```C++\n    block.hashPrevBlock = params.GenesisBlock().GetHash();\n    assert(params.GetConsensus().powLimit == uint256{\"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"}); // lowest difficulty\n    block.nBits = UintToArith256(params.GetConsensus().powLimit).GetCompact();\n```"
  },
  {
   "t": "2025-12-22T21:21:46Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Maybe have genesis block as previous block to be slightly more regular, instead of being a second genesis block (due to null `hashPrevBlock`)?\n```suggestion\n    block.nTime = params.GenesisBlock().nTime + 10 * 60;\n    block.hashPrevBlock = params.GenesisBlock().GetHash();\n    block.nBits = params.GenesisBlock().nBits;\n```\nBeing 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)."
  },
  {
   "t": "2025-12-22T21:30:11Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/strencodings.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "nit: Why not go for something in the same order of magnitude in size as on master, and use more modern std::bytes?\n```suggestion\n    auto data{rng.randbytes(MAX_BLOCK_WEIGHT)};\n```"
  },
  {
   "t": "2025-12-22T21:35:02Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/readwriteblock.cpp",
   "commit": "d97466727ac702e483333b965f58d74efb37c588",
   "in_reply_to": null,
   "text": "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.\n\n1 would be correct if our prev is genesis though, see my other comment."
  },
  {
   "t": "2025-12-22T21:44:33Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "```suggestion\n            const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};\n```"
  },
  {
   "t": "2025-12-22T21:49:51Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Not sure what's going on here regarding the sometimes need of explicit type, but might as well stay consistent:\n```suggestion\n        std::pair{rec.null_data_prob, [&] {\n```"
  },
  {
   "t": "2025-12-22T21:51:59Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Developer notes encourage:\n```suggestion\nauto CreateScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)\n```"
  },
  {
   "t": "2025-12-22T21:54:06Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "nit: Would prefer only mentioning namespace once at the top:\n\n```C++\nnamespace benchmark {\nnamespace {\n...\n```\n\n```suggestion\nauto createScriptFactory(FastRandomContext& rng, const ScriptRecipe& rec)\n```"
  },
  {
   "t": "2025-12-22T21:56:43Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Could be uncompressed in some cases: https://learnmeabitcoin.com/technical/script/p2pk/"
  },
  {
   "t": "2025-12-22T22:13:13Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2025-12-22T22:18:38Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "in_reply_to": null,
   "text": "https://learnmeabitcoin.com/technical/mining/coinbase-transaction/#requirements\n\n[quoted text omitted]"
  },
  {
   "t": "2025-12-22T22:20:59Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "```suggestion\n    auto script_factory{createScriptFactory(rng, rec)};\n```"
  },
  {
   "t": "2025-12-23T20:01:41Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "in_reply_to": null,
   "text": "When experimenting with changing the scripts I hit block size limits with just 2000 transactions due to large scripts.\n\nIt 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.\n\nWe could also insert an OP_RETURN output to a random tx to make it reach exactly the limit.\n\nDiff\n\n```diff\ndiff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp\nindex 2782b8d129..da7741f328 100644\n--- a/src/bench/block_generator.cpp\n+++ b/src/bench/block_generator.cpp\n@@ -79,10 +79,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n     FastRandomContext rng{seed};\n\n     assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);\n-    const auto tx_count{rec.tx_count ? rec.tx_count : 1000 + rng.randrange(2000)};\n+    const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};\n\n     CBlock block{};\n-    block.vtx.reserve(1 + tx_count);\n+    block.vtx.reserve(1 + (4000 * tx_occupancy_limit));\n\n     // coinbase\n     {\n@@ -95,7 +95,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n\n     auto scriptFactory{createScriptFactory(rng, rec)};\n     auto rand_script{[&] {\n-        double probability{rng.rand64() * (1.0 / std::numeric_limits<uint64_t>::max())};\n+        double probability{MakeUnitDouble(rng.rand64())};\n         for (const auto& [p, factory] : scriptFactory) {\n             if (probability < p) return factory();\n             probability -= p;\n@@ -104,7 +104,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n         return CScript(raw.begin(), raw.end());\n     }};\n\n-    for (size_t i{0}; i < tx_count; ++i) {\n+    // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.\n+    uint64_t block_size_no_witness{::GetSerializeSize(TX_NO_WITNESS(block)) + 2};\n+    uint64_t block_size_with_witness{::GetSerializeSize(TX_WITH_WITNESS(block)) + 2};\n+    const uint64_t block_size_limit(tx_occupancy_limit * MAX_BLOCK_WEIGHT);\n+    while (block_size_no_witness * WITNESS_SCALE_FACTOR < block_size_limit\n+           && block_size_no_witness * WITNESS_SCALE_FACTOR + (block_size_with_witness - block_size_no_witness) < block_size_limit) {\n         CMutableTransaction tx;\n         tx.version = 1 + rng.randrange<int>(3);\n         tx.nLockTime = (rng.randrange<uint8_t>(100) < 90) ? 0 : rng.rand32();\n@@ -133,8 +138,12 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n             tx_out.scriptPubKey = rand_script();\n         }\n\n+        block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));\n+        block_size_with_witness += ::GetSerializeSize(TX_WITH_WITNESS(tx));\n         block.vtx.push_back(MakeTransactionRef(std::move(tx)));\n     }\n+    // Remove the transaction that had us exceed the limit.\n+    block.vtx.pop_back();\n\n     block.nVersion = 1 + rng.randrange<int>(VERSIONBITS_LAST_OLD_BLOCK_VERSION);\n     block.nTime = params.GenesisBlock().nTime;\ndiff --git a/src/bench/block_generator.h b/src/bench/block_generator.h\nindex 4560005d53..4ce71f9c24 100644\n--- a/src/bench/block_generator.h\n+++ b/src/bench/block_generator.h\n@@ -28,7 +28,7 @@ struct ScriptRecipe\n     double nonstandard_prob{0};\n\n     //! tuning\n-    size_t tx_count{0};\n+    double tx_occupancy_limit{0}; // 0 means random, 1.0 is a full block.\n     double geometric_base_prob{0.5};\n };\n\n@@ -46,7 +46,7 @@ inline constexpr ScriptRecipe kLegacy{\n     .witness_unknown_prob = 0.00,\n     .nonstandard_prob = 0.005,\n\n-    .tx_count = 1500,\n+    .tx_occupancy_limit = 0.8,\n };\n\n /* witness-heavy mix (roughly Taproot-era main chain) */\n@@ -63,7 +63,7 @@ inline constexpr ScriptRecipe kWitness{\n     .witness_unknown_prob = 0.02,\n     .nonstandard_prob = 0.02,\n\n-    .tx_count = 2000,\n+    .tx_occupancy_limit = 1.0,\n };\n\n DataStream GetBlockData(\ndiff --git a/src/random.cpp b/src/random.cpp\nindex aeaf6baa8e..1908d45644 100644\n--- a/src/random.cpp\n+++ b/src/random.cpp\n@@ -701,16 +701,22 @@ void RandomInit()\n     ReportHardwareRand();\n }\n\n+double MakeUnitDouble(uint64_t uniform) noexcept\n+{\n+    // Convert uniform into a uniformly-distributed double in range [0, 1), use the expression\n+    // ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under\n+    // \"Generating uniform doubles in the unit interval\". Call this value x.\n+    return (uniform >> 11) * -0x1.0p-53;\n+}\n+\n double MakeExponentiallyDistributed(uint64_t uniform) noexcept\n {\n     // To convert uniform into an exponentially-distributed double, we use two steps:\n-    // - Convert uniform into a uniformly-distributed double in range [0, 1), use the expression\n-    //   ((uniform >> 11) * 0x1.0p-53), as described in https://prng.di.unimi.it/ under\n-    //   \"Generating uniform doubles in the unit interval\". Call this value x.\n+    // - MakeUnitDouble()\n     // - Given an x in uniformly distributed in [0, 1), we find an exponentially distributed value\n     //   by applying the quantile function to it. For the exponential distribution with mean 1 this\n     //   is F(x) = -log(1 - x).\n     //\n     // Combining the two, and using log1p(x) = log(1 + x), we obtain the following:\n-    return -std::log1p((uniform >> 11) * -0x1.0p-53);\n+    return -std::log1p(MakeUnitDouble(uniform));\n }\ndiff --git a/src/random.h b/src/random.h\nindex f0c0dcee81..ac2c5e924e 100644\n--- a/src/random.h\n+++ b/src/random.h\n@@ -158,6 +158,9 @@ concept StdChronoDuration = requires {\n         std::type_identity<T>());\n };\n\n+/** Given a uniformly random uint64_t, return a uniformly-distributed double in range [0, 1). */\n+double MakeUnitDouble(uint64_t uniform) noexcept;\n+\n /** Given a uniformly random uint64_t, return an exponentially distributed double with mean 1. */\n double MakeExponentiallyDistributed(uint64_t uniform) noexcept;\n\n```"
  },
  {
   "t": "2026-02-07T19:16:08Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d"
  },
  {
   "t": "2026-02-09T20:55:32Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Could add caveat:\n```suggestion\n    // Make sure we've generated a valid block (barring checks requiring context of other blocks)\n```"
  },
  {
   "t": "2026-02-09T22:02:41Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "Might want to make OP_RETURNs very large sometimes, probably using non-linear randomness?"
  },
  {
   "t": "2026-02-10T10:00:17Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "nit: Naming:\n```suggestion\n        const bool valid{CheckBlock(block, validationState, params.GetConsensus())};\n        assert(valid);\n```"
  },
  {
   "t": "2026-02-10T10:44:46Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.h",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "nit: Could document field (unless the approach is switched to `min_tx_occupancy`, see other comment):\n```suggestion\n    size_t tx_count{0}; //!< Does not include coinbase tx. Leave at 0 to pick a random, valid number.\n```"
  },
  {
   "t": "2026-02-11T11:37:42Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "I think it would be better to require that `ScriptRecipe` probabilities add up to ~1.0 and not generate random scripts as a fallback.\n\nIf we want to still generate random scripts it should be it's own `ScriptRecipe::random_prob` field IMO.\n\nDiff which applies on top off the tx_occupancy_limit-suggestion from other comment\n\n```diff\ndiff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp\nindex 1bedfb5f9d..1891400d20 100644\n--- a/src/bench/block_generator.cpp\n+++ b/src/bench/block_generator.cpp\n@@ -68,7 +68,9 @@ auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe&\n\n     double sum{};\n     for (const auto& p : table | std::views::keys) sum += p;\n+    // Verify that probabilities add up to ~1.0.\n     assert(sum <= 1);\n+    assert(sum + 0.01 > 1.0);\n\n     return table;\n }\n@@ -100,8 +102,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n             if (probability < p) return factory();\n             probability -= p;\n         }\n-        const auto raw{rng.randbytes<uint8_t>(1 + rng.randrange(100))};\n-        return CScript(raw.begin(), raw.end());\n+        return scriptFactory.back().second();\n     }};\n\n     // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.\ndiff --git a/src/bench/block_generator.h b/src/bench/block_generator.h\nindex 4ce71f9c24..9ce7ea5ad8 100644\n--- a/src/bench/block_generator.h\n+++ b/src/bench/block_generator.h\n@@ -35,16 +35,16 @@ struct ScriptRecipe\n /* purely legacy outputs, useful for pre-SegWit baselines */\n inline constexpr ScriptRecipe kLegacy{\n     .anchor_prob = 0.00,\n-    .multisig_prob = 0.05,\n-    .null_data_prob = 0.005,\n-    .pubkey_prob = 0.10,\n-    .pubkeyhash_prob = 0.20,\n-    .scripthash_prob = 0.10,\n-    .witness_v1_taproot_prob = 0.00,\n-    .witness_v0_keyhash_prob = 0.00,\n-    .witness_v0_scripthash_prob = 0.00,\n-    .witness_unknown_prob = 0.00,\n-    .nonstandard_prob = 0.005,\n+    .multisig_prob = 0.10,\n+    .null_data_prob = 0.01,\n+    .pubkey_prob = 0.20,\n+    .pubkeyhash_prob = 0.43,\n+    .scripthash_prob = 0.23,\n+    .witness_v1_taproot_prob = 0,\n+    .witness_v0_keyhash_prob = 0,\n+    .witness_v0_scripthash_prob = 0,\n+    .witness_unknown_prob = 0,\n+    .nonstandard_prob = 0.025,\n\n     .tx_occupancy_limit = 0.8,\n };\n@@ -53,13 +53,13 @@ inline constexpr ScriptRecipe kLegacy{\n inline constexpr ScriptRecipe kWitness{\n     .anchor_prob = 0.001,\n     .multisig_prob = 0.005,\n-    .null_data_prob = 0.002,\n+    .null_data_prob = 0.01,\n     .pubkey_prob = 0.001,\n     .pubkeyhash_prob = 0.02,\n     .scripthash_prob = 0.01,\n-    .witness_v1_taproot_prob = 0.20,\n-    .witness_v0_keyhash_prob = 0.25,\n-    .witness_v0_scripthash_prob = 0.05,\n+    .witness_v1_taproot_prob = 0.35,\n+    .witness_v0_keyhash_prob = 0.40,\n+    .witness_v0_scripthash_prob = 0.16,\n     .witness_unknown_prob = 0.02,\n     .nonstandard_prob = 0.02,\n\n```"
  },
  {
   "t": "2026-02-11T11:49:09Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": null,
   "text": "How about generating semi-realistic script sigs / unlock scripts?\n\nWIP diff\n\n```diff\ndiff --git a/src/bench/block_generator.cpp b/src/bench/block_generator.cpp\nindex 1891400d20..4ee174f8f5 100644\n--- a/src/bench/block_generator.cpp\n+++ b/src/bench/block_generator.cpp\n@@ -40,39 +40,164 @@ CPubKey RandPub(FastRandomContext& rng)\n     return CPubKey(pubkey.begin(), pubkey.end());\n }\n\n-auto createScriptFactory(FastRandomContext& rng, const benchmark::ScriptRecipe& rec)\n+struct FactoryEntry {\n+    const double prob;\n+    const std::function<CScript()> lock_script;\n+    const std::function<CScript()> unlock_script;\n+    const std::function<CScript()> witness_script; // TODO write + use field\n+};\n+\n+auto createScriptFactory(FastRandomContext& rng, const CExtKey& xprv, const benchmark::ScriptRecipe& rec)\n {\n-    std::array<std::pair<double, std::function<CScript()>>, 11> table{\n-        std::pair{rec.anchor_prob, [&] { return GetScriptForDestination(PayToAnchor{}); }},\n-        std::pair{rec.multisig_prob, [&] {\n-            const size_t keys_count{1 + rng.randrange<size_t>(15)};\n-            const size_t required{1 + rng.randrange<size_t>(keys_count)};\n-            std::vector<CPubKey> keys;\n-            keys.reserve(keys_count);\n-            for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));\n-            return GetScriptForMultisig(required, keys);\n-        }},\n-        {rec.null_data_prob, [&] {\n-            const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules\n-            return CScript() << OP_RETURN << rng.randbytes<unsigned char>(len);\n-        }},\n-        std::pair{rec.pubkey_prob, [&] { return GetScriptForRawPubKey(RandPub(rng)); }},\n-        std::pair{rec.pubkeyhash_prob, [&] { return GetScriptForDestination(PKHash(RandPub(rng))); }},\n-        std::pair{rec.scripthash_prob, [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); }},\n-        std::pair{rec.witness_v1_taproot_prob, [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); }},\n-        std::pair{rec.witness_v0_keyhash_prob, [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); }},\n-        std::pair{rec.witness_v0_scripthash_prob, [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); }},\n-        std::pair{rec.witness_unknown_prob, [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); }},\n-        std::pair{rec.nonstandard_prob, [&] { return CScript() << OP_TRUE; }},\n+    FactoryEntry table[] = {\n+        {\n+            .prob = rec.anchor_prob,\n+            .lock_script = [&] { return GetScriptForDestination(PayToAnchor{}); },\n+            .unlock_script = {}, // TODO: confirm correctness\n+            .witness_script = {}, // TODO: confirm correctness\n+        },\n+        {\n+            .prob = rec.multisig_prob,\n+            .lock_script = [&] {\n+                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};\n+                const size_t required{1 + rng.randrange<size_t>(keys_count)};\n+                std::vector<CPubKey> keys;\n+                keys.reserve(keys_count);\n+                for (size_t i{}; i < keys_count; ++i) keys.emplace_back(RandPub(rng));\n+                return GetScriptForMultisig(required, keys);\n+            },\n+            .unlock_script = [&] {\n+                const size_t keys_count{1 + rng.randrange<size_t>(MAX_PUBKEYS_PER_MULTISIG)};\n+                const size_t required{1 + rng.randrange<size_t>(keys_count)};\n+                std::vector<CKey> priv_keys;\n+                priv_keys.reserve(keys_count);\n+                std::vector<CPubKey> pub_keys;\n+                pub_keys.reserve(keys_count);\n+\n+                for (size_t i{}; i < keys_count; ++i) {\n+                    CExtKey child_xprv;\n+                    Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));\n+                    const CKey priv{child_xprv.key};\n+                    priv_keys.push_back(priv);\n+                    pub_keys.push_back(priv.GetPubKey());\n+                }\n+\n+                const CScript prevout_lock_script{GetScriptForMultisig(required, pub_keys)};\n+\n+                CScript s{OP_0}; // Extra required dummy value for CHECKMULTISIG.\n+                const uint8_t sighash_type{SIGHASH_ALL};\n+                CMutableTransaction tx;\n+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());\n+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};\n+                for (size_t i{}; i < required; ++i) {\n+                    std::vector<uint8_t> sig;\n+                    Assert(priv_keys[i].Sign(sighash, sig));\n+                    sig.push_back(sighash_type);\n+                    s << sig;\n+                }\n+\n+                return s;\n+            },\n+            .witness_script = {}, // TODO: confirm correctness\n+        },\n+        {\n+            .prob = rec.null_data_prob,\n+            .lock_script = [&] {\n+                const auto len{1 + rng.randrange<size_t>(90)}; // sometimes exceed policy rules\n+                return CScript{} << OP_RETURN << rng.randbytes(len);\n+            },\n+            .unlock_script = {},  // Unspendable\n+            .witness_script = {}, // Unspendable\n+        },\n+        {\n+            .prob = rec.pubkey_prob,\n+            .lock_script = [&] { return GetScriptForRawPubKey(RandPub(rng)); },\n+            .unlock_script = [&] {\n+                CExtKey child_xprv;\n+                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));\n+                const CKey priv{child_xprv.key};\n+                const CPubKey pub{priv.GetPubKey()};\n+                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};\n+\n+                const uint8_t sighash_type{SIGHASH_ALL};\n+                CMutableTransaction tx;\n+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());\n+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};\n+                std::vector<uint8_t> sig;\n+                Assert(priv.Sign(sighash, sig));\n+                sig.push_back(sighash_type);\n+\n+                return CScript{} << sig;\n+            },\n+            .witness_script = {},\n+        },\n+        {\n+            .prob = rec.pubkeyhash_prob,\n+            .lock_script = [&] { return GetScriptForDestination(PKHash(RandPub(rng))); },\n+            .unlock_script = [&] {\n+                CExtKey child_xprv;\n+                Assert(xprv.Derive(child_xprv, rng.rand<unsigned int>()));\n+                const CKey priv{child_xprv.key};\n+                const CPubKey pub{priv.GetPubKey()};\n+                const CScript prevout_lock_script{GetScriptForRawPubKey(pub)};\n+\n+                const uint8_t sighash_type{SIGHASH_ALL};\n+                CMutableTransaction tx;\n+                tx.vin.emplace_back(COutPoint{}, CScript{}, rng.rand32());\n+                const uint256 sighash{SignatureHash(prevout_lock_script, tx, 0, sighash_type, CAmount{0}, SigVersion::BASE)};\n+                std::vector<uint8_t> sig;\n+                Assert(priv.Sign(sighash, sig));\n+                sig.push_back(sighash_type);\n+\n+                return CScript{} << sig << pub;\n+            },\n+            .witness_script = {},\n+        },\n+        {\n+            .prob = rec.scripthash_prob,\n+            .lock_script = [&] { return GetScriptForDestination(ScriptHash(CScript() << OP_TRUE)); },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n+        {\n+            .prob = rec.witness_v1_taproot_prob,\n+            .lock_script = [&] { return GetScriptForDestination(WitnessV1Taproot(XOnlyPubKey(RandPub(rng)))); },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n+        {\n+            .prob = rec.witness_v0_keyhash_prob,\n+            .lock_script = [&] { return GetScriptForDestination(WitnessV0KeyHash(RandPub(rng))); },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n+        {\n+            .prob = rec.witness_v0_scripthash_prob,\n+            .lock_script = [&] { return GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)); },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n+        {\n+            .prob = rec.witness_unknown_prob,\n+            .lock_script = [&] { return CScript() << OP_2 << rng.randbytes<uint8_t>(32); },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n+        {\n+            .prob = rec.nonstandard_prob,\n+            .lock_script = [&] { return CScript() << OP_TRUE; },\n+            .unlock_script = {}, // TODO\n+            .witness_script = {}, // TODO\n+        },\n     };\n\n     double sum{};\n-    for (const auto& p : table | std::views::keys) sum += p;\n+    for (const auto& e : table) sum += e.prob;\n     // Verify that probabilities add up to ~1.0.\n     assert(sum <= 1);\n     assert(sum + 0.01 > 1.0);\n\n-    return table;\n+    return std::to_array(table);\n }\n\n CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec, const uint256& seed)\n@@ -80,6 +205,10 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n     assert(params.IsTestChain());\n     FastRandomContext rng{seed};\n\n+    CExtKey xprv;\n+    constexpr auto xprv_seed{std::to_array({std::byte{'2'}, std::byte{'1'}})};\n+    xprv.SetSeed(xprv_seed);\n+\n     assert(rec.geometric_base_prob >= 0 && rec.geometric_base_prob <= 1);\n     const auto tx_occupancy_limit{rec.tx_occupancy_limit != 0.0 ? rec.tx_occupancy_limit : MakeUnitDouble(rng.rand64())};\n\n@@ -95,14 +224,33 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n         block.vtx.push_back(MakeTransactionRef(std::move(cb)));\n     }\n\n-    auto scriptFactory{createScriptFactory(rng, rec)};\n-    auto rand_script{[&] {\n+    auto scriptFactory{createScriptFactory(rng, xprv, rec)};\n+    auto rand_lock_script{[&] {\n         double probability{MakeUnitDouble(rng.rand64())};\n-        for (const auto& [p, factory] : scriptFactory) {\n-            if (probability < p) return factory();\n-            probability -= p;\n+        for (const auto& entry : scriptFactory) {\n+            if (probability < entry.prob) return entry.lock_script();\n+            probability -= entry.prob;\n+        }\n+        return scriptFactory.back().lock_script();\n+    }};\n+    const double unlock_script_prob{[&] {\n+        double sum{0.0};\n+        for (const auto& entry : scriptFactory) {\n+            if (entry.unlock_script) sum += entry.prob;\n+        }\n+        return sum;\n+    }()};\n+    auto rand_unlock_script{[&] {\n+        const FactoryEntry* last_unlock_entry{nullptr};\n+        double probability{MakeUnitDouble(rng.rand64()) * unlock_script_prob};\n+        for (const auto& entry : scriptFactory) {\n+            if (!entry.unlock_script) continue;\n+            last_unlock_entry = &entry;\n+            if (probability < entry.prob) return entry.unlock_script();\n+            probability -= entry.prob;\n         }\n-        return scriptFactory.back().second();\n+        assert(last_unlock_entry);\n+        return last_unlock_entry->unlock_script();\n     }};\n\n     // Add 2 bytes to account for compact size representation of vtx vector increasing from 1 to 3 bytes.\n@@ -120,7 +268,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n         for (size_t in{0}; in < in_count; ++in) {\n             auto& tx_in{tx.vin[in]};\n             tx_in.prevout = {Txid::FromUint256(rng.rand256()), uint32_t(GeomCount(rng, rec.geometric_base_prob))};\n-            tx_in.scriptSig = rand_script();\n+            tx_in.scriptSig = rand_unlock_script();\n\n             const size_t witness_count{GeomCount(rng, rec.geometric_base_prob)};\n             tx_in.scriptWitness.stack.reserve(witness_count);\n@@ -136,7 +284,7 @@ CBlock BuildBlock(const CChainParams& params, const benchmark::ScriptRecipe& rec\n         for (size_t out{0}; out < out_count; ++out) {\n             auto& tx_out{tx.vout[out]};\n             tx_out.nValue = rng.randrange(GeomCount(rng, rec.geometric_base_prob) * COIN);\n-            tx_out.scriptPubKey = rand_script();\n+            tx_out.scriptPubKey = rand_lock_script();\n         }\n\n         block_size_no_witness += ::GetSerializeSize(TX_NO_WITNESS(tx));\ndiff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp\nindex 2bc03818ac..23354ba39c 100644\n--- a/src/bench/checkblock.cpp\n+++ b/src/bench/checkblock.cpp\n@@ -7,6 +7,7 @@\n #include <chainparams.h>\n #include <common/args.h>\n #include <consensus/validation.h>\n+#include <key.h>\n #include <primitives/block.h>\n #include <primitives/transaction.h>\n #include <streams.h>\n@@ -16,7 +17,6 @@\n #include <cassert>\n #include <cstddef>\n #include <memory>\n-#include <vector>\n\n // These are the two major time-sinks which happen after we have fully received\n // a block off the wire, but before we can relay the block on to peers using\n@@ -24,6 +24,7 @@\n\n static void DeserializeBlockTest(benchmark::Bench& bench)\n {\n+    ECC_Context ec_context;\n     auto stream{benchmark::GetBlockData()};\n     std::byte a{0};\n     stream.write({&a, 1}); // Prevent compaction\n@@ -37,6 +38,7 @@ static void DeserializeBlockTest(benchmark::Bench& bench)\n\n static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)\n {\n+    ECC_Context ec_context;\n     const auto& chain_params{CChainParams::RegTest(CChainParams::RegTestOptions{})};\n     auto stream{benchmark::GetBlockData(*chain_params)};\n     std::byte a{0};\n```"
  },
  {
   "t": "2026-02-11T12:34:48Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "text": "Concept ACK b5b57cdf396d8945612fcb2d51e530475db0d87d\n\n**Strong problem A-C-K - It's quite bad that we are only benchmarking against a pre-segwit block.**\n\nThe general direction is promising, I'd mainly prefer slightly more realistic transactions.\n\nAlso, 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?\n\nSuggestions branch: https://github.com/bitcoin/bitcoin/compare/master...hodlinator:bitcoin:pr/32554_suggestions\n(Corresponds pretty much to the inline comments)."
  },
  {
   "t": "2026-02-11T13:38:32Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2792847114,
   "text": "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."
  },
  {
   "t": "2026-02-16T12:00:16Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/strencodings.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2641304162,
   "text": "Good idea, applied those and split it into a separate PR, and added you as co-author in #34598"
  },
  {
   "t": "2026-02-16T13:06:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.h",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2641210291,
   "text": "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."
  },
  {
   "t": "2026-02-16T13:21:26Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/load_external.cpp",
   "commit": "3793ae94c6dc98ab7eb73acff2cef34a590b8357",
   "in_reply_to": 2641223542,
   "text": "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.\n\nI 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."
  },
  {
   "t": "2026-02-16T13:22:32Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2641285591,
   "text": "Added genesis as `hashPrevBlock`, thanks."
  },
  {
   "t": "2026-02-16T13:23:34Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/readwriteblock.cpp",
   "commit": "d97466727ac702e483333b965f58d74efb37c588",
   "in_reply_to": 2641314769,
   "text": "we have a genesis now so kept the 1 - thanks!"
  },
  {
   "t": "2026-02-16T13:35:40Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2641357046,
   "text": "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`)"
  },
  {
   "t": "2026-02-16T14:31:25Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "in_reply_to": 2641410600,
   "text": "I have added https://github.com/bitcoin/bitcoin/blob/37cc2a2d953c072a236b657bfd7de5167092a47a/src/validation.cpp#L4076 which runs `CheckWitnessMalleation` validation here, thanks for the comment"
  },
  {
   "t": "2026-02-16T14:35:01Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
   "in_reply_to": 2644156219,
   "text": "[quoted text omitted]\n\nThe 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.\n\nI 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.\n\n[quoted text omitted]\nI have added a `block_generator_multiple_seed_sanity` to help us discover these invalid setups anyway."
  },
  {
   "t": "2026-02-16T14:35:38Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2784493271,
   "text": "Reworded it, thanks"
  },
  {
   "t": "2026-02-16T15:45:49Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2784737375,
   "text": "With a fixed tx_count, even a few rare \u201chuge\u201d OP_RETURNs can push the block overweight and make the generator fail deterministically for the default seed.\nBut I've bumped it a tiny bit and added better comment:\n```C++\nconst auto len{1 + rng.randrange<size_t>(100)}; // can exceed pre-v30 OP_RETURN 83-byte policy limits\n```"
  },
  {
   "t": "2026-02-16T15:58:55Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2792800008,
   "text": "[quoted text omitted]\n\nGood idea, added `random_script_prob` to make this explicit."
  },
  {
   "t": "2026-02-16T16:12:55Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/bench/block_generator.cpp",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "in_reply_to": 2792847114,
   "text": "Good point, split `rand_lock_script` and `rand_unlock_script` with very simple differentiation.\nBut the above implementation scares me, not sure we need that at this stage.\nFor 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.\nThanks for spending so much time on this!"
  },
  {
   "t": "2026-02-16T17:09:51Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "b5b57cdf396d8945612fcb2d51e530475db0d87d",
   "text": "Thanks a lot for the excellent review (I see you started it in December already).\nI 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.\n\nSplit 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)\nAdded you as coauthor."
  },
  {
   "t": "2026-02-16T17:09:53Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "ea001f66246597a050cfe1f080592cc0da17ebc9"
  },
  {
   "t": "2026-02-20T07:38:18Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Could turn into draft while CI is red?\n\nhttps://github.com/bitcoin/bitcoin/actions/runs/22071701913/job/63777633190?pr=32554#step:11:2752\n\n```\nbench_bitcoin: bench/block_generator.cpp:136: auto (anonymous namespace)::CreateScriptFactory(FastRandomContext &, const benchmark::ScriptRecipe &): Assertion `sum <= 1.0 && sum + 0.01 > 1.0' failed."
  },
  {
   "t": "2026-02-20T10:14:18Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "406f1437872471a467c51e491095358163739840"
  },
  {
   "t": "2026-02-20T10:15:03Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nWe can do even better and fix it instead!"
  },
  {
   "t": "2026-03-25T11:35:38Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "3793ae94c6dc98ab7eb73acff2cef34a590b8357"
  },
  {
   "t": "2026-04-07T12:36:25Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "e20886998c89fc1176d407600373ee709fd36ab0"
  },
  {
   "t": "2026-04-07T13:01:01Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Rebased after the nanobench setup PR and applied https://github.com/bitcoin/bitcoin/pull/34208#discussion_r3044663534 to checkblock benchmarks to make them even simpler - ready for review again."
  },
  {
   "t": "2026-04-23T07:21:01Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "d97466727ac702e483333b965f58d74efb37c588"
  },
  {
   "t": "2026-06-09T10:32:22Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "a18780860927a0c43313109707e13d6b15396517"
  },
  {
   "t": "2026-06-09T11:22:49Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "31bae5edfe49ba454d4d18f2940ce71d63377054"
  },
  {
   "t": "2026-06-15T21:15:10Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "64f9ac690e70e853a923cdb7991e6ae4312da291"
  },
  {
   "t": "2026-06-15T22:28:24Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "9bd4f187d145fc798215e821b8fd4719e58dbdc3"
  },
  {
   "t": "2026-07-23T01:14:40Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "83c1df6e59980ec8871467a8ef9670e13688cc32"
  },
  {
   "t": "2026-08-21T19:57:42Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "7b37612fc9e6dad15941c36879e2fb5ee5804d22"
  },
  {
   "t": "2026-09-16T02:59:00Z",
   "kind": "comment",
   "who": "0xB10C",
   "assoc": "MEMBER",
   "text": "Concept ACK\n\nI think, if we're want to use blocks in our benchmarks going forward, something like this is the right move as a) it takes up far less repo space, and b) is extensible and we don't need to add a recent mainnet block every few years. See also https://github.com/bitcoin/bitcoin/pull/36254#issuecomment-5686775045, where I came to a similar conclusion.\n\nI wonder if it makes sense to add undo data generation here too (and eventually add benchmarks for writing/reading it). See https://github.com/bitcoin/bitcoin/pull/36254"
  }
 ],
 "labels_log": [
  {
   "t": "2025-05-21T14:03:47Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-07T04:12:34Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-07T19:41:50Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-16T18:02:24Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-20T11:36:32Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-06T22:53:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-07T14:14:56Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-23T06:14:48Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-23T08:23:19Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-08T19:43:46Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-09T11:22:21Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-09T11:23:18Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-09T12:44:44Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-15T14:34:31Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-15T21:38:36Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-15T22:28:51Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-16T07:38:52Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-21T08:43:39Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-21T21:53:36Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-09T11:54:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-05-21T13:44:06Z",
   "kind": "ready_for_review",
   "who": "l0rinc"
  },
  {
   "t": "2025-05-21T14:03:43Z",
   "kind": "renamed",
   "who": "l0rinc",
   "from": "RFC: bench: replace embedded raw block with configurable block generator",
   "to": "bench: replace embedded raw block with configurable block generator"
  }
 ],
 "text_chars": 34745,
 "text_tokens_estimate": 8686,
 "changed_paths": [
  "src/bench/CMakeLists.txt",
  "src/bench/block_generator.cpp",
  "src/bench/block_generator.h",
  "src/bench/checkblock.cpp",
  "src/bench/data/block413567.raw",
  "src/bench/load_external.cpp",
  "src/bench/readwriteblock.cpp",
  "src/bench/rpc_blockchain.cpp",
  "src/test/CMakeLists.txt",
  "src/test/bench_block_generator_tests.cpp"
 ],
 "files": [
  {
   "path": "src/bench/CMakeLists.txt",
   "add": 2,
   "del": 5
  },
  {
   "path": "src/bench/block_generator.cpp",
   "add": 278,
   "del": 0
  },
  {
   "path": "src/bench/block_generator.h",
   "add": 86,
   "del": 0
  },
  {
   "path": "src/bench/checkblock.cpp",
   "add": 6,
   "del": 6
  },
  {
   "path": "src/bench/data/block413567.raw",
   "add": null,
   "del": null
  },
  {
   "path": "src/bench/load_external.cpp",
   "add": 6,
   "del": 5
  },
  {
   "path": "src/bench/readwriteblock.cpp",
   "add": 13,
   "del": 20
  },
  {
   "path": "src/bench/rpc_blockchain.cpp",
   "add": 5,
   "del": 9
  },
  {
   "path": "src/test/CMakeLists.txt",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/test/bench_block_generator_tests.cpp",
   "add": 62,
   "del": 0
  }
 ],
 "test_lines": 505,
 "git": {
  "head": "7b37612fc9e6dad15941c36879e2fb5ee5804d22",
  "head_matches_backup": true,
  "base": "58a7869f860f022e6163dc3fae3106a0599830c5",
  "commits": [
   {
    "sha": "a28c0201bf",
    "subject": "bench: route legacy fixture through helper APIs",
    "files": 7,
    "add": 80,
    "del": 38
   },
   {
    "sha": "c27c6ff634",
    "subject": "bench: switch helper to generated blocks and recipes",
    "files": 6,
    "add": 392,
    "del": 14
   },
   {
    "sha": "7b37612fc9",
    "subject": "bench: remove embedded `block413567.raw` fixture",
    "files": 2,
    "add": 0,
    "del": 5
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "7b017e600457945e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}