{
 "number": 32575,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/32575",
 "title": "consensus: Remove special treatment for single threaded script checking",
 "author": "fjahr",
 "author_association": "MEMBER",
 "created_at": "2025-05-20T20:52:49Z",
 "updated_at": "2026-09-11T00:23:29Z",
 "age_days": 484,
 "draft": false,
 "labels": [
  "Refactoring"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "270af71e1eeca2615e9388c7906fef6bea792f31",
 "head_ref": "2025-05-CheckInputScript-split",
 "head_repo": "fjahr/bitcoin",
 "head_history": [
  {
   "t": "2025-05-20T21:43:33Z",
   "sha": "4262b75726e717f89f232867c87bc6af50de82a5"
  },
  {
   "t": "2025-05-22T20:07:15Z",
   "sha": "5dfd10c725bf44557ae06038ce54b4924ecc0fae"
  },
  {
   "t": "2025-06-19T22:00:13Z",
   "sha": "edcc004c40439b58fa2244da13c6b7fa3d028ba5"
  },
  {
   "t": "2025-06-20T12:59:30Z",
   "sha": "6667ea17a9562237749027c808732b9ee0e09fb1"
  },
  {
   "t": "2025-08-13T13:00:05Z",
   "sha": "4fd17753a0108e75bd1dde393d23267a61159e0d"
  },
  {
   "t": "2025-10-09T16:36:59Z",
   "sha": "ba32c2bbbc708b6eca8300844a5c0a6adf16583e"
  },
  {
   "t": "2025-10-11T18:01:36Z",
   "sha": "02e1c01ed49d69be92ea71c0111aaf8faa2289b9"
  },
  {
   "t": "2026-02-01T14:26:22Z",
   "sha": "7c3fa071b4160c13c62f1e36d9fd3c1678a0b807"
  },
  {
   "t": "2026-02-27T23:02:41Z",
   "sha": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688"
  },
  {
   "t": "2026-03-11T16:37:21Z",
   "sha": "1063f2cfce1117ea230a2affd4010ef91d96d300"
  },
  {
   "t": "2026-03-23T16:50:05Z",
   "sha": "23ac5f3f48d00a2087f9676609eb2e4b48813235"
  },
  {
   "t": "2026-05-11T13:02:51Z",
   "sha": "dfecae46c14c98e62ad8e2503493b152cb9b5b91"
  },
  {
   "t": "2026-05-28T21:48:21Z",
   "sha": "270af71e1eeca2615e9388c7906fef6bea792f31"
  }
 ],
 "additions": 107,
 "deletions": 85,
 "changed_files": 4,
 "commit_count": 6,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_nack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-4054825361"
     }
    ],
    "concept_ack": [
     {
      "login": "theuni",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-2895813848"
     },
     {
      "login": "stickies-v",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-2897442292"
     },
     {
      "login": "darosior",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-4075771363"
     },
     {
      "login": "Eunovo",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#pullrequestreview-4341927967"
     }
    ],
    "approach_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/32575#pullrequestreview-3520711695"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35662,
     "title": "script: prevent stale sighash caches across transactions",
     "author": "l0rinc"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    },
    {
     "number": 29843,
     "title": "policy: Allow non-standard scripts with -acceptnonstdtxn=1 (test nets only)",
     "author": "ajtowns"
    },
    {
     "number": 29491,
     "title": "[EXPERIMENTAL] Schnorr batch verification for blocks",
     "author": "fjahr"
    }
   ]
  }
 },
 "acks_parsed": {
  "theuni": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-05-20T20:57:04Z",
   "stale": false
  },
  "sedited": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-02-13T17:24:01Z",
   "stale": false
  },
  "stickies-v": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-05-21T10:28:43Z",
   "stale": false
  },
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-03-05T17:06:38Z",
   "stale": false
  },
  "darosior": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-03-17T15:16:10Z",
   "stale": false
  },
  "Eunovo": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-05-22T02:52:41Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 5,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 1,
  "distinct_reviewers": [
   "Eunovo",
   "darosior",
   "l0rinc",
   "maflcko",
   "mzumsande",
   "sedited",
   "stickies-v",
   "svanstaa",
   "theuni"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-05-28T22:15:39Z",
  "last_reviewer_activity": "2026-05-22T02:52:41Z",
  "last_reviewer": "Eunovo",
  "author_silent_days": 111,
  "waiting_on_author_days": 0,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [
   32317,
   35209
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 32317,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "kernel: Separate UTXO set access from validation functions"
   },
   {
    "number": 35209,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-05-06",
    "title": "validation: correct lifetime of precomputed tx data"
   }
  ],
  "conflicts": [
   35662,
   35569,
   29843,
   29491
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/txvalidationcache_tests.cpp",
  "src/validation.cpp"
 ],
 "body": "This topic has been motivated by my work on batch validation and a related conversation just happened here: https://github.com/bitcoin/bitcoin/pull/32467#discussion_r2098305199\n\n`CheckInputScripts` currently only does what its name implies if there is no multithreading usage for validation. If there are worker threads available the function instead only creates the checks and puts them into a vector. Aside from some shared pre-checks the multithreaded code path is much simpler compared to the rest. This dual use makes the function hard to grasp and its naming confusing.\n\nThis PR refactors this code to be more readable and improves documentation aside from unifying the single threaded and the multithreaded cases by letting checkqueue handle single threaded script checking as well. The code is already suited for this and there seems to be not noticable performance overhead from this.",
 "commits": [
  {
   "sha": "79e0bd7edf7cfa8202d2d069aa5080df0cd7edf2",
   "date": "2026-05-11T12:10:17Z",
   "message": "refactor, validation: Encapsulate script execution cache access in ValidationCache\n\nMove m_script_execution_cache to private and expose IsScriptValidated()\nand CacheScriptValidation() methods. This ensures the AssertLockHeld\ncheck is consistently applied for both cache lookups and insertions,\nrather than relying on callers to assert the lock.\n\nCo-authored-by: l0rinc <pap.lorinc@gmail.com>"
  },
  {
   "sha": "5e2f9c4d6a2eb5b760c35ffb67d7860af4bd6b95",
   "date": "2026-05-11T12:10:19Z",
   "message": "refactor, validation: Extract EnsureTxData from CheckInputScripts\n\nMove the spent outputs initialization and txdata setup into a\nstandalone helper."
  },
  {
   "sha": "2d553bdf24d1296969681c681c5daef3f9d1ae4f",
   "date": "2026-05-28T20:48:06Z",
   "message": "refactor: Extract GetScriptCacheEntry from CheckInputScripts\n\nSeparate the script execution cache lookup (coinbase check and cache\nlookup) into GetScriptCacheEntry. It returns std::optional<uint256> \u2014\nnullopt if no script checks are needed (coinbase or cache hit), or the\ncache entry hash for use by the caller when storing results later.\n\nCheckInputScripts now calls GetScriptCacheEntry internally and\ninitializes txdata via EnsureTxData only when checks are needed. This\nprepares for ConnectBlock to call GetScriptCacheEntry directly and\nconstruct script checks without going through CheckInputScripts."
  },
  {
   "sha": "f14e5d5be831112438d15d41735c8124d135399e",
   "date": "2026-05-28T21:00:20Z",
   "message": "test: Use GetScriptCacheEntry in txvalidationcache_tests\n\nUpdate cache-hit and cache-miss tests to call GetScriptCacheEntry\ndirectly instead of checking the pvChecks vector populated by\nCheckInputScripts. Both functions use the same caching logic\ninternally, and GetScriptCacheEntry is the more direct way to test\nwhether a transaction's scripts would need validation."
  },
  {
   "sha": "60bc921f97017bd5cc338d7d6412eaed0afad54f",
   "date": "2026-05-28T21:45:19Z",
   "message": "validation: Use checkqueue for all script checking in ConnectBlock\n\nInstead of having two code paths in ConnectBlock, one that collects\nscript checks for the checkqueue (multithreaded) and one that runs\nCheckInputScripts inline (single-threaded), just always use the\ncheckqueue. ConnectBlock now calls GetScriptCacheEntry directly and\nconstructs CScriptCheck objects to add to the queue regardless of the\nthread count.\n\nThis removes the HasThreads() check and the single-threaded special\ncase. The checkqueue already handles the case of having zero worker\nthreads by running checks on the calling thread.\n\nPreviously, script execution cache population during TestBlockValidity\n(fJustCheck=true) only worked in the single-threaded case, because\nCheckInputScripts guarded the cache insertion with !pvChecks. In the\nmultithreaded case, pvChecks was always set, so the cache was never\npopulated. This inconsistency has existed since the script execution\ncache was introduced. Now that ConnectBlock no longer uses\nCheckInputScripts, cache population is done explicitly after it is\nconfirmed that all script checks have passed. This means we populate\nthe cache in both single-threaded and multithreaded cases."
  },
  {
   "sha": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "date": "2026-05-28T21:45:41Z",
   "message": "refactor: Remove pvChecks parameter from CheckInputScripts\n\nSince ConnectBlock now constructs script checks directly via\nGetScriptCacheEntry, no caller passes a pvChecks vector to\nCheckInputScripts. Remove the parameter and the associated\ndead code paths."
  }
 ],
 "timeline": [
  {
   "t": "2025-05-20T20:57:04Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "Concept ACK. Anything to clean this up :)\n\nSee also #32317 which moves some of this functionality around similarly."
  },
  {
   "t": "2025-05-20T21:43:33Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "4262b75726e717f89f232867c87bc6af50de82a5"
  },
  {
   "t": "2025-05-20T21:49:56Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Cleaned up the comments so this should be ready for more detailed feedback, ~~but expect a rebase once https://github.com/bitcoin/bitcoin/pull/32467 is merged.~~"
  },
  {
   "t": "2025-05-20T21:54:14Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": null,
   "text": "I have been contemplating kicking this out (here and in `CheckInputScripts`) or even turning this into an assert because the callers already handle this everywhere. On the other hand it conceptually makes sense to return early if we know there is nothing to check because we don't expect any inputs. So I am still undecided on this one and happy to hear opinions."
  },
  {
   "t": "2025-05-20T22:12:27Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2025-05-21T10:28:43Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2025-05-22T18:29:24Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "Ready for rebase."
  },
  {
   "t": "2025-05-22T20:07:15Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "5dfd10c725bf44557ae06038ce54b4924ecc0fae"
  },
  {
   "t": "2025-05-22T20:21:11Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDone, ready for review :)"
  },
  {
   "t": "2025-05-22T21:26:34Z",
   "kind": "review_comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": null,
   "text": "I think this should go a little further. At this point, `CheckInputScripts` is essentially meant to be a mempool function and `PrepareInputScriptChecks` is for block validation, except for the weird edge-case  when we're single-threaded.\n\nI think I'd rather just see this go all the way and make the split purposeful. Something like:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex 1c81620889f..d9a447b460f 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -2723,19 +2723,18 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n             TxValidationState tx_state;\n-            // If parallel script checking is possible (worker threads are available) the checks are appended to a\n-            // vector without running them. The vector is then added to control which runs the checks asynchronously.\n-            // Otherwise, CheckInputScripts runs the checks on a single thread before returning.\n-            if (control) {\n-                std::vector<CScriptCheck> vChecks = PrepareInputScriptChecks(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n-                control->Add(std::move(vChecks));\n-            } else {\n-                if (!CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache)) {\n-                    // Any transaction validation failure in ConnectBlock is a block consensus failure\n-                    state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n-                                  tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n-                    break;\n+            // If parallel script checking is possible (worker threads are available), they are added to control which\n+            // which runs the checks asynchronously. Otherwise they are run here directly.\n+            std::vector<CScriptCheck> vChecks = PrepareInputScriptChecks(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n+            if (control) control->Add(std::move(vChecks));\n+            else {\n+                for (auto& check : vChecks) {\n+                    if (const auto& check_result = check()) {\n+                        state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf(\"mandatory-script-verify-flag-failed (%s)\", ScriptErrorString(check_result->first)), check_result->second);\n+                        break;\n+                    }\n                 }\n+                if (!state.IsValid()) break;\n             }\n         }\n\n```\n\nAn alternative would be to make `CCheckQueueControl` work with 0 worker threads (maybe it already does?) and just always use it. That would completely unify the approach here."
  },
  {
   "t": "2025-05-22T21:38:04Z",
   "kind": "review_comment",
   "who": "mzumsande",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": null,
   "text": "Just a rough idea, but did you consider the alternative approach of doing the `pvChecks` population not here (but in `ConnectBlock()` or a separate function), so that `CheckInputScripts` could also call `PrepareInputScriptChecks` and there would be no duplication of the coinbase check, cache check and `TxData` init?"
  },
  {
   "t": "2025-05-22T22:54:19Z",
   "kind": "review_comment",
   "who": "mzumsande",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103422435,
   "text": "I like that idea.\n\nI think it would be a small change in behaviour, because  if I read [this line](https://github.com/bitcoin/bitcoin/blob/2df824f4e62b6bc569044819cd64f66f3839ba13/src/validation.cpp#L2248) right it will add entries to the cache if\n1)`pvChecks` is not set (single-threaded)\n2) `cacheFullScriptStore` is set (i.e. if `ConnectBlock()` was called with `fJustCheck=True`, so currently only from `TestBlockValidity`).\nI wonder if the current behavior is intended - it seems very strange to me that mining rpcs would add entries to the script cache, but only in the `-par=1` case."
  },
  {
   "t": "2025-06-17T18:01:13Z",
   "kind": "review_comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103422435,
   "text": "@mzumsande I think it's very unlikely that that behavior is intended :)\n\nEdit: likely -> unlikely."
  },
  {
   "t": "2025-06-17T18:02:28Z",
   "kind": "review_comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103422435,
   "text": "Whooops, *Very unlikely."
  },
  {
   "t": "2025-06-19T22:00:13Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "edcc004c40439b58fa2244da13c6b7fa3d028ba5"
  },
  {
   "t": "2025-06-19T22:00:21Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYeah, that does work basically out of the box. I tested it with this minimal change on top of the changes here: https://github.com/fjahr/bitcoin/commit/320b9a967f88ece5b98779948c47493ad7717da2 However I don't favor that approach at the moment since there seems to be a lot of overhead when using checkqueue with a single thread and a lot of docs would need to be changed accordingly as well. Just logically you wouldn't think about using a queue when you just have a single thread, so while it would definitely simplify the code in `ConnectBlock`, I am not sure it's a bigger win than the other approaches discussed here, given that it may cause irritation in other places and a performance penalty. I am happy to be convinced of this approach though if I overlooked something.\n\nI have now pushed here an updated version that addresses both the suggestions from @theuni and @mzumsande ."
  },
  {
   "t": "2025-06-19T22:03:34Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103434017,
   "text": "I have implemented this at the cost of duplicating the cache entry calculation. I think it should be fine but I will try to run some benchmarks."
  },
  {
   "t": "2025-06-19T22:04:10Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103422435,
   "text": "Took the suggestion here."
  },
  {
   "t": "2025-06-20T09:34:52Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": null,
   "text": "\u201cthis transactions\u2019s input scripts\u201d -> \u201cthis transaction\u2019s input scripts\u201d [incorrect possessive form]\n    duplicate \u201cwhich\u201d in \u201ccontrol which which runs the checks asynchronously\u201d -> remove one \u201cwhich\u201d [typo duplication]"
  },
  {
   "t": "2025-06-20T12:59:30Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "6667ea17a9562237749027c808732b9ee0e09fb1"
  },
  {
   "t": "2025-06-20T12:59:43Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2158491253,
   "text": "both typos fixed"
  },
  {
   "t": "2025-08-13T13:00:05Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d"
  },
  {
   "t": "2025-10-09T16:36:59Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "ba32c2bbbc708b6eca8300844a5c0a6adf16583e"
  },
  {
   "t": "2025-10-11T18:01:36Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "02e1c01ed49d69be92ea71c0111aaf8faa2289b9"
  },
  {
   "t": "2025-10-11T20:44:55Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Silent merged conflict was resolved with the last push but the CI still fails with some problem fetching the qa-assets repo, seems to be a github issue since I also see it in master, retrying one more time with close/re-open to see if it works again now."
  },
  {
   "t": "2025-10-13T17:41:35Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Turning it off and on again finally worked :p"
  },
  {
   "t": "2025-11-30T21:19:41Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7c3fa071b4160c13c62f1e36d9fd3c1678a0b807",
   "in_reply_to": null,
   "text": "Nit: Could make these functions `static`."
  },
  {
   "t": "2025-11-30T21:37:18Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688",
   "in_reply_to": null,
   "text": "Why is this made a separate function?"
  },
  {
   "t": "2025-11-30T21:46:32Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "02e1c01ed49d69be92ea71c0111aaf8faa2289b9",
   "in_reply_to": null,
   "text": "Can you add braces here? I find different inlining styles for the same if-else block slightly confusing to read."
  },
  {
   "t": "2025-11-30T22:04:51Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Looking at this patch, I am asking myself why we're keeping the single threaded / no checkqueue case in the first place. This seems to introduce a vector of jobs for both cases now, so maybe we could just create a queue without any workers?"
  },
  {
   "t": "2026-02-01T14:26:22Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "7c3fa071b4160c13c62f1e36d9fd3c1678a0b807"
  },
  {
   "t": "2026-02-01T19:08:27Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Finally getting around to picking this up again.\n\n[quoted text omitted]\nCheckqueue can run without worker threads as well but I still expected that some changes would be needed. That doesn't seem to be the case, it seems to work well as is. I also expected that there would be some performance penalty due to the overhead within checkqueue. This also doesn't seem to be the case, at least not to a degree that is measurable by our connectblock benchmark. So I pushed this as an additional refactoring commit, which of course get's rid of a few lines of extra code. Let me know what you all think.\n\nEDIT: The last commit should probably be squashed into the one before it but I am leaving it as standalone for now until I have gotten some feedback on the new approach. Unless there are issues noted I will probably squash it with the next push."
  },
  {
   "t": "2026-02-13T17:24:01Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "7c3fa071b4160c13c62f1e36d9fd3c1678a0b807",
   "text": "Approach ACK\n\nThis seems like a nice cleanup, will look at it in detail later."
  },
  {
   "t": "2026-02-27T23:02:41Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688"
  },
  {
   "t": "2026-02-27T23:03:05Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "02e1c01ed49d69be92ea71c0111aaf8faa2289b9",
   "in_reply_to": 2574939490,
   "text": "This line is removed in the final commit. I have squashed that commit now as promised."
  },
  {
   "t": "2026-02-27T23:03:15Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688",
   "in_reply_to": 2574918633,
   "text": "I just found it nicer in terms of documentation and readability to extract this into a function. It isn't strictly necessary but it should make the actual behavior change easier to review IMO."
  },
  {
   "t": "2026-02-27T23:03:19Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7c3fa071b4160c13c62f1e36d9fd3c1678a0b807",
   "in_reply_to": 2574881133,
   "text": "Done"
  },
  {
   "t": "2026-03-05T10:15:43Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688",
   "in_reply_to": 2574918633,
   "text": "I also don't see the advantage of simply extracting it locally (incorrectly, since we need `AssertLockHeld` for the insertion as well), but I could get behind properly encapsulating the cache and exposing only the contains/insert methods, something like:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex 67130a31cf..3947d0dfde 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -2036,6 +2036,18 @@ ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, cons\n               approx_size_bytes >> 20, script_execution_cache_bytes >> 20, num_elems);\n }\n\n+bool ValidationCache::IsScriptValidated(const uint256& entry, bool erase) const\n+{\n+    AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n+    return m_script_execution_cache.contains(entry, erase);\n+}\n+\n+void ValidationCache::CacheScriptValidation(const uint256& entry)\n+{\n+    AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n+    m_script_execution_cache.insert(entry);\n+}\n+\n /**\n  * Check whether all of this transaction's input scripts succeed.\n  *\n@@ -2075,8 +2087,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n     uint256 hashCacheEntry;\n     CSHA256 hasher = validation_cache.ScriptExecutionCacheHasher();\n     hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n-    AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n-    if (validation_cache.m_script_execution_cache.contains(hashCacheEntry, !cacheFullScriptStore)) {\n+    if (validation_cache.IsScriptValidated(hashCacheEntry, !cacheFullScriptStore)) {\n         return true;\n     }\n\n@@ -2124,7 +2135,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n     if (cacheFullScriptStore && !pvChecks) {\n         // We executed all of the provided scripts, and were told to\n         // cache the result. Do so now.\n-        validation_cache.m_script_execution_cache.insert(hashCacheEntry);\n+        validation_cache.CacheScriptValidation(hashCacheEntry);\n     }\n\n     return true;\ndiff --git a/src/validation.h b/src/validation.h\nindex 482772c0d6..2bec47aecc 100644\n--- a/src/validation.h\n+++ b/src/validation.h\n@@ -373,8 +373,10 @@ private:\n     //! Pre-initialized hasher to avoid having to recreate it for every hash calculation.\n     CSHA256 m_script_execution_cache_hasher;\n\n-public:\n+    //! Cache for script verification results to avoid re-validation.\n     CuckooCache::cache<uint256, SignatureCacheHasher> m_script_execution_cache;\n+\n+public:\n     SignatureCache m_signature_cache;\n\n     ValidationCache(size_t script_execution_cache_bytes, size_t signature_cache_bytes);\n@@ -384,6 +386,9 @@ public:\n\n     //! Return a copy of the pre-initialized hasher.\n     CSHA256 ScriptExecutionCacheHasher() const { return m_script_execution_cache_hasher; }\n+\n+    bool IsScriptValidated(const uint256& entry, bool erase) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);\n+    void CacheScriptValidation(const uint256& entry) EXCLUSIVE_LOCKS_REQUIRED(cs_main);\n };\n\n /** Functions for validating blocks and updating the block tree */\n```\n\nGiven the sensitive nature of this area, I'd do it in a separate commit.\n[quoted text omitted]"
  },
  {
   "t": "2026-03-05T10:49:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe comment says this is about the `CuckooCache`, which we are inserting into later, so we cannot just move the lock assertion to the `contains` method only."
  },
  {
   "t": "2026-03-05T11:54:22Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "6b379531347eb6715ad8373a2311a6811b36f17e",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis new abstraction is doing multiple things (which makes it a bit hard to name):\n* asking `txdata` whether the payload will be accepted, to avoid calculating `GetSpentOutputs` needlessly\n* calculating it if it will be accepted\n* passing it to `txdata.Init`\n* asserting that everything makes sense regardless of previous actions\n\nMost of these make more sense at the call site. The only excessive calculation is the to-be-spent outputs calculation. We could extract only that (making sure no extra copies happen, of course), something like:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\n--- a/src/validation.cpp\t(revision d26ff1ff03da3531d1a6a28466784911ece28e0a)\n+++ b/src/validation.cpp\t(date 1772711510934)\n@@ -2048,6 +2048,18 @@\n     m_script_execution_cache.insert(entry);\n }\n\n+static std::vector<CTxOut> GetSpentOutputs(const CTransaction& tx, const CCoinsViewCache& inputs)\n+{\n+    std::vector<CTxOut> spent_outputs;\n+    spent_outputs.reserve(tx.vin.size());\n+    for (const auto& txin : tx.vin) {\n+        const Coin& coin = inputs.AccessCoin(txin.prevout);\n+        assert(!coin.IsSpent());\n+        spent_outputs.emplace_back(coin.out);\n+    }\n+    return spent_outputs;\n+}\n+\n /**\n  * Check whether all of this transaction's input scripts succeed.\n  *\n@@ -2091,18 +2103,7 @@\n         return true;\n     }\n\n-    if (!txdata.m_spent_outputs_ready) {\n-        std::vector<CTxOut> spent_outputs;\n-        spent_outputs.reserve(tx.vin.size());\n-\n-        for (const auto& txin : tx.vin) {\n-            const COutPoint& prevout = txin.prevout;\n-            const Coin& coin = inputs.AccessCoin(prevout);\n-            assert(!coin.IsSpent());\n-            spent_outputs.emplace_back(coin.out);\n-        }\n-        txdata.Init(tx, std::move(spent_outputs));\n-    }\n+    if (!txdata.m_spent_outputs_ready) txdata.Init(tx, GetSpentOutputs(tx, inputs));\n     assert(txdata.m_spent_outputs.size() == tx.vin.size());\n\n     for (unsigned int i = 0; i < tx.vin.size(); i++) {\n```\n\nhttps://godbolt.org/z/jj3eGq8eY indicates that `GetSpentOutputs` allocates once via `reserve`, and `Init`'s `&&` parameter steals the buffer pointer directly, so it's a pure refactor."
  },
  {
   "t": "2026-03-05T11:58:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe commit message does not explain the motivation behind this change, or the extra work that we are doing now.\n\nnit: formatting of the new code is off"
  },
  {
   "t": "2026-03-05T12:00:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe commit forgot to mention that we're recalculating the hash now, instead of only doing it once. I don't consider that a `refactor`, like the commit title suggests."
  },
  {
   "t": "2026-03-05T12:03:55Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4fd17753a0108e75bd1dde393d23267a61159e0d",
   "in_reply_to": 2103434017,
   "text": "This isn't a minor issue; this should be mentioned in the commit message and PR description."
  },
  {
   "t": "2026-03-05T12:17:49Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe method name describes **when** this is executed, not **what** it does.\nIt returns a bool, which forces us to recompute the cache key. What if we adjust it to return the cache key instead, something like:\n```C++\ndiff --git a/src/validation.cpp b/src/validation.cpp\n--- a/src/validation.cpp\t(revision 4b2f347fc0495a9e4ddb9e47ec066f3b82a20d30)\n+++ b/src/validation.cpp\t(date 1772727309644)\n@@ -2060,6 +2060,36 @@\n     return spent_outputs;\n }\n\n+/** Perform pre-checks for CheckInputScripts: skip coinbase, check the script\n+ *  execution cache, reserve space for script checks, and initialize txdata.\n+ *  Returns nullopt if the transaction is already validated (caller should\n+ *  return true), or the cache entry hash needed to store results later. */\n+static std::optional<uint256> LookupScriptValidation(\n+    const CTransaction& tx,\n+    const CCoinsViewCache& inputs, script_verify_flags flags,\n+    bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n+    const ValidationCache& validation_cache) EXCLUSIVE_LOCKS_REQUIRED(cs_main)\n+{\n+    if (tx.IsCoinBase()) return std::nullopt;\n+\n+    // First check if script executions have been cached with the same\n+    // flags. Note that this assumes that the inputs provided are\n+    // correct (ie that the transaction hash which is in tx's prevouts\n+    // properly commits to the scriptPubKey in the inputs view of that\n+    // transaction).\n+    uint256 hashCacheEntry;\n+    CSHA256 hasher = validation_cache.ScriptExecutionCacheHasher();\n+    hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n+    if (validation_cache.IsScriptValidated(hashCacheEntry, !cacheFullScriptStore)) {\n+        return std::nullopt;\n+    }\n+\n+    if (!txdata.m_spent_outputs_ready) txdata.Init(tx, GetSpentOutputs(tx, inputs));\n+    assert(txdata.m_spent_outputs.size() == tx.vin.size());\n+\n+    return hashCacheEntry;\n+}\n+\n /**\n  * Check whether all of this transaction's input scripts succeed.\n  *\n@@ -2085,22 +2115,8 @@\n                        ValidationCache& validation_cache,\n                        std::vector<CScriptCheck>* pvChecks)\n {\n-    if (tx.IsCoinBase()) return true;\n-\n-    // First check if script executions have been cached with the same\n-    // flags. Note that this assumes that the inputs provided are\n-    // correct (ie that the transaction hash which is in tx's prevouts\n-    // properly commits to the scriptPubKey in the inputs view of that\n-    // transaction).\n-    uint256 hashCacheEntry;\n-    CSHA256 hasher = validation_cache.ScriptExecutionCacheHasher();\n-    hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n-    if (validation_cache.IsScriptValidated(hashCacheEntry, !cacheFullScriptStore)) {\n-        return true;\n-    }\n-\n-    if (!txdata.m_spent_outputs_ready) txdata.Init(tx, GetSpentOutputs(tx, inputs));\n-    assert(txdata.m_spent_outputs.size() == tx.vin.size());\n+    const auto hash_cache_entry{LookupScriptValidation(tx, inputs, flags, cacheFullScriptStore, txdata, validation_cache)};\n+    if (!hash_cache_entry) return true;\n\n     for (unsigned int i = 0; i < tx.vin.size(); i++) {\n\n@@ -2133,7 +2149,7 @@\n     if (cacheFullScriptStore && !pvChecks) {\n         // We executed all of the provided scripts, and were told to\n         // cache the result. Do so now.\n-        validation_cache.CacheScriptValidation(hashCacheEntry);\n+        validation_cache.CacheScriptValidation(*hash_cache_entry);\n     }\n\n     return true;\n```"
  },
  {
   "t": "2026-03-05T13:42:56Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWe could avoid recomputing the the hash:\n```suggestion\n    const auto hash_cache_entry{LookupScriptValidation(tx, inputs, flags, cacheFullScriptStore, txdata, validation_cache, pvChecks)};\n    if (!hash_cache_entry) return true;\n```"
  },
  {
   "t": "2026-03-05T13:45:04Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/txvalidationcache_tests.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCould we do the test updates in a separate change? It doesn't seem strictly related to the refactor in `src/validation.cpp` (which is risky enough)."
  },
  {
   "t": "2026-03-05T15:08:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis change is too big and contains too many subtle assumptions. For example, this seems to be dead code: it will always return `true`, since the only invalid path is when `pvChecks` is not provided.\nWe should clarify that in smaller steps and iterate toward the unification in smaller, trivial steps, e.g. something like:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\n--- a/src/validation.cpp\t(revision c009adccee7a268ec844e29bf7070b543a26ae90)\n+++ b/src/validation.cpp\t(date 1772723088401)\n@@ -2600,18 +2600,14 @@\n         if (!tx.IsCoinBase() && fScriptChecks)\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n-            bool tx_ok;\n             TxValidationState tx_state;\n-            // If CheckInputScripts is called with a pointer to a checks vector, the resulting checks are appended to it. In that case\n-            // they need to be added to control which runs them asynchronously. Otherwise, CheckInputScripts runs the checks before returning.\n             if (control) {\n+                // CheckInputScripts always returns true when pvChecks is provided\n+                // (checks are collected for deferred execution, not executed inline).\n                 std::vector<CScriptCheck> vChecks;\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n-                if (tx_ok) control->Add(std::move(vChecks));\n-            } else {\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n-            }\n-            if (!tx_ok) {\n+                Assume(CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks));\n+                control->Add(std::move(vChecks));\n+            } else if (!CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache)) {\n                 // Any transaction validation failure in ConnectBlock is a block consensus failure\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n                               tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n```"
  },
  {
   "t": "2026-03-05T16:12:53Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWhat's the explanation for this removal? There are too many changes in this commit. If you want to move it closer to usage, we could do:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\n--- a/src/validation.cpp\t(revision b3b6dc9bb7165bbfd21cb1af636cdfc467de7f01)\n+++ b/src/validation.cpp\t(revision 4b2f347fc0495a9e4ddb9e47ec066f3b82a20d30)\n@@ -2087,10 +2087,6 @@\n {\n     if (tx.IsCoinBase()) return true;\n\n-    if (pvChecks) {\n-        pvChecks->reserve(tx.vin.size());\n-    }\n-\n     // First check if script executions have been cached with the same\n     // flags. Note that this assumes that the inputs provided are\n     // correct (ie that the transaction hash which is in tx's prevouts\n@@ -2117,6 +2113,7 @@\n         // Verify signature\n         CScriptCheck check(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &txdata);\n         if (pvChecks) {\n+            pvChecks->reserve(tx.vin.size());\n             pvChecks->emplace_back(std::move(check));\n         } else if (auto result = check(); result.has_value()) {\n             // Tx failures never trigger disconnections/bans.\n\n```\nin a separate commit"
  },
  {
   "t": "2026-03-05T16:52:41Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWe're mixing refactoring (separation of the two intertwined use cases) with a behavior change in a consensus area. I have a really hard time trusting this completely.\n\nIt would help if we could separate \"validation\" from \"refactor\" here. We could split the collection use case from validation by duplicating `CheckInputScripts` into a `CollectScriptChecks`, which creates the vector, fills it, and returns the values, while making input script validation ignore the vector. The two algorithms would already be very different (probably already achieving the main goal of this change without introducing any potentially dangerous unification, which we can still do in a follow-up if needed). This is what that would look like:\n```patch\ndiff --git a/src/validation.cpp b/src/validation.cpp\n--- a/src/validation.cpp\t(revision 87fcd645598d186bd1e4f68d5aca275dc7815fda)\n+++ b/src/validation.cpp\t(date 1772729378036)\n@@ -137,11 +137,17 @@\n     return m_chain.Genesis();\n }\n\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore,\n-                       bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n-                       ValidationCache& validation_cache,\n-                       std::vector<CScriptCheck>* pvChecks = nullptr)\n+void CollectScriptChecks(const CTransaction& tx,\n+                         const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore,\n+                         bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n+                         ValidationCache& validation_cache,\n+                         std::vector<CScriptCheck>& pvChecks)\n+                         EXCLUSIVE_LOCKS_REQUIRED(cs_main);\n+\n+bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore,\n+                       bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n+                       ValidationCache& validation_cache)\n                        EXCLUSIVE_LOCKS_REQUIRED(cs_main);\n\n bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx)\n@@ -2060,8 +2066,8 @@\n     return spent_outputs;\n }\n\n-/** Perform pre-checks for CheckInputScripts: skip coinbase, check the script\n- *  execution cache, reserve space for script checks, and initialize txdata.\n+/** Perform pre-checks for script validation: skip coinbase, check the script\n+ *  execution cache, and initialize txdata.\n  *  Returns nullopt if the transaction is already validated (caller should\n  *  return true), or the cache entry hash needed to store results later. */\n static std::optional<uint256> LookupScriptValidation(\n@@ -2090,16 +2096,28 @@\n     return hashCacheEntry;\n }\n\n+/** Collect script checks for all inputs without executing them.\n+ *  Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp */\n+void CollectScriptChecks(const CTransaction& tx,\n+                         const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore,\n+                         bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n+                         ValidationCache& validation_cache,\n+                         std::vector<CScriptCheck>& checks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)\n+{\n+    if (LookupScriptValidation(tx, inputs, flags, cacheFullScriptStore, txdata, validation_cache)) {\n+        checks.reserve(tx.vin.size());\n+        for (unsigned int i = 0; i < tx.vin.size(); i++) {\n+            checks.emplace_back(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &txdata);\n+        }\n+    }\n+}\n+\n /**\n  * Check whether all of this transaction's input scripts succeed.\n  *\n  * This involves ECDSA signature checks so can be computationally intensive. This function should\n  * only be called after the cheap sanity checks in CheckTxInputs passed.\n  *\n- * If pvChecks is not nullptr, script checks are pushed onto it instead of being performed inline. Any\n- * script checks which are not necessary (eg due to script execution cache hits) are, obviously,\n- * not pushed onto pvChecks/run.\n- *\n  * Setting cacheSigStore/cacheFullScriptStore to false will remove elements from the corresponding cache\n  * which are matched. This is useful for checking blocks where we will likely never need the cache\n  * entry again.\n@@ -2112,8 +2130,7 @@\n bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n                        const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n-                       ValidationCache& validation_cache,\n-                       std::vector<CScriptCheck>* pvChecks)\n+                       ValidationCache& validation_cache)\n {\n     const auto hash_cache_entry{LookupScriptValidation(tx, inputs, flags, cacheFullScriptStore, txdata, validation_cache)};\n     if (!hash_cache_entry) return true;\n@@ -2128,10 +2145,7 @@\n\n         // Verify signature\n         CScriptCheck check(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &txdata);\n-        if (pvChecks) {\n-            pvChecks->reserve(tx.vin.size());\n-            pvChecks->emplace_back(std::move(check));\n-        } else if (auto result = check(); result.has_value()) {\n+        if (auto result = check()) {\n             // Tx failures never trigger disconnections/bans.\n             // This is so that network splits aren't triggered\n             // either due to non-consensus relay policies (such as\n@@ -2146,7 +2160,7 @@\n         }\n     }\n\n-    if (cacheFullScriptStore && !pvChecks) {\n+    if (cacheFullScriptStore) {\n         // We executed all of the provided scripts, and were told to\n         // cache the result. Do so now.\n         validation_cache.CacheScriptValidation(*hash_cache_entry);\n@@ -2596,18 +2610,20 @@\n         if (!tx.IsCoinBase() && fScriptChecks)\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n-            TxValidationState tx_state;\n             if (control) {\n                 // CheckInputScripts always returns true when pvChecks is provided\n                 // (checks are collected for deferred execution, not executed inline).\n                 std::vector<CScriptCheck> vChecks;\n-                Assume(CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks));\n+                CollectScriptChecks(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, vChecks);\n                 control->Add(std::move(vChecks));\n-            } else if (!CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache)) {\n-                // Any transaction validation failure in ConnectBlock is a block consensus failure\n-                state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n-                              tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n-                break;\n+            } else {\n+                TxValidationState tx_state;\n+                if (!CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache)) {\n+                    // Any transaction validation failure in ConnectBlock is a block consensus failure\n+                    state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n+                                  tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n+                    break;\n+                }\n             }\n         }\n```"
  },
  {
   "t": "2026-03-05T17:06:38Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "CHANGES_REQUESTED",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688",
   "text": "Concept ACK. The recent consensus bug in the BIP110 soft fork proposal in this exact area indicates the current code is hard to understand.\nApproach NACK. The recent consensus bug in the BIP110 soft fork proposal in this exact area indicates we need to be extra careful, which isn't the case in this PR yet.\n\nI left a ton of suggestions as I recreated the change locally, experimenting with a few different implementations. We should definitely split the two intertwined functionalities, but not in the same PR that also modifies the behavior. There are too many unexplained changes here that I strongly object to.\n\nLet's untangle the two functionalities first. That refactor is verbose but quite safe. If it gains momentum and gets merged, we can build on it to unify the two even more, if needed.\n\nEdit:\nI measured multithreaded script verification, and I can confirm that it doesn't introduce a slowdown\nBenchmark results\n\n```python\nfor DBCACHE in 5000; do \\\n    COMMITS=\"043301a537fdad469ef83ee3d1033404a0cefe16 7726a6a6c3e378e538ecb4b5bfd82c2880cc0688\"; \\\n    STOP=936639; CC=gcc; CXX=g++; \\\n    BASE_DIR=\"/mnt/my_storage\"; DATA_DIR=\"$BASE_DIR/BitcoinData\"; LOG_DIR=\"$BASE_DIR/logs\"; \\\n    (echo \"\"; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \\\n    (echo \"\" && echo \"$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)\"; echo \"\") && \\\n    hyperfine \\\n    --sort command \\\n    --runs 1 \\\n    --export-json \"$BASE_DIR/rdx-$(sed -E 's/(\\w{8})\\w+ ?/\\1-/g;s/-$//'<<<\"$COMMITS\")-$STOP-$DBCACHE-$CC.json\" \\\n    --parameter-list COMMIT ${COMMITS// /,} \\\n    --prepare \"killall -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \\\n      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \\\n      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log\" \\\n    --conclude \"killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q \"$(printf %.12s {COMMIT})\"; \\\n                cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log\" \\\n    \"COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0\";\ndone\n\n043301a537 refactor: Extract cache check from CheckInputScripts\n7726a6a6c3 validation, refactor: Remove single threaded special case\n\n2026-03-05 | reindex-chainstate | 936639 blocks | dbcache 5000 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD\n\nBenchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 (COMMIT = 043301a537fdad469ef83ee3d1033404a0cefe16)\n  Time (abs \u2261):        37023.077 s               [User: 389375.279 s, System: 1125.389 s]\n\nBenchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 (COMMIT = 7726a6a6c3e378e538ecb4b5bfd82c2880cc0688)\n  Time (abs \u2261):        37195.482 s               [User: 388819.276 s, System: 1134.977 s]\n\nRelative speed comparison\n        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 (COMMIT = 043301a537fdad469ef83ee3d1033404a0cefe16)\n        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 (COMMIT = 7726a6a6c3e378e538ecb4b5bfd82c2880cc0688)\n```\n\nI will measure single-threaded version next."
  },
  {
   "t": "2026-03-11T16:37:21Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "1063f2cfce1117ea230a2affd4010ef91d96d300"
  },
  {
   "t": "2026-03-11T16:38:32Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": 2891176328,
   "text": "This preserves the single-threaded special case and looks more like an earlier version of the code that I proposed here. Based on feedback from early review I have eliminated that and [I was surprised](https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-3831778712) that it worked better than I had expected so I am happy I adopted the current approach here. The whole motivation of this PR is to unify the paths through checkqueue, which simplifies the code and the benchmarks confirm has no performance cost. So I am continuing with the current approach here."
  },
  {
   "t": "2026-03-11T16:38:38Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": 2890997868,
   "text": "The `reserve` is removed because in the new structure, `PreCheckInputScripts` handles the early-return logic and the vector construction happens in `ConnectBlock` directly with its own reserve (as `vChecks`). The suggestion to move reserve inside the loop would call it each iteration of the loop unnecessarily."
  },
  {
   "t": "2026-03-11T16:38:49Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": 2890576862,
   "text": "I'm confused by this comment, there may be dead code existing in some commit but it's only transient and the commit you are mentioning in the beginning of your comment is removing this code. So I am not sure where you would like me to apply what change."
  },
  {
   "t": "2026-03-11T16:39:50Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/test/txvalidationcache_tests.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": 2890090345,
   "text": "I moved them into a separate commit although I think they would have been fine to leave in the same commit as well. The tests are simplified significantly because this change gives us a cleaner interface to what the test actually want to check. And I don't think it added significant overhead."
  },
  {
   "t": "2026-03-11T16:39:56Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": 2890079614,
   "text": "Addressed as mentioned in other comments."
  },
  {
   "t": "2026-03-11T16:40:14Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": 2889644265,
   "text": "I have changed this function to return the hash so we don't have to recalculate it. I am not sure about the naming change to `LookupScriptValidation`, that doesn't seem to communicate the checking part well. I have thought about a better name that captures this function and I went with `PrepareScriptChecks`."
  },
  {
   "t": "2026-03-11T16:40:23Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": 2889552352,
   "text": "Addressed as the newest push doesn't recalculate the hash anymore."
  },
  {
   "t": "2026-03-11T16:40:37Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "4e851e1745104e01ac46c5a3cc5be8cec35e8b89",
   "in_reply_to": 2889544298,
   "text": "I have fixed the formatting and re-worked/extended the commit messages overall."
  },
  {
   "t": "2026-03-11T16:40:45Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "6b379531347eb6715ad8373a2311a6811b36f17e",
   "in_reply_to": 2889527083,
   "text": "`EnsureTxData` has a clear contract: it ensures that `txdata` is initialized. Your alternative pushes the m_spent_outputs_ready check and assertion back to every call site, which is what I'm trying to avoid. You say \"most of these make more sense at the call site\" but you don't say why. Can you elaborate? However, I don't feel too strongly about it and I'll adopt `GetSpentOutputs` as you suggest if other reviewers prefer that style."
  },
  {
   "t": "2026-03-11T16:40:52Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": 2889217755,
   "text": "This code was replaced with your suggestion, so this is now obsolete."
  },
  {
   "t": "2026-03-11T16:41:00Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "7726a6a6c3e378e538ecb4b5bfd82c2880cc0688",
   "in_reply_to": 2574918633,
   "text": "I agree that it is nicer to this in `ValidationCache`, I have taken these changes as a separate commit with a minor edit (the `const` on `IsScriptValidated` is misleading if erase is set to `true`) and removed the one that added `IsScriptCached` as a free function."
  },
  {
   "t": "2026-03-11T16:41:21Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Thanks for the review @l0rinc , I hope I have addressed all your comments either with a response here or with changes in the code.\n\nI have also tried to split up the commits a bit more where it made sense to me. Overall I think this is still a pretty concise and managable change that doesn't need to be split across multiple PRs. The refactorings are largely move-only and the behavior change is better isolated now.\n\n[quoted text omitted]\nNot sure what the relevance of this is. There will always be forks with bugs because they don't have the same standard of review as we have here. This isn't new and I don't think that should dictate our own processes. But anyway, I think all of the previous reviewers here are aware of the highly sensitive nature of this code and I would not have worked a refactoring of this code unless there had intested signaled in this even before opening. it is also not clear from your comments what it actually is that you are suggesting that is more careful."
  },
  {
   "t": "2026-03-13T12:42:39Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "A few of my concerns here were addressed, so I'm fine with merging the first commit only. ACK 5d6cbfff18e756fe4a31be6336eb2270a46ee4b2\n\nThe rest of the commits still combine a consensus-sensitive behavior change with a structural refactor, so the risk seems significant. This is why I mentioned the BIP110 consensus bug: to highlight that this isn't just theoretical, it's dangerous territory, and yet it's treated lightly, even after a detailed review. I think we should split the behavior change from the refactor into separate PRs.\n\nWe can get most of the advantage (intertwined, complicated script validation doing different things in the same method) simply by separating the two usages into two methods that reuse the components, which isn't risky and would solve the confusing and objectively hard-to-understand logic.\nAnd even for the separation part, we would need proper tests and reproducers and more accurate commit messages (`refactor` shouldn't change user behavior, and even the PR title still claims that).\n\nLastly, you aren't backing your claims with reproducible data. It seems hand-wavy. For example, what makes you say this doesn't introduce any regression? I've been trying to measure the `-assumevalid=0 -par=1` case for almost a week now. `master` finished in 60 hours; this branch is still running...\n\nre-NACK"
  },
  {
   "t": "2026-03-14T15:59:13Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThanks. But I don't think making this a series of one-commit PRs is a good path forward.\n\n[quoted text omitted]\nPlease stop claiming this is being \"treated lightly\". That is not true, and repeating it does not make it so. Again, the BIP110 comparison is not relevant here. Bringing in an unrelated bug from a fork proposal does not strengthen your argument. It just escalates the rhetoric.\n\nI don't agree that splitting a PR is safer by default. Most of the refactoring here are simple extractions moving unchanged code around and are thus relatively easy to review. The behavior changing part is already isolated more, as you had requested. Splitting the PR into separate PRs makes it possible for the refactoing parts and the actual behavior change to be reviewed by different people at different times, which makes it easier to miss issues introduced by the combination. So I would say that, depending on the scenario, splitting can also be less safe.\n\nIn fact I have been criticized for splitting a PR prematurely just earlier this week. So I would need more reviewers to share your view that I should indeed split the PR.\n\n[quoted text omitted]\nAs I have said before, I don't agree that it is a better approach. Keeping separate paths around longer is not obviously safer. It keeps more duplication and more room for subtle drift between paths. The point of this change is to unify the logic, not to preserve two partially disentangled variants of it, and that approach has already received positive review.\n\nAlso, \"objectively hard-to-understand\" is overstated. It is fine to say you prefer a different decomposition. It is not fine to present that preference as if it were self-evidently superior. If there are objective criteria by which you can proof this claim in an measurable, verifiable way, please name them.\n\n[quoted text omitted]\nThe behavior changing part is explicitly called out in the description of the commit that introduces it. So again, please stop implying that this is being hidden. It is not. If you think a specific test or reproducer is missing, please point to it directly, otherwise this feedback is not helpful at all.\n\n[quoted text omitted]\n\"hand-wavy\" is another unfair characterization. Nowhere in this thread have I talked about regressions, what are you referring to exactly? All I have said is that I have not seen a performance hit when running a specific benchmark. And then you yourself said you had done your own measurements and could confirm there was no slowdown. But now suddenly your objection has shifted to a specific `-assumevalid=0 -par=1` case that you are still measuring but never mentioned before. That is fine as something to investigate, but it is not a basis for claiming that my statements were careless or unsupported.\n\nIf you get reproducible results that provide new insights, please share them. Until then, I would appreciate sticking to concrete technical points instead of exaggerations, repeated insinuations and needlessly inflammatory language. Open source collaboration can not work if we try to shut people down that don't follow every single suggestion. We should rather try to help each other with the goal to make progress. Progress is already hard enough in this project as it is."
  },
  {
   "t": "2026-03-15T23:07:10Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nA week later and this is still running against your \"refactor\""
  },
  {
   "t": "2026-03-17T15:16:10Z",
   "kind": "comment",
   "who": "darosior",
   "assoc": "MEMBER",
   "text": "Concept ACK.\n\nIn commit 92a9aabe784aa67e715c099834fed6d062582f56 you state:\n[quoted text omitted]\n(see TestBlockValidity), successful script results were added to the\nexecution cache. This no longer happens, matching the existing\nmultithreaded behavior. The caching there was likely unintended.\n\nI think this is incorrect. Script execution caching was introduced in commit b5fea8d0ccc8117644a4ea11256bc531b60fd9a3, it makes TBV populate this cache (likewise the pre-existing sigcache). This makes sense and i see no reason to believe this behaviour was unintended. On the contrary, the commit also updates a comment to explicitly mention TBV, and i would presume the author was well-aware.\n\nBesides, i think this is a regression to remove script execution caching from TBV. This introduces additional delay at block submission time, doing work that was already performed at block template generation time.\n\n~~Finally, i think this introduces a bug in that TBV will erase the entries that were cached at mempool-verification time, further delaying block submission for the lucky miner, and increasing validation time for all other miners.~~ Actually i think that last part is incorrect, because TBV will set `cacheFullScriptStore` to `true` in calling `PrepareScriptChecks`, which will lookup the cache with `erase` set to `false`."
  },
  {
   "t": "2026-03-17T18:14:47Z",
   "kind": "comment",
   "who": "darosior",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nHmm but TBV would only help here insofar as it would re-cache whatever would have been previously cached by the mempool and then overridden, which could be negligible."
  },
  {
   "t": "2026-03-20T05:55:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31",
   "in_reply_to": 2891176328,
   "text": "I pushed a narrower alternative of this PR, keeping as much as I could, as long as it's still a pure refactor without any observable behavior change (+ tests for the two separate behaviors), see: https://github.com/bitcoin/bitcoin/pull/34875"
  },
  {
   "t": "2026-03-22T07:30:10Z",
   "kind": "comment",
   "who": "svanstaa",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nHi,\njust ran a quick check for single-thread, the times are the same within noise, with the change being marginally faster. Will test again with more blocks. Are your results in for the full run already?\n\n2026-03-21 | reindex-chainstate | par=1 | 300000 blocks | dbcache 5000 | Fractal | x86_64 | AMD Ryzen 9 9950X3D 16-Core Processor | 32 cores | 59Gi RAM | SSD\n\nBenchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=300000 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = b6891931f7)\n  Time (abs \u2261):        2823.900 s               [User: 2753.878 s, System: 9.790 s]\n\nBenchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=300000 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 92a9aabe78)\n  Time (abs \u2261):        2782.771 s               [User: 2773.661 s, System: 7.039 s]\n\nRelative speed comparison\n        1.01          COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=300000 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = b6891931f7)\n        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=300000 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 92a9aabe78)"
  },
  {
   "t": "2026-03-23T16:50:05Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "23ac5f3f48d00a2087f9676609eb2e4b48813235"
  },
  {
   "t": "2026-03-23T16:50:51Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThank you, yeah, you are right, I misunderstood @theuni 's [comment](https://github.com/bitcoin/bitcoin/pull/32575#discussion_r2152860968) of \"likely unintended\" slightly and went in the wrong direction here without fully thinking it through and checking the history. I have now re-introduced caching in the same commit and fixed the commit message accordingly.\n\n[quoted text omitted]\nRight, I think for most standard scenarios there will be little to no benefit from caching here. But the cost is also minimal and I suspect there are some more rarer use-cases where this can still be beneficial. As we know not all transactions are coming through the mempool and also not all fees may be paid from within the transaction. Not something we focus on optimizing for usually but these are just examples for potential scenarios where it might matter."
  },
  {
   "t": "2026-04-05T16:36:11Z",
   "kind": "comment",
   "who": "svanstaa",
   "assoc": "CONTRIBUTOR",
   "text": "Results are in for the full run. No measurable reduction of speed after the change. Benchmark-wise, the PR is good.\n\n2026-03-26 | reindex-chainstate | par=1 | 936639 blocks | dbcache 5000 | Fractal | x86_64 | AMD Ryzen 9 9950X3D 16-Core Processor | 32 cores | 59Gi RAM | SSD\n\nBenchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = b6891931f7)\n  Time (abs \u2261):        132546.275 s               [User: 128543.250 s, System: 505.881 s]\n\nBenchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 92a9aabe78)\n  Time (abs \u2261):        132469.850 s               [User: 128467.273 s, System: 503.243 s]\n\nRelative speed comparison\n        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = b6891931f7)\n        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/home/sebastian/.bitcoin -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 92a9aabe78)\n\nadditional full benchmark run data\n\nfor DBCACHE in 5000; do \\\n    COMMITS=\"b6891931f7 92a9aabe78\"; \\\n    STOP=936639; CC=gcc; CXX=g++; \\\n    BASE_DIR=\"$HOME/.bitcoin\"; DATA_DIR=\"$BASE_DIR\"; LOG_DIR=\"$HOME/bench-logs\"; \\\n    mkdir -p $LOG_DIR; \\\n    (echo \"\"; for c in $COMMITS; do git log -1 --pretty='%h %s' $c || exit 1; done) && \\\n    (echo \"\" && echo \"$(date -I) | reindex-chainstate | par=1 | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | SSD\"; echo \"\") && \\\n    hyperfine \\\n    --sort command \\\n    --runs 1 \\\n    --export-json \"$LOG_DIR/rdx-par1-$STOP-$DBCACHE-$CC.json\" \\\n    --parameter-list COMMIT ${COMMITS// /,} \\\n    --prepare \"killall -9 bitcoind 2>/dev/null; sleep 3; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \\\n      cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && make -C build bitcoind -j$(nproc) && \\\n      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 10; rm -f $DATA_DIR/debug.log\" \\\n    --conclude \"killall bitcoind 2>/dev/null || true; sleep 5; grep -q 'height=$STOP' $DATA_DIR/debug.log; \\\n                cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-par1-$(date +%s).log\" \\\n    \"COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1\"; \\\ndone\n\nb6891931f7 refactor: Extract PrepareScriptChecks from CheckInputScripts\n92a9aabe78 validation: Use checkqueue for all script checking in ConnectBlock"
  },
  {
   "t": "2026-04-06T10:20:39Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThanks for checking. I ran it again and it crashed my server again. It's possible it's unrelated to this PR, will retry on a different machine, maybe the server is toast. Or maybe there's a race condition.\nDoesn't really matter, my main concerns are still:\n* The PR title still says \"refactor\" but commit d84cbf4b5366d4802f973f99e65c903daad80923 unifies the single-threaded and multithreaded paths, which is a behavior change in consensus-critical code. I still have [a hard time trusting this completely](https://github.com/bitcoin/bitcoin/pull/32575#discussion_r2891176328), I think we should do the refactor and the behavior change [separately](https://github.com/bitcoin/bitcoin/pull/34875).\n* The back-and-forth around cache population (e.g. darosior's [correction](https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-4075771363)) illustrates exactly why structural refactoring and behavioral changes shouldn't be done in the same PR for consensus critical code.\n* Missing upfront test coverage to demonstrate no behavior change - see my alternative which [adds thread-mode tests first](https://github.com/bitcoin/bitcoin/pull/34875/commits/26715c2b887e) before any refactoring, so regressions are caught immediately. I'd be happy to close that PR if you decide to do only the refactoring here, but I don't like the behavior changes here.\n\nEdit:\nI managed to run `-par=1` on a different server, it has very similar performance. My objections still stand, see above.\n\nBefore\n\n```bash\nfor DBCACHE in 5000; do \\\n    COMMITS=\"544c15ff4e22e0271f91341b446ad84b6f4fd6c3\"; \\\n    STOP=936639; CC=gcc; CXX=g++; \\\n    BASE_DIR=\"/mnt/my_storage\"; DATA_DIR=\"$BASE_DIR/BitcoinData\"; LOG_DIR=\"$BASE_DIR/logs\"; \\\n    (echo \"\"; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \\\n    (echo \"\" && echo \"$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)\"; echo \"\") && \\\n    hyperfine \\\n    --sort command \\\n    --runs 1 \\\n    --export-json \"$BASE_DIR/rdx-$(sed -E 's/(\\w{8})\\w+ ?/\\1-/g;s/-$//'<<<\"$COMMITS\")-$STOP-$DBCACHE-$CC.json\" \\\n    --parameter-list COMMIT ${COMMITS// /,} \\\n    --prepare \"killall -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \\\n      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \\\n      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log\" \\\n    --conclude \"killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q \"$(printf %.12s {COMMIT})\"; \\\n                cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log\" \\\n    \"COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1\";\ndone\n\n544c15ff4e Merge bitcoin/bitcoin#34759: walletdb: hash pubkey/privkey in one shot to avoid leaking secret data\n\n2026-04-10 | reindex-chainstate | 936639 blocks | dbcache 5000 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD\n\nBenchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 544c15ff4e22e0271f91341b446ad84b6f4fd6c3)\n  Time (abs \u2261):        223862.666 s               [User: 220249.873 s, System: 1347.124 s]\n```\n\nAfter\n\n```bash\nfor DBCACHE in 5000; do \\\n    COMMITS=\"23ac5f3f48d00a2087f9676609eb2e4b48813235\"; \\\n    STOP=936639; CC=gcc; CXX=g++; \\\n    BASE_DIR=\"/mnt/my_storage\"; DATA_DIR=\"$BASE_DIR/BitcoinData\"; LOG_DIR=\"$BASE_DIR/logs\"; \\\n    (echo \"\"; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \\\n    (echo \"\" && echo \"$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)\"; echo \"\") && \\\n    hyperfine \\\n    --sort command \\\n    --runs 1 \\\n    --export-json \"$BASE_DIR/rdx-$(sed -E 's/(\\w{8})\\w+ ?/\\1-/g;s/-$//'<<<\"$COMMITS\")-$STOP-$DBCACHE-$CC.json\" \\\n    --parameter-list COMMIT ${COMMITS// /,} \\\n    --prepare \"killall -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \\\n      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \\\n      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log\" \\\n    --conclude \"killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q \"$(printf %.12s {COMMIT})\"; \\\n                cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log\" \\\n    \"COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1\";\ndone\n\n23ac5f3f48 refactor: Remove pvChecks parameter from CheckInputScripts\n\n2026-04-07 | reindex-chainstate | 936639 blocks | dbcache 5000 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD\n\nBenchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=5000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -assumevalid=0 -par=1 (COMMIT = 23ac5f3f48d00a2087f9676609eb2e4b48813235)\n  Time (abs \u2261):        230301.470 s               [User: 219951.017 s, System: 1338.768 s]\n```"
  },
  {
   "t": "2026-04-14T12:28:27Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThanks for finally confirming that.\n\nFor the future: Maybe post a new comment with your benchmark results instead of editing a comment that is over a week old. This way, other reviewers will get a notification which may trigger them to continue their review. Review which might have been blocked by your earlier comments about a potential performance issue.\n\n[quoted text omitted]\nI have removed `refactoring` from the title although I think it's highly debatable if this is necessary. It seems like by your definition no code change is actually a refactor unless, maybe, it compiles to the exact same ASM. The way consensus rules are enforced is still exactly the same, there isn't even new code being used for it, it's using the same code just without worker threads which already works because the master thread has always been participating in the work. Thus this is still a refactor by my definition.\n\n[quoted text omitted]\nIndeed @darosior made a great observation and I made an according change but as you can see from the prior comments, the code was previously broken and the benefit of this is not entirely clear either so this would not have been a dramatic issue either way it seems. I can also promise you that I would have likely made the same mistake if I had broken up the PR in two from the start and we don't know if @darosior would have caught the issue in that alternative universe. So, all the evidence that I see is that the review process works on this PR.\n\n[quoted text omitted]\nThe functional tests [already have `-par=1` coverage](https://github.com/bitcoin/bitcoin/blob/master/test/functional/p2p_segwit.py#L222) and the fuzz test also use 0 worker threads in deterministic mode so I think this doesn't make a huge difference but if this makes you happy finally, I can pull the tests commit in here since it doesn't seem to hurt at first glance. Otherwise you could also add it as a follow-up or open a stand-alone PR. Since your PR changes a similar number of LoCs I don't think it's any easier to review than this PR here and just because you don't use CheckQueue it isn't any less \"dangerous\". So stacking another PR on top of yours then to get what we actually want isn't helpful in terms of safety or review burden, but I have already laid this out previously in more detail. It also doesn't seem like your comments have changed the minds of the other, previous commenters, so I will leave the changes here as is."
  },
  {
   "t": "2026-04-14T14:05:12Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI have explicitly pinged you and the original comment already stated that performance wasn't my main concern and that I was still measuring it on a different machine. Are you not curious why the other server failed?\nThe updated results don't change my review - my objections are about mixing a behavior change with a refactor in consensus-critical code, not performance.\n\n[quoted text omitted]\nI disagree. The [BIP-110 consensus failure](https://github.com/bitcoinknots/bitcoin/pull/238#issuecomment-3896624715) specifically relied on the `-par=1` path, which is exactly the path being unified here.\nThat bug demonstrates concretely that changes in this area can have non-obvious consensus implications - which is why I keep suggesting we separate the behavior change from the pure refactor.\n\n[quoted text omitted]\nI think it is, precisely because of the above. Keeping both paths intact while cleaning up their implementations avoids introducing new interactions between previously independent code paths.\n\n[quoted text omitted]\nI'm raising these concerns because I think they matter for the safety of the codebase, and I find the overall dismissive tone of this response concerning. We don't have to agree, but I'd appreciate the concerns being engaged with on their technical merits.\n\n[quoted text omitted]\nThe line count isn't the concern. It's combining a behavior change with a refactor in consensus-critical code. darosior's [correction](https://github.com/bitcoin/bitcoin/pull/32575#issuecomment-4075771363) about cache population and the BIP-110 consensus bug are concrete examples of why that combination is risky - that issue was caught only after detailed review, and it directly resulted from mixing refactoring with behavioral changes. Separating the two would make each part individually easier to verify."
  },
  {
   "t": "2026-04-14T14:35:46Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI am more than happy to look it when you actually post some information on what might have happened there. Until then it's just a distraction and I don't think this PR should be kept hostage by it after there was no follow-up for a long time. The performance issue that you were threatening for weeks didn't turn out to be a problem either.\n\n[quoted text omitted]\nThat's fine, I think I have addressed your comments suffiently, taking into account earlier reviews and weighing them against your concerns as well as my personal view and laying out my arguments as clearly as I can. But I am always happy to hear from other reviewers and reconsider if they have changed their mind based on your comments. I will not engage further on you posting your earlier arguments repeatedly since it seems like a waste of everyone's time.\n\n[quoted text omitted]\nRight, let's move on."
  },
  {
   "t": "2026-05-11T13:02:51Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91"
  },
  {
   "t": "2026-05-11T13:04:16Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Rebase after #35209"
  },
  {
   "t": "2026-05-22T02:03:45Z",
   "kind": "review_comment",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/32575/commits/74a05c8b3b318aa4406bf0cfca9bf155bb1bbb71:\n\nI think a better name for `PrepareScriptChecks` is `GetScriptCacheKey` or `GetScriptCacheEntry`, since it returns the Script Cache key if the script is not already validated. I find `PrepareScriptChecks` to be a confusing name for what it actually does."
  },
  {
   "t": "2026-05-22T02:48:49Z",
   "kind": "review_comment",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/32575/commits/1bfa2ec4bb3bd27a8fd4bf276b6931a010797294:\n\nI found this comment to be confusing. The actual decision point where `script_cache_entries` is populated is several lines above; `fJustCheck=true` is not relevant to this loop. I think it is much better to remove this comment.\n\nA better comment here should state that we only cache entries after we determine that validation succeeded."
  },
  {
   "t": "2026-05-22T02:52:41Z",
   "kind": "review",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91",
   "text": "Concept ACK https://github.com/bitcoin/bitcoin/pull/32575/commits/dfecae46c14c98e62ad8e2503493b152cb9b5b91:\n\nThe changes simplify the flow for script verification in `ConnectBlock`, and they have a nice side effect of simplifying `txvalidationcache_tests.cpp`."
  },
  {
   "t": "2026-05-28T21:48:21Z",
   "kind": "force_push",
   "who": "fjahr",
   "commit": "270af71e1eeca2615e9388c7906fef6bea792f31"
  },
  {
   "t": "2026-05-28T22:14:17Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91",
   "in_reply_to": 3285525084,
   "text": "Yeah, makes sense, I moved this comment with minor edits and added a new comment here per your suggestion."
  },
  {
   "t": "2026-05-28T22:14:59Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfecae46c14c98e62ad8e2503493b152cb9b5b91",
   "in_reply_to": 3285368791,
   "text": "A pure renaming would have dropped the \"prepare\" part (`EnsureTxData`) under the table, which IMO would not have been better than the status quo. But since @l0rinc also had a similar issue with this function that I didn't address back then, I now moved `EnsureTxData` out of this function and into the call sites. This means I can apply your renaming suggestion cleanly to the remaining function here."
  },
  {
   "t": "2026-05-28T22:15:39Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Addressed @Eunovo 's comments, thanks for the review!"
  }
 ],
 "labels_log": [
  {
   "t": "2025-05-20T22:19:12Z",
   "action": "labeled",
   "label": "Refactoring",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-22T18:15:52Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-22T20:34:22Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-12T22:40:07Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-13T14:10:20Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-08T00:13:52Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-09T16:50:46Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-28T00:17:30Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-02T16:27:27Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-06T09:13:16Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-11T13:19:55Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-05-20T21:48:58Z",
   "kind": "ready_for_review",
   "who": "fjahr"
  },
  {
   "t": "2025-05-20T22:19:09Z",
   "kind": "renamed",
   "who": "fjahr",
   "from": "RFC: refactor: Split multithreaded case out of CheckInputScripts",
   "to": "refactor: Split multithreaded case out of CheckInputScripts"
  },
  {
   "t": "2025-10-11T20:44:55Z",
   "kind": "closed",
   "who": "fjahr"
  },
  {
   "t": "2025-10-11T20:45:00Z",
   "kind": "reopened",
   "who": "fjahr"
  },
  {
   "t": "2025-10-13T06:43:47Z",
   "kind": "closed",
   "who": "maflcko"
  },
  {
   "t": "2025-10-13T06:43:54Z",
   "kind": "reopened",
   "who": "maflcko"
  },
  {
   "t": "2026-02-01T19:11:01Z",
   "kind": "renamed",
   "who": "fjahr",
   "from": "refactor: Split multithreaded case out of CheckInputScripts",
   "to": "refactor: Remove special treatment for single threaded script checks"
  },
  {
   "t": "2026-02-01T19:11:13Z",
   "kind": "renamed",
   "who": "fjahr",
   "from": "refactor: Remove special treatment for single threaded script checks",
   "to": "refactor: Remove special treatment for single threaded script checking"
  },
  {
   "t": "2026-04-14T11:48:24Z",
   "kind": "renamed",
   "who": "fjahr",
   "from": "refactor: Remove special treatment for single threaded script checking",
   "to": "consensus: Remove special treatment for single threaded script checking"
  }
 ],
 "text_chars": 66804,
 "text_tokens_estimate": 16701,
 "changed_paths": [
  "src/checkqueue.h",
  "src/test/txvalidationcache_tests.cpp",
  "src/validation.cpp",
  "src/validation.h"
 ],
 "files": [
  {
   "path": "src/checkqueue.h",
   "add": 0,
   "del": 2
  },
  {
   "path": "src/test/txvalidationcache_tests.cpp",
   "add": 17,
   "del": 22
  },
  {
   "path": "src/validation.cpp",
   "add": 84,
   "del": 60
  },
  {
   "path": "src/validation.h",
   "add": 6,
   "del": 1
  }
 ],
 "test_lines": 39,
 "git": {
  "head": "270af71e1eeca2615e9388c7906fef6bea792f31",
  "head_matches_backup": true,
  "base": "1af8e0c4e895ebd159304ed86ac059bebeced1f8",
  "commits": [
   {
    "sha": "79e0bd7edf",
    "subject": "refactor, validation: Encapsulate script execution cache access in ValidationCache",
    "files": 2,
    "add": 20,
    "del": 4
   },
   {
    "sha": "5e2f9c4d6a",
    "subject": "refactor, validation: Extract EnsureTxData from CheckInputScripts",
    "files": 1,
    "add": 18,
    "del": 13
   },
   {
    "sha": "2d553bdf24",
    "subject": "refactor: Extract GetScriptCacheEntry from CheckInputScripts",
    "files": 1,
    "add": 29,
    "del": 18
   },
   {
    "sha": "f14e5d5be8",
    "subject": "test: Use GetScriptCacheEntry in txvalidationcache_tests",
    "files": 1,
    "add": 11,
    "del": 15
   },
   {
    "sha": "60bc921f97",
    "subject": "validation: Use checkqueue for all script checking in ConnectBlock",
    "files": 2,
    "add": 21,
    "del": 18
   },
   {
    "sha": "270af71e1e",
    "subject": "refactor: Remove pvChecks parameter from CheckInputScripts",
    "files": 2,
    "add": 10,
    "del": 19
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "8e9fb9af7bd37f9f",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}