{
 "number": 36318,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36318",
 "title": "validation: Ensure Invalid ValidationState has result",
 "author": "optout21",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-23T11:51:17Z",
 "updated_at": "2026-10-15T10:23:12Z",
 "description_hash": "9376cc5cc484fc30",
 "age_days": 39,
 "draft": false,
 "labels": [
  "Validation"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "22cf9d54b3fd9561b6b3ad057264dcb2ddbfef56",
 "head_ref": "2609-invalid-must-have-reason",
 "head_repo": "optout21/bitcoin",
 "head_history": [],
 "additions": 27,
 "deletions": 11,
 "changed_files": 10,
 "commit_count": 4,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "hodlinator"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-23T16:00:10Z",
  "last_reviewer_activity": "2026-09-23T12:20:56Z",
  "last_reviewer": "hodlinator",
  "author_silent_days": 39,
  "waiting_on_author_days": 0,
  "days_since_update": 17
 },
 "refs": {
  "mentioned": [
   31981,
   35570
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 31981,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-06-19",
    "title": "Add checkBlock() to Mining interface"
   },
   {
    "number": 35570,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "refactor: Change some validation.cpp methods to return BlockValidationState"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Block and transaction validation results are represented in `ValidationState`, and invalid results in \"Invalid\" states. Invalid validation results have a `result`, an enum value, and a `reject_reason`, a structured string.\n\nThe change is to ensure that an __invalid result always has a set result and reject reason__. This increases code robustness/security.\n\nOne instance was identified where the result was not set, and is being fixed by a new `BLOCK_INCONCLUSIVE` result. PR #31981 moved a previous-block check from outside of `TestBlockValidity()` to inside it, and introduced a new usage of `BlockValidationState` with unset result. The result field is not checked outside. This instance is changed now to use the newly-introduced `BLOCK_INCONCLUSIVE` value.\n\nThe new enum value is added to the kernel C API and its C++ wrapper. In P2P, a `BLOCK_INCONCLUSIVE` result does not lead to punishing the peer.\n\nA few test-only instances with empty reject reason are changed to use a dummy value (\"dummy-reason\").\n\nHistory: this was found on the side of #35570, but left untouched there; see https://github.com/bitcoin/bitcoin/pull/35570#issuecomment-5739477357 and https://github.com/bitcoin/bitcoin/pull/35570#discussion_r4075379069.",
 "commits": [
  {
   "sha": "34c6f9ff71288e4028f1ce425519c6e5f161318d",
   "date": "2026-09-23T11:36:14Z",
   "message": "validation: Add BlockValidationResult::BLOCK_INCONCLUSIVE (unused)\n\nThe goal is to ensure that an Invalid `ValidationState` always has a set result.\nHowever, there is a violating usage in `TestBlockValidity`.\nAs a preparation to address that, a new value is added to the `BlockValidationResult`\nenum, `BLOCK_INCONCLUSIVE`, representing a result where validity could not be\ndetermined, e.g. due to some missing data (previous block).\nA corresponding value is added to the kernel C API\n(btck_BlockValidationResult_INCONCLUSIVE) and its C++ wrapper.\nIn P2P, a `BLOCK_INCONCLUSIVE` result does not lead to punishing the peer.\nThe new value is used in a later commit."
  },
  {
   "sha": "ee04f8207542cee76659ccef4cd82f87301dfa48",
   "date": "2026-09-23T11:36:46Z",
   "message": "validation: In TestBlockValidity, use INCONCLUSIVE\n\nThe goal is to ensure that an Invalid ValidationState always has a set result.\nA violating usage in `TestBlockValidity` is being changed: instead of the\ndefault `BLOCK_RESULT_UNSET`, it now uses `BLOCK_INCONCLUSIVE`.\nThe result field from `TestBlockValidity` is currently not inspected by callers,\nbut the constraint \"an Invalid state must have a set result\"\nis satisfied this way."
  },
  {
   "sha": "db793b6ef2cfe89270456288ac3825fabe4fa428",
   "date": "2026-09-23T11:37:01Z",
   "message": "validation: Ensure that Invalid ValidationState always has a result\n\nEnsure that an Invalid `ValidationState`, constructed through `Invalid()`,\nalways has a set result field, by using an `Assume`.\nThis increases code robustness/security."
  },
  {
   "sha": "22cf9d54b3fd9561b6b3ad057264dcb2ddbfef56",
   "date": "2026-09-23T11:37:05Z",
   "message": "validation: Ensure Invalid ValidationState has non-empty reject_reason\n\nIn addition to ensuring a set result, also ensure that `reject_reason`\nis not empty for an Invalid `ValidationState`.\nAlso remove its empty default value, as it should never be empty.\nSome test-only usages, where empty reject reason was used, are changed\nto use a dummy value."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-23T11:54:07Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "CC: @Sjors (author of #31981), @hodlinator (reviewer of #35570).",
   "id": "c:5794327606",
   "url": "https://github.com/bitcoin/bitcoin/pull/36318#issuecomment-5794327606",
   "hash": "1d24d4a8be6f8af1",
   "edited": null
  },
  {
   "t": "2026-09-23T12:20:56Z",
   "kind": "comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "text": "From what I deduce the defaulted result enum was suggested in the diff posted by @ryanofsky in https://github.com/bitcoin/bitcoin/pull/31981#discussion_r2087339443, so he might also be interested in this.",
   "id": "c:5794699942",
   "url": "https://github.com/bitcoin/bitcoin/pull/36318#issuecomment-5794699942",
   "hash": "dc43cd06d3d0a7a8",
   "edited": null
  },
  {
   "t": "2026-09-23T16:00:10Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Undrafting upon clean CI",
   "id": "c:5798176625",
   "url": "https://github.com/bitcoin/bitcoin/pull/36318#issuecomment-5798176625",
   "hash": "4934aab5eca30a4c",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-23T11:51:21Z",
   "action": "labeled",
   "label": "Validation",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-23T16:00:14Z",
   "kind": "ready_for_review",
   "who": "optout21"
  }
 ],
 "text_chars": 3265,
 "text_tokens_estimate": 816,
 "changed_paths": [
  "src/bitcoin-chainstate.cpp",
  "src/consensus/validation.h",
  "src/kernel/bitcoinkernel.cpp",
  "src/kernel/bitcoinkernel.h",
  "src/kernel/bitcoinkernel_wrapper.h",
  "src/net_processing.cpp",
  "src/test/fuzz/txdownloadman.cpp",
  "src/test/kernel/test_kernel.cpp",
  "src/test/txdownload_tests.cpp",
  "src/validation.cpp"
 ],
 "files": [
  {
   "path": "src/bitcoin-chainstate.cpp",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/consensus/validation.h",
   "add": 6,
   "del": 3
  },
  {
   "path": "src/kernel/bitcoinkernel.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/kernel/bitcoinkernel.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/kernel/bitcoinkernel_wrapper.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/net_processing.cpp",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/test/fuzz/txdownloadman.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/test/kernel/test_kernel.cpp",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/test/txdownload_tests.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/validation.cpp",
   "add": 1,
   "del": 1
  }
 ],
 "test_lines": 15,
 "git": {
  "head": "22cf9d54b3fd9561b6b3ad057264dcb2ddbfef56",
  "head_matches_backup": true,
  "base": "2b95b45a9abd63a1532ce9deea1438e07e7a635f",
  "patch_id": "edaadb3ad0394aea",
  "commits": [
   {
    "sha": "34c6f9ff71",
    "subject": "validation: Add BlockValidationResult::BLOCK_INCONCLUSIVE (unused)",
    "files": 7,
    "add": 16,
    "del": 2
   },
   {
    "sha": "ee04f82075",
    "subject": "validation: In TestBlockValidity, use INCONCLUSIVE",
    "files": 1,
    "add": 1,
    "del": 1
   },
   {
    "sha": "db793b6ef2",
    "subject": "validation: Ensure that Invalid ValidationState always has a result",
    "files": 1,
    "add": 2,
    "del": 0
   },
   {
    "sha": "22cf9d54b3",
    "subject": "validation: Ensure Invalid ValidationState has non-empty reject_reason",
    "files": 3,
    "add": 9,
    "del": 9
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "edaadb3ad0394aea",
 "input_hash": "0c21fff69b906a1b",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}