{
 "number": 36503,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36503",
 "title": "rpc: reject AssumeUTXO rollback without tip data",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-10-11T22:50:54Z",
 "updated_at": "2026-10-31T03:48:45Z",
 "description_hash": "02c637c863573642",
 "age_days": 21,
 "draft": false,
 "labels": [
  "RPC/REST/ZMQ"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "3cb70c28f5e97bd42f45b4a917e21fbd1452f4a3",
 "head_ref": "l0rinc/rpc-dumptxoutset-missing-undo",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [],
 "additions": 19,
 "deletions": 3,
 "changed_files": 2,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "musaHaruna",
      "url": "https://github.com/bitcoin/bitcoin/pull/36503#pullrequestreview-5492606225"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "musaHaruna": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-12T15:07:23Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "musaHaruna"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-11T22:39:01Z",
  "last_reviewer_activity": "2026-10-12T15:07:23Z",
  "last_reviewer": "musaHaruna",
  "author_silent_days": 21,
  "waiting_on_author_days": 20,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "**Problem:** Loading a UTXO snapshot on a pruned node can leave the active tip without block or undo data. Attempting a rollback with `dumptxoutset` then triggers an assertion and terminates the node.\n\n**Fix:** Reuse `GetPruneHeight` to reject rollback with the existing pruning error when the required data is unavailable.",
 "commits": [
  {
   "sha": "bef6e5abaa83a7841570ce43bc761ddce0d41b82",
   "date": "2026-10-11T22:37:08Z",
   "message": "test: characterize snapshot rollback without tip data\n\nRecord the assertion when the snapshot tip lacks block data or undo, then restart the node to continue the existing checks."
  },
  {
   "sha": "3cb70c28f5e97bd42f45b4a917e21fbd1452f4a3",
   "date": "2026-10-11T22:39:01Z",
   "message": "rpc: reject snapshot rollback without tip data\n\nReuse GetPruneHeight to return the existing pruning error when the snapshot tip lacks block or undo data."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-12T15:07:23Z",
   "kind": "review",
   "who": "musaHaruna",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "3cb70c28f5e97bd42f45b4a917e21fbd1452f4a3",
   "text": "Concept ACK. The fix looks straightforward and contained: `GetPruneHeight` handles missing data at the tip before calling `GetFirstBlock`, allowing the RPC to return an error instead of hitting the assertion.\n\nI have a few questions about the surrounding behavior.\n\n- Should the early availability check also cover non-pruned nodes? They can load a snapshot without having the historical block or undo data. I understand that the code limits the early check to pruning mode, and non-pruned nodes can fail later during rollback. Could checking availability earlier avoid copying the UTXO set before discovering that rollback is impossible? Registering `TemporaryPruneLock` could still remain conditional on `IsPruneMode()`.\n\n- Would a more general error message describe these cases better? Specifically, changing:\n`Could not roll back to requested height since necessary block data is already pruned.`\nto:\n`Could not roll back to requested height because required block or undo data is unavailable.`\nAfter loading a UTXO snapshot, the node may lack historical block or undo data because it has not obtained or generated it yet, not because pruning deleted it. Keeping the existing wording for this focused fix seems reasonable, though.\n\n- Could the boundary eventually compare the highest missing-data height against the target using `>` instead of `>=`? The loop in `CreateRolledBackUTXOSnapshot` uses: `while (block_index->nHeight > target->nHeight)`\nFor example, exporting state 299 from tip 301 disconnects blocks 301 and 300, so it does not need undo for block 299. If `GetPruneHeight` returns 299, the current `299 >= 299` check rejects that request, whereas `299 > 299` would allow it. Am I missing another requirement for the target\u2019s data?\nIn a locally modified version using `>` and applying the early availability check to both pruned and non-pruned nodes, the added boundary tests passed. Exporting state 299 succeeded and matched the original snapshot\u2019s UTXO hash, while exporting state 298 still failed.",
   "id": "r:5492606225",
   "url": "https://github.com/bitcoin/bitcoin/pull/36503#pullrequestreview-5492606225",
   "hash": "8a958c12be0c0d14",
   "edited": "2026-10-12T15:08:33Z"
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-11T22:50:58Z",
   "action": "labeled",
   "label": "RPC/REST/ZMQ",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-10-15T11:35:22Z",
   "kind": "renamed",
   "who": "l0rinc",
   "from": "rpc: reject snapshot rollback without tip data",
   "to": "rpc: reject AssumeUTXO rollback without tip data"
  }
 ],
 "text_chars": 2722,
 "text_tokens_estimate": 680,
 "changed_paths": [
  "src/rpc/blockchain.cpp",
  "test/functional/feature_assumeutxo.py"
 ],
 "files": [
  {
   "path": "src/rpc/blockchain.cpp",
   "add": 1,
   "del": 3
  },
  {
   "path": "test/functional/feature_assumeutxo.py",
   "add": 18,
   "del": 0
  }
 ],
 "test_lines": 18,
 "git": {
  "head": "3cb70c28f5e97bd42f45b4a917e21fbd1452f4a3",
  "head_matches_backup": true,
  "base": "a8b69bdeafdf7993ad42aa93a80d5fc938aef1cb",
  "patch_id": "fe95c78cffd307c5",
  "commits": [
   {
    "sha": "bef6e5abaa",
    "subject": "test: characterize snapshot rollback without tip data",
    "files": 1,
    "add": 23,
    "del": 0
   },
   {
    "sha": "3cb70c28f5",
    "subject": "rpc: reject snapshot rollback without tip data",
    "files": 2,
    "add": 5,
    "del": 12
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "fe95c78cffd307c5",
 "input_hash": "64d0ba6df1a39826",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}