{
 "number": 35570,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35570",
 "title": "refactor: Change some validation.cpp methods to return BlockValidationState",
 "author": "optout21",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-06-20T06:15:23Z",
 "updated_at": "2026-09-17T10:35:47Z",
 "age_days": 89,
 "draft": false,
 "labels": [
  "Refactoring"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
 "head_ref": "2605-validation-state-return",
 "head_repo": "optout21/bitcoin",
 "head_history": [
  {
   "t": "2026-06-20T06:21:06Z",
   "sha": "cc4bac4c8ce7eda287d68dbf0dcdc384624a017a"
  },
  {
   "t": "2026-06-20T07:11:51Z",
   "sha": "8963bb6ce0dc5fb2aee7456d08c01357fc09e469"
  },
  {
   "t": "2026-06-20T07:22:39Z",
   "sha": "d7e603567f518dc39ed34cd9e25a578c73b2c3ef"
  },
  {
   "t": "2026-06-20T13:30:44Z",
   "sha": "a3a5c037c126d2c2fc434b5b3efd3b90b12d6f45"
  },
  {
   "t": "2026-06-22T16:27:34Z",
   "sha": "4626ef2a16cb8e07d03a80731f8331a9d88349e9"
  },
  {
   "t": "2026-06-22T16:38:19Z",
   "sha": "8207ac1bb79f76c9005e8e2d695b725539b3c5c0"
  },
  {
   "t": "2026-06-29T21:48:32Z",
   "sha": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f"
  },
  {
   "t": "2026-07-06T04:53:32Z",
   "sha": "576270a95c87edb8ca3ffb46e5722701e711f0a0"
  },
  {
   "t": "2026-07-06T05:07:38Z",
   "sha": "6bc18988126a1204185d1f5f550dd1ac00f15982"
  },
  {
   "t": "2026-07-08T04:32:08Z",
   "sha": "6e5a0392d2f2d3727aaa989690c75c2da3e02da9"
  },
  {
   "t": "2026-07-12T07:07:36Z",
   "sha": "e600c4ce022bf1e856cb81ca941310fe177d7eb6"
  },
  {
   "t": "2026-08-18T11:03:01Z",
   "sha": "f65361acfae8d5d0d9f525fe7887334f03cedcc3"
  },
  {
   "t": "2026-09-08T09:53:01Z",
   "sha": "58d6d23275067dc284ca13f3d7906ac9aaf7843a"
  },
  {
   "t": "2026-09-11T07:22:03Z",
   "sha": "981912489d89d40a2f54b58b9e035906e5cb4737"
  },
  {
   "t": "2026-09-16T13:59:55Z",
   "sha": "312c4e78faa5dc775bb33351ea4dda8a9e18b50a"
  },
  {
   "t": "2026-09-16T16:50:29Z",
   "sha": "a92cd68a3433b2cdae9c944345d8d20417c5a46f"
  }
 ],
 "additions": 263,
 "deletions": 226,
 "changed_files": 16,
 "commit_count": 14,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4760189210"
     },
     {
      "login": "purpleKarrot",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4765343006"
     },
     {
      "login": "stringintech",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4783419874"
     },
     {
      "login": "yuvicc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4845903603"
     },
     {
      "login": "nervana21",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4927519609"
     }
    ],
    "stale_ack": [
     {
      "login": "arejula27",
      "url": "https://github.com/bitcoin/bitcoin/pull/35570#pullrequestreview-5169457684"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36244,
     "title": "validation, net: Process blocks asynchronously and reduce cs_main contention",
     "author": "w0xlt"
    },
    {
     "number": 36066,
     "title": "validation: Separate check-only version of ConnectBlock",
     "author": "optout21"
    },
    {
     "number": 35906,
     "title": "First steps towards a stateless, side-effect free validation library",
     "author": "purpleKarrot"
    },
    {
     "number": 35793,
     "title": "Implement BIP 54 (Consensus Cleanup) without mainnet activation",
     "author": "darosior"
    },
    {
     "number": 35751,
     "title": "validation: use parallel input prevout fetching in TestBlockValidity",
     "author": "andrewtoth"
    },
    {
     "number": 35646,
     "title": "RFC: Separate out runtime errors from BlockValidationState using `util::Expected`",
     "author": "yuvicc"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    },
    {
     "number": 35557,
     "title": "kernel, validation: Add btck_chainstate_manager_set_clock_time",
     "author": "ryanofsky"
    },
    {
     "number": 29700,
     "title": "kernel, refactor: return error status on all fatal errors",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-20T22:36:15Z",
   "stale": false
  },
  "purpleKarrot": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-22T05:59:23Z",
   "stale": false
  },
  "arejula27": {
   "kind": "ack",
   "hash": "58d6d23275067dc284ca13f3d7906ac9aaf7843a",
   "t": "2026-09-10T16:03:59Z",
   "stale": true
  },
  "stringintech": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-23T20:59:42Z",
   "stale": false
  },
  "yuvicc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-06-30T16:49:01Z",
   "stale": false
  },
  "nervana21": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-09T16:49:48Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 5,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "arejula27",
   "hodlinator",
   "nervana21",
   "purpleKarrot",
   "ryanofsky",
   "stringintech",
   "w0xlt",
   "yuvicc"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-16T16:50:29Z",
  "last_reviewer_activity": "2026-09-17T07:55:47Z",
  "last_reviewer": "hodlinator",
  "author_silent_days": 0,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   29700,
   33856,
   34897,
   35621,
   35646
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 33856,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-05-19",
    "title": "kernel: Refactor process_block_header to return btck_BlockValidationState"
   },
   {
    "number": 29700,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "kernel, refactor: return error status on all fatal errors"
   },
   {
    "number": 34897,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-09",
    "title": "indexes: Don't commit ahead of the flushed chainstate"
   },
   {
    "number": 35621,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-06",
    "title": "validation: Ignore eventual error message from flushing in AcceptBlock"
   },
   {
    "number": 35646,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "RFC: Separate out runtime errors from BlockValidationState using `util::Expected`"
   }
  ],
  "conflicts": [
   36244,
   36066,
   35906,
   35793,
   35751,
   35646,
   35569,
   35557,
   29700
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/bench/checkblock.cpp",
  "src/bench/duplicate_inputs.cpp",
  "src/consensus/validation.h",
  "src/kernel/bitcoinkernel.cpp",
  "src/test/fuzz/block.cpp",
  "src/test/validation_block_tests.cpp",
  "src/validation.cpp",
  "src/validation.h"
 ],
 "body": "**Summary**. Refactor validation result to be a return value instead of an output parameter in several `validation.cpp` methods.\n\n**Motivation**. The benefits of the change are:\n- Exclude the potentially inconsistent case when the bool return value and the returned `state` are inconsistent\n- Exclude the ambiguity whether the passed in value of state is used or not (not obvious in chained calls)\n- Remove the possibility of unintuitive interaction between subsequent calls with the same state. In case of a validation error, a failure reason is set if the state was valid, but not if it was already invalid.\n- Slightly simpler: It's more evident which is the result; one less parameters.\n\nThis has grown out from #33856, mentioned in [comment here](https://github.com/bitcoin/bitcoin/pull/33856#pullrequestreview-4276791829) and [here](https://github.com/bitcoin/bitcoin/pull/33856#issuecomment-4477751949).\n\n**Details**. Many methods follow the scheme where the validation state is returned in an output parameter (`BlockValidationState& state`), and and additional `bool` return value indicating success. In success case the convention is that `state.IsValid()` and the return value are both true. After the change there is only a `BlockValidationState` return value, which is either success (`state.IsValid() == true`), or an invalid/error case.\n\nThis change is a highly localized refactor, but touching a sensitive file.\n\nRelevant methods called by `ProcessNewBlockHeaders` and `AcceptBlock` (directly and indirectly) are touched.\n\nChanges are separated into commits by touched methods, ordered by bottom-to-top in the call hierarchy.",
 "commits": [
  {
   "sha": "8e634edcd2ab5d366d1f1afc276ae1f7ca580d1e",
   "date": "2026-09-16T16:20:00Z",
   "message": "Add factory method to BlockValidationState for easier construction\n\nAdd `MakeInvalid` static helper to create invalid `BlockValidationState` instance,\nfor easier construction in error branches. Instead of the 3 statements of\ndeclaration, setting, and return with the exisiting non-static method, now\nreturn&construction is possible in one statement."
  },
  {
   "sha": "c11723bb412dc7427c3f506dfad389c4f91f964f",
   "date": "2026-09-16T16:27:17Z",
   "message": "Refactor CheckBlockHeader signature\n\nChange the (internal) method `CheckBlockHeader` to return the validation\nresult in the return value instead of an output parameter."
  },
  {
   "sha": "da560bec5f90a4372a113d05f79c00a5e70b888b",
   "date": "2026-09-16T16:29:37Z",
   "message": "Refactor ContextualCheckBlockHeader signature\n\nChange the (internal) method `ContextualCheckBlockHeader` to return the validation\nresult in the return value instead of an output parameter."
  },
  {
   "sha": "6d932645d6f1d9fea94447996cf7305de16d2148",
   "date": "2026-09-16T16:29:40Z",
   "message": "Refactor AcceptBlockHeader signature\n\nChange the method `AcceptBlockHeader` to return the `BlockValidationState`\nvalidation result in the return value instead of an output parameter."
  },
  {
   "sha": "d78d98713fbcd03c6fbb3f44913c4feed1c5a6cb",
   "date": "2026-09-16T16:30:31Z",
   "message": "Change ProcessNewBlockHeaders\n\nReturn BlockValidationState by value instead of using an out-parameter,\nsimilar to the TestBlockValidity refactoring in 74690f4ed82b1584abb07c0387db0d924c4c0cab.\n\nRemove redundant int return from btck_chainstate_manager_process_block_header.\nPreviously returned both an int result and an output validation state parameter, creating ambiguity\nwhere non-zero could mean either invalid header or processing failure. Since ProcessNewBlockHeaders already provides complete validation info, the int return was redundant.\n\nCo-authored-by: stringintech <stringintech@gmail.com>\nCo-authored-by: stickies-v <stickies-v@protonmail.com>"
  },
  {
   "sha": "e8e5e678bb010c02f813d2ed4498ce90b9c9679e",
   "date": "2026-09-16T16:30:31Z",
   "message": "Refactor CheckMerkleRoot signature\n\nChange the (internal) method `CheckMerkleRoot` to return the `BlockValidationState`\nvalidation result in the return value instead of an output parameter."
  },
  {
   "sha": "a95ca2c7c2879a40a79bd2f12d91a70df8555cad",
   "date": "2026-09-16T16:30:32Z",
   "message": "Refactor CheckBlock signature\n\nChange the method `CheckBlock` to return the `BlockValidationState`\nvalidation result in the return value instead of an output parameter."
  },
  {
   "sha": "5ee8ad8db1d4a70931a1e442cbdcc1effa194793",
   "date": "2026-09-16T16:30:33Z",
   "message": "Refactor CheckWitnessMalleation signature\n\nChange the (internal) method `CheckWitnessMalleation` to return the\n`BlockValidationState` validation result in the return value\ninstead of an output parameter."
  },
  {
   "sha": "6ce967984eecfa417b90f5c367e685568bf624bf",
   "date": "2026-09-16T16:30:33Z",
   "message": "Refactor ContextualCheckBlock signature\n\nChange the (internal) method `ContextualCheckBlock` to return the\n`BlockValidationState` validation result in the return value\ninstead of an output parameter."
  },
  {
   "sha": "b02647c1b72e678c5e30b3cf4128a671ad1763e8",
   "date": "2026-09-16T16:30:34Z",
   "message": "Refactor FatalError signature\n\nChange the method `FatalError` to return the constructed\n`BlockValidationState` object in a return value instead of\nan output parameter."
  },
  {
   "sha": "e194156313eab2871f1e31f3b131803b903adbc8",
   "date": "2026-09-16T16:30:34Z",
   "message": "Refactor FlushStateToDisk signature\n\nChange the method `FlushStateToDisk` to return the `BlockValidationState`\nvalidation result in the return value instead of an output parameter."
  },
  {
   "sha": "323a6993e43bbf617dafe0605decd91ff499a5b2",
   "date": "2026-09-16T16:30:35Z",
   "message": "Refactor AcceptBlock signature\n\nChange the method `AcceptBlock` to return the `BlockValidationState`\nvalidation result in the return value instead of an output parameter."
  },
  {
   "sha": "470b305665cad8e2d60a9f3ad1d44e9c8f129f19",
   "date": "2026-09-16T16:30:36Z",
   "message": "Internal simplification in TestBlockValidity"
  },
  {
   "sha": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "date": "2026-09-16T16:30:36Z",
   "message": "Add MakeError factory method to BlockValidationState\n\nAdd an `MakeError` factory method to the `BlockValidationState` error class, similar to `MakeInvalid`.\nUse it throughout validation.cpp, where Error state is created (two instances).\n\nCo-authored-by: \u00cd\u00f1igo Ar\u00e9jula A\u00edsa <arejula27@gmail.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-06-20T06:21:06Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "cc4bac4c8ce7eda287d68dbf0dcdc384624a017a"
  },
  {
   "t": "2026-06-20T07:11:51Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "8963bb6ce0dc5fb2aee7456d08c01357fc09e469"
  },
  {
   "t": "2026-06-20T07:22:39Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "d7e603567f518dc39ed34cd9e25a578c73b2c3ef"
  },
  {
   "t": "2026-06-20T13:30:31Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "While working on this PR, one instance was identified where the invariant `return_value == state.IsValid()` was not guaranteed:\nAt the end of `ChainstateManager::AcceptBlock()`, `FlushStateToDisk()` was called, and its return value discarded, but it could have a side effect in `state`. In turn, this could make a difference in `ChainstateManager::LoadExternalBlockFile()`.\n\nOptions to resolve this:\n\nA. Since the return value is ignored, ignore the `state` returned as well.\nB. Handle the error from `FlushStateToDisk()`, and pass the error.\n\nThe ignoring of flush result (A.) is also proposed in #29700 (cc: @ryanofsky ).\n\nThe minor behavior-change could be also omitted from this PR, limiting strictly to a no-behavior-change refactor:\n- Do not change `AcceptBlock`, keep the dual value & state return values\n- Restrict the scope of the PR and drop all commits 5-13\n- Defer this PR until this issue is solved separately first"
  },
  {
   "t": "2026-06-20T13:30:44Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "a3a5c037c126d2c2fc434b5b3efd3b90b12d6f45"
  },
  {
   "t": "2026-06-20T22:36:15Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-06-22T05:59:23Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "Concept ACK. Preferring the return value over output parameters is a useful and I would say *necessary* improment.\n\nBut could we avoid using `BlockValidationState` as a state machine, or mutable local variable? That means, instead of code like:\n\n```cpp\n{\n  BlockValidationState state;\n  if (...) {\n    state.Invalid(...);\n  }\n  return state;\n}\n```\n\nI would prefer something like:\n\n```cpp\n{\n  if (...) {\n    return BlockValidationState::Invalid(...);\n  }\n  return BlockValidationState::Valid();\n}\n```"
  },
  {
   "t": "2026-06-22T06:16:06Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nI fully agree, shorter-scoped variables mean less chance of potential interplay between calls, less complexity. I did change this in several places, but not everywhere. In some places there was a real chance for interaction between subsequent calls (e.g. error reason is set conditional of already set value), and I kept it to be safe (w.r.t. behavior changes). But I should review and change it more aggressively."
  },
  {
   "t": "2026-06-22T16:27:34Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "4626ef2a16cb8e07d03a80731f8331a9d88349e9"
  },
  {
   "t": "2026-06-22T16:38:19Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "8207ac1bb79f76c9005e8e2d695b725539b3c5c0"
  },
  {
   "t": "2026-06-22T16:40:28Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Applied some improvements:\n- Got rid of method-wide `BlockValidationState state` variables, use only restricted scope state variables, optimally in `\"if (const auto state = Xxx; !state.IsValid())\"` construct. Thanks @purpleKarrot for the emphasis!\n- Added `InvalidState` static helper to create invalid `BlockValidationState` instance. In error branches, instead of 3 statements (declaration, setting, and return) now one is enough (return&construction). Added as new first commit."
  },
  {
   "t": "2026-06-23T16:36:01Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.h",
   "commit": "f65361acfae8d5d0d9f525fe7887334f03cedcc3",
   "in_reply_to": null,
   "text": "[quoted text omitted]\nWould not be \"true\"?"
  },
  {
   "t": "2026-06-23T18:11:07Z",
   "kind": "review",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "8207ac1bb79f76c9005e8e2d695b725539b3c5c0",
   "text": "concept ACK 8207ac1bb79f76c9005e8e2d695b725539b3c5c0\n\nLooks good overall. I'd push this one step further and use `util::Expected` here, it's a natural fit and follows the PR's own motivation. The PR already cleans up the `bool` + `state` out-param inconsistency by making state the return value; `util::Expected<T, BlockValidationState>` does the same thing but at the type level instead of by convention. Success and failure become distinct alternatives of the type.\n\nConcretely, a bare `BlockValidationState` still carries all three modes (`M_VALID`/`M_INVALID`/`M_ERROR`) in the return value even though `M_VALID` is now redundant with \"the call succeeded\". With `util::Expected` that mode collapses into `has_value()` and is no longer stored, the failure modes move to the error channel, and out-params like `ppindex` fold into the success channel. `util/result.h` also explicitly steers low-level functions toward `util::Expected`.\n\n```cpp\n// pindex folded into the success channel\n[[nodiscard]] util::Expected<CBlockIndex*, BlockValidationError> AcceptBlockHeader(\n    const CBlockHeader& block, bool min_pow_checked);\n\nauto res = AcceptBlockHeader(header, /*min_pow_checked=*/true);\nif (!res) {\n    if (res.error().IsInvalid()) MaybePunishNodeForBlock(...); // peer's fault\n    return util::Unexpected{std::move(res).error()};\n}\nCBlockIndex* pindex = res.value(); // only reachable on success\n```\n\nOne open question (happy to leave it as discussion): where does `M_INVALID` fit best? Above I put it in the error channel next to `M_ERROR`, but it's arguably a *successful, expected* outcome of validation, the function did run and produced a verdict (\"this block is invalid\"), whereas `M_ERROR` is a genuine runtime failure that prevented producing a result. So an alternative split would keep Valid/Invalid in the value channel and reserve the error channel for runtime errors only. I don't have a strong preference."
  },
  {
   "t": "2026-06-23T20:59:42Z",
   "kind": "comment",
   "who": "stringintech",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK\n[quoted text omitted]\n\nThere is a thread in #33856 where this was also brought up. I included a POC in the [last comment](https://github.com/bitcoin/bitcoin/pull/33856#discussion_r3068580118) of that thread which keeps the value channel for `M_VALID`/`M_INVALID` and the error channel for `M_ERROR`, which I think makes more sense as you're suggesting: `M_INVALID` is not an operation failure.\n\nIn general, I think stripping the runtime failure from `BlockValidationState` could also leave the door open for preferring exceptions over error values in the future. That said, this should be orthogonal to this PR and could be addressed in a follow-up if desired."
  },
  {
   "t": "2026-06-24T10:36:30Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nI think both approaches are ok, though I slightly prefer `util::Expected<CBlockIndex*, BlockValidationError>`, an invalid block is an error in the same way a hardware failure causes validation to fail. That said, I understand the distinction behind `util::Expected<BlockValidationState, std::string>`, where the value represents logical state (valid or not) and the error is something unrelated to the function's logic. **I'd accept either.**\n\n[quoted text omitted]\nI completely disagree that this is orthogonal to the PR. This is a fundamentally different refactor, it differs not just internals but the API and its callers in a critical section of the code. I don't think it's correct to keep returning state through an value of the `BlockValidationState `when we already have `util::Expected` in the codebase for exactly this purpose.\n\nOn top of that, I don't think it's a good idea to keep touching this section of the code without a clear long-term direction  changes that are purely stylistic or for maintainability, only to refactor everything again later when `util::Expected` gets introduced. This PR is already proposing a structural change to error handling here, it should go all the way rather than leave a half-finished migration that will need to be revisited."
  },
  {
   "t": "2026-06-24T11:03:10Z",
   "kind": "comment",
   "who": "stringintech",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nThe way I see it, the main thing we achieve with the current refactor is the ability to reason about things more locally, by not having to pass state around and absorb it across a chain of operations. This seems like a separate objective from making `BlockValidationState` no longer absorb runtime errors or changing it to `BlockValidationError`. Even if reviewers would prefer seeing both goals addressed in a single batch/PR, I think it would be valuable to have a kind of separation between them through distinct commits."
  },
  {
   "t": "2026-06-24T14:15:56Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nAgree, this is a relevant topic alone to be merged, and **my concerns should not be a blocker**, however i feel that would be the correct way to implement this and fits inside the PR.  I will continue the discussion when more changes or reviews are submitted to not flood the thread"
  },
  {
   "t": "2026-06-25T14:38:43Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "@arejula27: very valuable suggestion using `util::Expected`, I will explore this idea. The current PR doesn't change the content of the `BlockValidationState` returned, only how it is returned, there is no behavior change (except the special case 9965489e243143fdbba6e8f379e598c92368d45c), the changes in the commits are relatively localized scope. Nonetheless, I will explore the `util::Expected` suggestion, and apply it, or not (in that case I will mention it as an alternative considered).\n\nAnother idea related to this: some methods return only Valid or Invalid, but not Error -- I've documented this in the header comments in a few places for more visibility. This is by convention, and not enforced. Maybe this could be enforced (maybe by the return value used)."
  },
  {
   "t": "2026-06-28T21:24:06Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-4758227624\n\n[quoted text omitted]\nReally nice catch. I think it would make sense to open a separate PR to fix this, so this PR can just be a refactoring and not change behavior. One approach could be: 78100ea27d076e9c76f444364dfd8fbaf719fefc"
  },
  {
   "t": "2026-06-29T13:11:19Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nThanks, I'll do this. Thanks for the prepared commit as well."
  },
  {
   "t": "2026-06-29T21:48:20Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Update:\n- The single minor behavior-relevant change has been split off into #35621.\n- The commit from there is included in this PR as first commit (rebased on top of #35621); this PR should come only after #35621."
  },
  {
   "t": "2026-06-29T21:48:32Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f"
  },
  {
   "t": "2026-06-30T16:49:01Z",
   "kind": "comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-03T05:13:41Z",
   "kind": "comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "text": "Following up on the discussion above about `util::Expected` being the long term refactor, I've opened an RFC #35646 which might be the direction where this could end."
  },
  {
   "t": "2026-07-04T23:33:40Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": null,
   "text": "Missing [[nodiscard]], unlike the other converted functions (CheckBlockHeader, CheckMerkleRoot, ContextualCheckBlockHeader)"
  },
  {
   "t": "2026-07-04T23:34:39Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": null,
   "text": "Same here: missing [[nodiscard]] for consistency with the other converted functions"
  },
  {
   "t": "2026-07-04T23:35:14Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": null,
   "text": "nit: state is dead after this line, so create(std::move(state)) avoids copy-constructing the state."
  },
  {
   "t": "2026-07-04T23:36:39Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/checkblock.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": null,
   "text": "nit: could be const (only IsValid() is called), like the fuzz/block.cpp temporaries this PR already made const."
  },
  {
   "t": "2026-07-04T23:36:55Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/duplicate_inputs.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": null,
   "text": "nit: could be const (only const methods are called on it)."
  },
  {
   "t": "2026-07-04T23:37:28Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/validation_block_tests.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": null,
   "text": "nit: could be const auto res{...}"
  },
  {
   "t": "2026-07-04T23:39:43Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "I miss a comment here about the case.\n\nI would like to know the reason to drop the previous  `(if (!state.IsValid()) NONFATAL_UNREACHABLE(); return state;)`, so a hypothetical `ConnectBlock ` that returns true while setting an invalid state would now be reported as Valid.\n\nIntentional as part of the simplification? Is this case not possible anymore?"
  },
  {
   "t": "2026-07-04T23:42:50Z",
   "kind": "review",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "text": "re-concept-ACK 014f2774b9\nSecond pass focused on correctness and the new signatures,  nothing significant to flag"
  },
  {
   "t": "2026-07-06T04:44:40Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 3524009398,
   "text": "Indeed. Such checks ensured that the bool and state return values are in-sync, and they became pointless in most places. However, here `ConnectBlock` still return both bool and state, so the check makes sense. I've put it back in the `else` branch."
  },
  {
   "t": "2026-07-06T04:53:32Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0"
  },
  {
   "t": "2026-07-06T04:54:35Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": 3524003775,
   "text": "I haven't deemed `nodiscard` important on all compile-unit-internal methods, but I've added now for consistency."
  },
  {
   "t": "2026-07-06T04:55:17Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": 3524004724,
   "text": "Added (as above)"
  },
  {
   "t": "2026-07-06T05:04:37Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": 3524005190,
   "text": "Done"
  },
  {
   "t": "2026-07-06T05:04:44Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/checkblock.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": 3524006558,
   "text": "Done"
  },
  {
   "t": "2026-07-06T05:04:52Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/duplicate_inputs.cpp",
   "commit": "014f2774b9ff271cc82e94a057eb1ce310b7cb8f",
   "in_reply_to": 3524006831,
   "text": "Done"
  },
  {
   "t": "2026-07-06T05:05:02Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/validation_block_tests.cpp",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "in_reply_to": 3524007306,
   "text": "Done"
  },
  {
   "t": "2026-07-06T05:07:17Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "576270a95c87edb8ca3ffb46e5722701e711f0a0",
   "text": "Applied some (minor) changes following review from @arejula27 -- thanks!"
  },
  {
   "t": "2026-07-06T05:07:38Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "6bc18988126a1204185d1f5f550dd1ac00f15982"
  },
  {
   "t": "2026-07-08T04:31:51Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased, following the inclusion of #35621 (first commit dropped, as already in master)."
  },
  {
   "t": "2026-07-08T04:32:08Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "6e5a0392d2f2d3727aaa989690c75c2da3e02da9"
  },
  {
   "t": "2026-07-09T16:49:48Z",
   "kind": "comment",
   "who": "nervana21",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-12T07:07:18Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased, post #34897 (straightforward resolution)."
  },
  {
   "t": "2026-07-12T07:07:36Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "e600c4ce022bf1e856cb81ca941310fe177d7eb6"
  },
  {
   "t": "2026-08-18T11:03:01Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "f65361acfae8d5d0d9f525fe7887334f03cedcc3"
  },
  {
   "t": "2026-08-18T11:03:12Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased to current master. Only tests affected: some removed tests, some straightforward resolutions.\n`git range-diff e600c4ce022bf1e856cb81ca941310fe177d7eb6...f65361acfae8d5d0d9f525fe7887334f03cedcc3`"
  },
  {
   "t": "2026-09-05T13:40:32Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "I see the default constructor gets a valid state from\n```\nenum class ModeState {\n    M_VALID,   //!< everything ok\n    M_INVALID, //!< network rule violation (DoS value may be set)\n    M_ERROR,   //!< run-time error\n} m_mode{ModeState::M_VALID};   // <-- in-class initializer\n```\nhowever it would be better for the reader if you also added a factory for the valid state (alongside InvalidState(...)), so the returned state is explicit rather than implicit and independent of the default value of `ModeState`"
  },
  {
   "t": "2026-09-05T14:23:39Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "b04810f7936620f28563c9263b8a41ce1eeb8730",
   "in_reply_to": null,
   "text": "I only found one other manual` Error(...) `construction, but might still be cleaner to have a factory here too, e.g. `BlockValidationState::ErrorState(reject_reason)`. Then this becomes:\n\n```suggestion\n    notifications.fatalError(message);\n    return BlockValidationState::ErrorState(message.original);\n\n```"
  },
  {
   "t": "2026-09-05T14:44:15Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "73e20f87de96f751b9cc5476a9a4bded634e1cf6",
   "in_reply_to": null,
   "text": "nit: I would prefer `header_state`, not a fan of using verbs in variable names\n```suggestion\n        BlockValidationState header_state{AcceptBlockHeader(block, &pindex, min_pow_checked)};\n```"
  },
  {
   "t": "2026-09-05T14:51:50Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "4a3a1a4eb0d174600e6ca04d0c60657ba5406a51",
   "in_reply_to": null,
   "text": "nit: could you put this brace a line below, to keep a consistent format with the file (see class `ValidationState `right above)?"
  },
  {
   "t": "2026-09-05T15:14:40Z",
   "kind": "review",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "f65361acfae8d5d0d9f525fe7887334f03cedcc3",
   "text": "Ack f65361acfae8d5d0d9f525fe7887334f03cedcc3\n\nThis PR only changes signatures as the PR description says, no behavioral changes.\n\nWrote some small comments, none of them are blockers, just suggestions, but I would highly encourage at least fixing the comment/doc error I mentioned in my last review (`validation.h:1280`, \"On failure,` IsInvalid()` is false\" should read \"true\").\n\nAs I mentioned in the inline comments, I think adding both factories would help readers understand the code and make it cleaner.\n\nAlso wrote two small tests (arejula27/bitcoin@67a3be1) that guard how each case is reported (valid/invalid/error). Feel free to cherry-pick, copy, edit or omit."
  },
  {
   "t": "2026-09-08T07:11:08Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.h",
   "commit": "f65361acfae8d5d0d9f525fe7887334f03cedcc3",
   "in_reply_to": 3461362198,
   "text": "Good catch. On failure `IsValid()` is false, moreover `IsInvalid()` is true. I state: \"On failure, IsValid() is false and the state contains the specific validation failure reason. Never returns Error state.\". From the last bit it follows that `IsInvalid()` is true, but I don't mention it here, only `IsValid()`, as that should be used to check for normal vs. exceptional case."
  },
  {
   "t": "2026-09-08T09:47:37Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 3940788953,
   "text": "I think it's intuitive for the state class to initialize to Valid by default. Adding a factory method for the Valid would be an overkill in my view. It's true that `BlockValidationState` and `ValidationState` don't have an explicit constructor, but it's rather clear that the default constructor initializes to `M_VALID`. I have added a note to `ValidationState` to clarify, but not changing anything else."
  },
  {
   "t": "2026-09-08T09:49:22Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "b04810f7936620f28563c9263b8a41ce1eeb8730",
   "in_reply_to": 3940903704,
   "text": "Suggestion taken, factory method for Error added. I've also found a second construction instance (in `AcceptBlock`), both use the factory method now. Added you as co-author."
  },
  {
   "t": "2026-09-08T09:49:37Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "73e20f87de96f751b9cc5476a9a4bded634e1cf6",
   "in_reply_to": 3940952975,
   "text": "Taken."
  },
  {
   "t": "2026-09-08T09:49:49Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "4a3a1a4eb0d174600e6ca04d0c60657ba5406a51",
   "in_reply_to": 3940974585,
   "text": "Done."
  },
  {
   "t": "2026-09-08T09:52:40Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "f65361acfae8d5d0d9f525fe7887334f03cedcc3",
   "text": "Thanks for the reviews, @arejula27!\nChanges applied -- all minor:\n\n- Fix error in documentation of `ProcessNewBlockHeaders`.\n- `ProcessNewBlockHeaders`: add assert to check that the non-valid case is invalid (and not error).\n- `AcceptBlockHeader`: add asserts to check that non-valid cases are all invalid (and not error).\n- Minor local variable rename in `AcceptBlock`\n- Minor formatting change (`src/consensus/validation.h`)\n- Add `ErrorState` factory method to the `BlockValidationState` error class. Added as a separate commit.\n\n`git range-diff f65361acfae8d5d0d9f525fe7887334f03cedcc3..58d6d23275067dc284ca13f3d7906ac9aaf7843a`"
  },
  {
   "t": "2026-09-08T09:53:01Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "58d6d23275067dc284ca13f3d7906ac9aaf7843a"
  },
  {
   "t": "2026-09-10T16:03:11Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 3940788953,
   "text": "agree"
  },
  {
   "t": "2026-09-10T16:03:59Z",
   "kind": "review",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "58d6d23275067dc284ca13f3d7906ac9aaf7843a",
   "text": "reACK 58d6d23275067dc284ca13f3d7906ac9aaf7843a"
  },
  {
   "t": "2026-09-10T22:12:54Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nThanks, but I think you need the form \"reACK\" exactly, to be picked up by the review bot. Could you edit it (or re-comment)?"
  },
  {
   "t": "2026-09-10T22:19:30Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "CONTRIBUTOR",
   "text": "Sorry, first time doing it\ud83d\ude05"
  },
  {
   "t": "2026-09-11T01:07:13Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/block.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "I am trying to understand this.\nCalling `validation_state_pow_and_merkle.Error(\"\")` changes the state to Error, making the later `if (validation_state_pow_and_merkle.IsValid())` always false so it never runs.\n\nSeparately, the `IsValid() || IsInvalid() || IsError()` assertions appear redundant, even before the refactor: a default-constructed state was `Valid`, so these assertions would pass even if `CheckBlock()` never updated it."
  },
  {
   "t": "2026-09-11T07:20:25Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/block.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 3985017578,
   "text": "Pre-change, `CheckBlock` returned a bool and status in the parameter. The fuzz test saved the bools, and used them for checks at the end. Since there is no bool return any more, those became unavailable, and `status.IsValid()` is used instead.\n\nYou are right, that the setting of `validation_state_pow_and_merkle` to error interferes with this! I'm not sure of the intention of that line, but it seems it didn't do anything meaningful, as that variable was not accessed afterwards. It may contribute to test coverage though. So my resulotion is to move the `.Error()` call to the end.\n\nApplying now, as this is a (minor) bug to the fuzz test. Thanks for the find!"
  },
  {
   "t": "2026-09-11T07:22:01Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "58d6d23275067dc284ca13f3d7906ac9aaf7843a",
   "text": "Applied fix to a minor fuzz test bug, found in review (thanks @w0xlt).\n\n`git range-diff 58d6d23275067dc284ca13f3d7906ac9aaf7843a...981912489d89d40a2f54b58b9e035906e5cb4737`"
  },
  {
   "t": "2026-09-11T07:22:03Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737"
  },
  {
   "t": "2026-09-15T09:48:15Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": null,
   "text": "nit: Could use default ctor initialization to avoid any risk of having to seek through the string literals for NUL term.\n```suggestion\n        const std::string& reject_reason = {}, const std::string& debug_message = {})\n```"
  },
  {
   "t": "2026-09-15T09:53:00Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "nit: Could add precondition:\n```c++\n        Assume(result != BlockValidationResult::BLOCK_RESULT_UNSET);\n```"
  },
  {
   "t": "2026-09-15T09:54:19Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": null,
   "text": "nits: So much \"state\" in `BlockValidationState::InvalidState(...` how about:\n```suggestion\n    static BlockValidationState MakeInvalid(BlockValidationResult result,\n```\nor alternatively make it a free function outside of `BlockValidationState` as we don't need access to private parts:\n```c++\n[[nodiscard]] inline BlockValidationState InvalidBlockValidationState(BlockValidationResult result,\n    const std::string& reject_reason = {}, const std::string& debug_message = {})\n{\n```\n(+ make it `nodiscard`)."
  },
  {
   "t": "2026-09-15T10:00:26Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "0f6e59cd81755e801cd4f2bb60ff7211bb8762ba",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWe want to make sure that we are not overriding an invalid/error state with a valid state with this refactor. Could add this before overwriting the incoming state parameter:\n```c++\n    Assume(state.IsValid());\n```\n\nThe CI tests and reviewers running the ancestor commits can then help verify that the behavior doesn't change.\n\nMost of them could then hopefully be removed in later commits where we further reduce the scopes of `state`-variables.\n\nSame in `AcceptBlockHeader()` in this commit, and same pattern in later commits."
  },
  {
   "t": "2026-09-15T11:01:41Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "remark in 86104e9d74d92cd0c4b72aa0983177c9fcf665e0 \"Refactor AcceptBlockHeader signature\":\n\nI understand that these asserts *can* be added since no paths currently return error. Hope these wires won't be tripped outside of tests in the future. Not sure whether these types of documentation asserts should be used in such critical code when the conditions they catch don't obviously cause problems later on during execution (in contrast to checking a pointer for non-nullness before de-referencing).\n\nIt would feel safer if we made the paths which do not return error states instead return a narrower state type which only allowed valid/invalid, instead of having these more imperative checks.\n\nMaybe their additions could be broken out into a commit at the end, or be deferred to a later PR, *keeping this more strictly a refactoring PR*. Similar changes are done in 4ed127368759983972278265320c278ef3014230 \"Change ProcessNewBlockHeaders\", which in its title admits it's not a pure refactoring commit."
  },
  {
   "t": "2026-09-15T12:10:28Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": null,
   "text": "nit: Would prefer this were introduced together with `InvalidState()` and then used in the later refactoring commits for `FatalError()` and `AcceptBlock()`."
  },
  {
   "t": "2026-09-15T12:27:55Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/checkblock.cpp",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": null,
   "text": "nanonit: Could reduce camelCase in touched lines as developer-notes.md enforces snake_case.\n```suggestion\n            const BlockValidationState state{CheckBlock(block, chain_params->GetConsensus())};\n            assert(state.IsValid());\n```"
  },
  {
   "t": "2026-09-15T12:30:06Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/bench/duplicate_inputs.cpp",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": null,
   "text": "nanonit: Even stronger assertion:\n```suggestion\n        assert(cvstate.IsInvalid());\n```"
  },
  {
   "t": "2026-09-15T13:18:32Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "text": "Reviewed 981912489d89d40a2f54b58b9e035906e5cb4737\n\nI get the impression the alternative #35646 returns narrower types, which makes the code more correct by construction rather than adding `assert(state.IsInvalid)`. Only just discovered that PR though, so will try to review it next, before coming back to this one."
  },
  {
   "t": "2026-09-16T12:22:50Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nThanks for the review!\n#35646 and this PR overlap, but not completely, both have their own merits. PR 35646 was raised later, and I like it, I ack'ed it. I haven't closed this PR, as it has some improvements not present there. In case 35646 is merged first, I will update this PR. Please have a look at #35646 too.\n\nClarification (Sept 17): I concept-acked #35646, not proper-ack, but I made a mistake, and it showed up as proper-ack  (https://github.com/bitcoin/bitcoin/pull/35646#issuecomment-5712874848)."
  },
  {
   "t": "2026-09-16T12:25:41Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/duplicate_inputs.cpp",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4015573540,
   "text": "Technically the check only checked for `false` return value, that corresponds to `!IsValid()`. This is a tightening of the checks, but the duplicate inputs case should result in invalid (not error), so it is safe to change. Accepted."
  },
  {
   "t": "2026-09-16T12:28:07Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/bench/checkblock.cpp",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4015555012,
   "text": "Taken."
  },
  {
   "t": "2026-09-16T12:29:45Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4015407700,
   "text": "It is done (in later commit?)."
  },
  {
   "t": "2026-09-16T12:40:46Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "0f6e59cd81755e801cd4f2bb60ff7211bb8762ba",
   "in_reply_to": 4014342260,
   "text": "This point becomes irrelevant in a later commit, when `CheckBlock` itself doesn't get an input `state`, and the state returned from `CheckBlockHeader` call is placed in a new local variable, so no overwriting can happen.\n\n```\n    // Check that the header is valid (particularly PoW).  This is mostly\n    // redundant with the call in AcceptBlockHeader.\n    if (const auto state = CheckBlockHeader(block, consensusParams, fCheckPOW); !state.IsValid())\n        return state;\n```"
  },
  {
   "t": "2026-09-16T12:43:06Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4014298303,
   "text": "Accepted.\n\n```\nsed -i \"s/InvalidState/MakeInvalid/g\" src/validation.cpp src/validation.h\nsed -i \"s/ErrorState/MakeError/g\" src/validation.cpp src/validation.h\n```"
  },
  {
   "t": "2026-09-16T12:44:17Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 4014286632,
   "text": "Accepted."
  },
  {
   "t": "2026-09-16T12:44:37Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4014245663,
   "text": "Taken."
  },
  {
   "t": "2026-09-16T13:47:00Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 4014835740,
   "text": "I understand the doubts about using potentially catastrophic `assert`'s.\nHowever, if an invariant can be verified by static code analysis of a small amount of code, assert are useful for not only documenting, but enforcing invariants.\nI agree that narrower return values are superior solution in this case, but I did not see an easy way to ensure that.\nThe concern about code being changed later is always a valid worry, I agree.\nI would prefer these methods to have a single return at the end, and assert just before the single return. However, with early-exit returns style that's not practical.\n\nIn this PR, the following 4 methods can return Valid or Invalid, but never Error:\n\n- `CheckBlockHeader`: Internal method, and trivial to verify the code: it has 3 lines with 2 returns, and none of them is Error.\n- `ContextualCheckBlockHeader`: Internal method, more complicated, around 8 returns, one `{}` and the rest all `MakeInvalid`\n- `AcceptBlockHeader`: This is a ~~public~~ private method, and its return are Invalid or Valid, except 2 cases, where it calls into the above two methods. If those return non-valid, that must be invalid, and this is asserted and returned. This way it can be verified by looking only at this method that it cannot return Error.\n- `ProcessNewBlockHeaders`: Public method. Returns valid, or non-valid from `AcceptBlockHeader`, which, by the above, must be Invalid. This is asserted, so that the invariant can be verified by looking only at this method.\nIt is only this last method which is called from outside, from `net_processing.cpp`, and there is is `Assume`'d that it doesn't return Error.\n\nMoving these asserts to a separate commit is tempting, but in fact it's only cosmetics.\nI've also considered using less dangerous `Assume`. Or leaving them out altogether.\n\nBased on the above explanation I keep as it is for now. Those asserts are needed to ensure the invariant added to the documentation (\"Never returns Error state\")."
  },
  {
   "t": "2026-09-16T13:59:48Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "text": "Made some (minor) changes, following latest review (@hodlinator).\n\n```git range-diff 981912489d89d40a2f54b58b9e035906e5cb4737...312c4e78faa5dc775bb33351ea4dda8a9e18b50a```"
  },
  {
   "t": "2026-09-16T13:59:55Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "312c4e78faa5dc775bb33351ea4dda8a9e18b50a"
  },
  {
   "t": "2026-09-16T16:50:27Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Fixed IWYU CI failure (missing include for new `Assume`).\nAdditionally added comments to `CheckBlockHeader` and `ContextualCheckBlockHeader` (that they cannot return Error), and also fixed an outdated comment in `src/net_processing.cpp` (AcceptBlockHeader/ProcessNewBlockHeaders)."
  },
  {
   "t": "2026-09-16T16:50:29Z",
   "kind": "force_push",
   "who": "optout21",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f"
  },
  {
   "t": "2026-09-16T18:25:14Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4014245663,
   "text": "thread https://github.com/bitcoin/bitcoin/pull/35570#discussion_r4014245663:\n\nPlease also fix `MakeError(const std::string& reject_reason = \"\")`."
  },
  {
   "t": "2026-09-16T19:15:21Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": null,
   "text": "This can be dropped in the \"Refactor CheckBlock signature\" commit so we only assign state once.\n```suggestion\n```"
  },
  {
   "t": "2026-09-16T19:31:08Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/consensus/validation.h",
   "commit": "981912489d89d40a2f54b58b9e035906e5cb4737",
   "in_reply_to": 4015407700,
   "text": "thread https://github.com/bitcoin/bitcoin/pull/35570#discussion_r4015407700:\n\nWhat I meant is to add both `MakeInvalid()` and `MakeError()` helpers in the first commit. Then in the \"Refactor FatalError signature\" and \"Refactor AcceptBlock signature\" commits you can switch directly to `MakeError()` instead of the intermediate hop through `state.Error() ... return state` which those commits currently do."
  },
  {
   "t": "2026-09-16T19:37:09Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "0f6e59cd81755e801cd4f2bb60ff7211bb8762ba",
   "in_reply_to": 4014342260,
   "text": "thread https://github.com/bitcoin/bitcoin/pull/35570#discussion_r4014342260:\n\nI know many checks away in later commits but still think it's good practice to add them in intermediate commits when you are changing behavior of such critical code.\n\nAt the very least the \"test ancestor commits\" CI job can churn through them.\n\nYou are changing the local code behavior from *maybe* changing `state` when passed as an argument, to definitely stomping it with a return value.\n\nI went through and added `Assume`s in my suggestions branch and many of them survive until the end, although I admit some are overly paranoid."
  },
  {
   "t": "2026-09-16T20:04:25Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "in_reply_to": 4014835740,
   "text": "thread https://github.com/bitcoin/bitcoin/pull/35570#discussion_r4014835740:\n\nFollowing the call tree *on the current PR base* is good to do, but not the best we can do. Adding asserts and now comments about current behavior feels like duct tape to me.\n\nAs the PRs currently stand, I much prefer #35646 be merged before this PR so invariants are enforced at compile time through types rather than by the duct tape that is runtime failures and comments. That makes it much harder for silent merge conflicts and the like to cause issues."
  },
  {
   "t": "2026-09-17T07:35:11Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "470b305665cad8e2d60a9f3ad1d44e9c8f129f19",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis `state` should be removed if we keep this commit where we call `CheckBlock()` etc which return it's own instances instead of overwriting `state`. That means we also refactor the next `if`-block:\n\n```c++\n    if (block.hashPrevBlock != *Assert(tip->phashBlock)) {\n        return BlockValidationState::MakeInvalid({}, \"inconclusive-not-best-prevblk\");\n    }\n```\n\nThat also necessitates re-introducing `state` before calling `ConnectBlock()`, unless that method is also refactored to return `BlockValidationState`:\n\n```c++\n    {\n        BlockValidationState state;\n        // Set fJustCheck to true in order to update, and not clear, validation caches.\n        if (!chainstate.ConnectBlock(block, state, &index_dummy, view_dummy, /*fJustCheck=*/true)) {\n            if (state.IsValid()) NONFATAL_UNREACHABLE();\n            return state;\n        } else {\n            if (!state.IsValid()) NONFATAL_UNREACHABLE();\n        }\n    }\n```"
  },
  {
   "t": "2026-09-17T07:55:47Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
   "text": "Reviewed a92cd68a3433b2cdae9c944345d8d20417c5a46f\n\nThe way this PR transforms pre-existing functions one at a time is something I wish #35646 did as well.\n\nAgree that it complements #35646, but think it should go in after that one (see inline comment), so I'll defer my Concept A-C-K until something like that lands. Please don't see my reservations as disinterest, I spend time on it because I agree the general direction is worthwhile.\n\nBranch incorporating my suggestions into existing commits: https://github.com/hodlinator/bitcoin/tree/pr/35570_suggestions"
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-20T06:15:27Z",
   "action": "labeled",
   "label": "Refactoring",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-20T07:12:22Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-20T08:38:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-22T16:40:41Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-22T17:49:18Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-06T05:08:17Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-06T06:20:16Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-10T00:53:04Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-12T09:13:57Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-14T15:41:58Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T12:19:08Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-16T15:24:38Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-16T17:54:42Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-06-21T05:00:24Z",
   "kind": "ready_for_review",
   "who": "optout21"
  }
 ],
 "text_chars": 30738,
 "text_tokens_estimate": 7684,
 "changed_paths": [
  "src/bench/checkblock.cpp",
  "src/bench/duplicate_inputs.cpp",
  "src/consensus/validation.h",
  "src/kernel/bitcoinkernel.cpp",
  "src/net_processing.cpp",
  "src/node/blockstorage.cpp",
  "src/rpc/mining.cpp",
  "src/test/baseindex_tests.cpp",
  "src/test/chainstate_write_tests.cpp",
  "src/test/fuzz/block.cpp",
  "src/test/fuzz/utxo_snapshot.cpp",
  "src/test/util/mining.cpp",
  "src/test/validation_block_tests.cpp",
  "src/test/validation_chainstate_tests.cpp",
  "src/validation.cpp",
  "src/validation.h"
 ],
 "files": [
  {
   "path": "src/bench/checkblock.cpp",
   "add": 2,
   "del": 3
  },
  {
   "path": "src/bench/duplicate_inputs.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/consensus/validation.h",
   "add": 24,
   "del": 2
  },
  {
   "path": "src/kernel/bitcoinkernel.cpp",
   "add": 4,
   "del": 6
  },
  {
   "path": "src/net_processing.cpp",
   "add": 13,
   "del": 12
  },
  {
   "path": "src/node/blockstorage.cpp",
   "add": 7,
   "del": 3
  },
  {
   "path": "src/rpc/mining.cpp",
   "add": 1,
   "del": 2
  },
  {
   "path": "src/test/baseindex_tests.cpp",
   "add": 4,
   "del": 3
  },
  {
   "path": "src/test/chainstate_write_tests.cpp",
   "add": 4,
   "del": 5
  },
  {
   "path": "src/test/fuzz/block.cpp",
   "add": 10,
   "del": 13
  },
  {
   "path": "src/test/fuzz/utxo_snapshot.cpp",
   "add": 2,
   "del": 4
  },
  {
   "path": "src/test/util/mining.cpp",
   "add": 1,
   "del": 2
  },
  {
   "path": "src/test/validation_block_tests.cpp",
   "add": 7,
   "del": 2
  },
  {
   "path": "src/test/validation_chainstate_tests.cpp",
   "add": 4,
   "del": 5
  },
  {
   "path": "src/validation.cpp",
   "add": 166,
   "del": 149
  },
  {
   "path": "src/validation.h",
   "add": 12,
   "del": 13
  }
 ],
 "test_lines": 75,
 "git": {
  "head": "a92cd68a3433b2cdae9c944345d8d20417c5a46f",
  "head_matches_backup": true,
  "base": "16f4bd15bc9f46d399c44378806df87c778a6c9d",
  "commits": [
   {
    "sha": "8e634edcd2",
    "subject": "Add factory method to BlockValidationState for easier construction",
    "files": 1,
    "add": 14,
    "del": 1
   },
   {
    "sha": "c11723bb41",
    "subject": "Refactor CheckBlockHeader signature",
    "files": 1,
    "add": 10,
    "del": 7
   },
   {
    "sha": "da560bec5f",
    "subject": "Refactor ContextualCheckBlockHeader signature",
    "files": 1,
    "add": 17,
    "del": 12
   },
   {
    "sha": "6d932645d6",
    "subject": "Refactor AcceptBlockHeader signature",
    "files": 3,
    "add": 21,
    "del": 21
   },
   {
    "sha": "d78d98713f",
    "subject": "Change ProcessNewBlockHeaders",
    "files": 8,
    "add": 36,
    "del": 33
   },
   {
    "sha": "e8e5e678bb",
    "subject": "Refactor CheckMerkleRoot signature",
    "files": 1,
    "add": 12,
    "del": 9
   },
   {
    "sha": "a95ca2c7c2",
    "subject": "Refactor CheckBlock signature",
    "files": 8,
    "add": 65,
    "del": 59
   },
   {
    "sha": "5ee8ad8db1",
    "subject": "Refactor CheckWitnessMalleation signature",
    "files": 1,
    "add": 10,
    "del": 10
   },
   {
    "sha": "6ce967984e",
    "subject": "Refactor ContextualCheckBlock signature",
    "files": 1,
    "add": 11,
    "del": 11
   },
   {
    "sha": "b02647c1b7",
    "subject": "Refactor FatalError signature",
    "files": 3,
    "add": 25,
    "del": 13
   },
   {
    "sha": "e194156313",
    "subject": "Refactor FlushStateToDisk signature",
    "files": 3,
    "add": 26,
    "del": 37
   },
   {
    "sha": "323a6993e4",
    "subject": "Refactor AcceptBlock signature",
    "files": 4,
    "add": 38,
    "del": 37
   },
   {
    "sha": "470b305665",
    "subject": "Internal simplification in TestBlockValidity",
    "files": 1,
    "add": 8,
    "del": 11
   },
   {
    "sha": "a92cd68a34",
    "subject": "Add MakeError factory method to BlockValidationState",
    "files": 2,
    "add": 12,
    "del": 7
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "82d07e1a9154f78d",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}