{
 "number": 35728,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35728",
 "title": "rpc: Properly throw on internal I/O errors in GetTransaction",
 "author": "maflcko",
 "author_association": "MEMBER",
 "created_at": "2026-07-15T12:09:32Z",
 "updated_at": "2026-08-15T15:38:27Z",
 "age_days": 64,
 "draft": true,
 "labels": [
  "RPC/REST/ZMQ",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "fac83ef8e9f9f6baac7049e5246da244187ee434",
 "head_ref": "2607-io-error-tx",
 "head_repo": "maflcko/bitcoin-core",
 "head_history": [
  {
   "t": "2026-07-15T12:15:07Z",
   "sha": "faa74d2c5dff4fa40f014e567bdf861ad3b46da1"
  },
  {
   "t": "2026-07-15T12:53:37Z",
   "sha": "fa1ce3d277703d1aba28f18bdbd39e9f16a70f85"
  },
  {
   "t": "2026-07-15T13:14:51Z",
   "sha": "fac83ef8e9f9f6baac7049e5246da244187ee434"
  }
 ],
 "additions": 108,
 "deletions": 41,
 "changed_files": 10,
 "commit_count": 3,
 "size_bucket": "M",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "yuvicc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35728#issuecomment-4988714009"
     },
     {
      "login": "musaHaruna",
      "url": "https://github.com/bitcoin/bitcoin/pull/35728#pullrequestreview-4713363862"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35676,
     "title": "util: Abort in CheckDiskSpace/FlatFileSeq::Open on rare exceptions",
     "author": "maflcko"
    },
    {
     "number": 35531,
     "title": "txindex: hash keys and pack positions to reduce disk usage",
     "author": "andrewtoth"
    },
    {
     "number": 34729,
     "title": "Reduce log noise",
     "author": "ajtowns"
    },
    {
     "number": 24230,
     "title": "indexes: Stop using node internal types and locking cs_main, improve sync logic",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "yuvicc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-16T06:02:05Z",
   "stale": false
  },
  "musaHaruna": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-16T11:51:24Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "musaHaruna",
   "yuvicc"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-07-16T12:07:29Z",
  "last_reviewer_activity": "2026-07-16T13:10:07Z",
  "last_reviewer": "musaHaruna",
  "author_silent_days": 63,
  "waiting_on_author_days": 63,
  "days_since_update": 33
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   35676,
   35531,
   34729,
   24230
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/rpc/rawtransaction.cpp",
  "test/functional/feature_io_errors.py"
 ],
 "body": "Currently the GetTransaction return value is a nullable transaction\npointer, where nullptr represents two different outcomes: I/O error and\nmissing tx.\n\nReporting a confirmed transaction as missing is wrong, so properly\nreturn an error instead.",
 "commits": [
  {
   "sha": "cccc39d4b6528eea7c889724801421e4847baa4b",
   "date": "2026-07-15T12:14:01Z",
   "message": "rpc: Properly throw on internal FindTx IO errors in ProcessPSBT\n\nCurrently, the FindTx return value is a flat bool, where false\nrepresents two different outcomes: I/O error and missing tx.\n\nOne may argue that this is fine in ProcessPSBT, because a -txindex\nlookup error is rare, and an error where the fallbacks to lookup the tx\nare also failing is even rarer. However, a -txindex corruption may also\npoint to a larger system corruption and it seems better to notify the\nuser properly by throwing an exception.\n\nIf I/O errors should be ignored in the future, it would be trivial to\nuse value_or(nullptr) here. However, that decision should be up to the\ncaller, and not enforced by the low-level function.\n\nThis commit only fixes ProcessPSBT. Other RPCs are done in the next\ncommit and use the value_or(nullptr) flattening fallback for now."
  },
  {
   "sha": "222207dcc1883e53df9b3fda2911608867c12ad5",
   "date": "2026-07-15T13:13:47Z",
   "message": "rpc: Properly throw on internal I/O errors in GetTransaction\n\nCurrently the GetTransaction return value is a nullable transaction\npointer, where nullptr represents two different outcomes: I/O error and\nmissing tx.\n\nReporting a confirmed transaction as missing is wrong, so properly\nreturn an error instead."
  },
  {
   "sha": "fac83ef8e9f9f6baac7049e5246da244187ee434",
   "date": "2026-07-15T13:13:53Z",
   "message": "refactor: Use GetTransaction in ProcessPSBT"
  }
 ],
 "timeline": [
  {
   "t": "2026-07-15T12:15:07Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "faa74d2c5dff4fa40f014e567bdf861ad3b46da1"
  },
  {
   "t": "2026-07-15T12:53:37Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fa1ce3d277703d1aba28f18bdbd39e9f16a70f85"
  },
  {
   "t": "2026-07-15T13:14:51Z",
   "kind": "force_push",
   "who": "maflcko",
   "commit": "fac83ef8e9f9f6baac7049e5246da244187ee434"
  },
  {
   "t": "2026-07-16T06:02:05Z",
   "kind": "comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK\nDistinguishing \u201ctransaction not found\u201d from internal read failures in `TxIndex::FindTx/GetTransaction` makes sense."
  },
  {
   "t": "2026-07-16T07:04:24Z",
   "kind": "review_comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "path": "src/rpc/rawtransaction.cpp",
   "commit": "222207dcc1883e53df9b3fda2911608867c12ad5",
   "in_reply_to": null,
   "text": "Do we ever reach here? In pruned mode when user calls `getrawtransaction <txid> <blockhash>` for the block that is pruned we return `\"I/O error reading block data\"` instead we should return `\"Block not available\"` which was done previously?\n\nfunctional test to check this behavior\n\n```python\n    def test_block_without_data_is_not_io_error(self):\n        node = self.nodes[0]\n        txid = node.getblock(node.getblockhash(1))[\"tx\"][0]\n        tip = node.getbestblockhash()\n        block = create_block(\n            int(tip, 16),\n            create_coinbase(node.getblockcount() + 1),\n            ntime=node.getblock(tip)[\"time\"] + 1,\n        )\n        block.solve()\n        node.submitheader(block.serialize()[:80].hex())\n\n        assert_raises_rpc_error(-1, \"Block not available\", node.getrawtransaction, txid, 1, block.hash_hex)\n\n```"
  },
  {
   "t": "2026-07-16T11:51:24Z",
   "kind": "review",
   "who": "musaHaruna",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "fac83ef8e9f9f6baac7049e5246da244187ee434",
   "text": "Concept ACK on separating expected outcomes (\"transaction not found\") from unexpected failures (I/O errors)."
  },
  {
   "t": "2026-07-16T11:59:10Z",
   "kind": "review_comment",
   "who": "musaHaruna",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_io_errors.py",
   "commit": "fac83ef8e9f9f6baac7049e5246da244187ee434",
   "in_reply_to": null,
   "text": "```suggestion\n        # Simulate a block file I/O failure by temporarily renaming the block file.\n        blk_dat.rename(blk_dat_moved)\n\n        try:\n            msg = \"I/O error while opening block file via txindex\"\n            assert_raises_rpc_error(-32603, msg, node.gettxoutproof, [txid])\n            assert_raises_rpc_error(-32603, msg, node.getrawtransaction, txid)\n            if self.is_wallet_compiled():\n                assert_raises_rpc_error(-32603, msg, node.utxoupdatepsbt, psbt)\n            msg = \"I/O error reading block data\"\n            assert_raises_rpc_error(-32603, msg, node.getrawtransaction, \"a\" * 64, blockhash=block)\n            msg = \"Can't read block from disk\"\n            assert_raises_rpc_error(-32603, msg, node.gettxoutproof, [tx2[\"txid\"]])\n        finally:\n            blk_dat_moved.rename(blk_dat)\n```\nSome small suggestions:\n\nWill it be worth it to add a brief comment before renaming `blk00000.dat` to clarify that the rename is intentionally simulating a block file I/O failure?\n\nSince the test temporarily renames `blk00000.dat` to simulate an I/O failure, would it be safer to wrap the assertions in a `try...finally` block so that the block file is always restored, even if an assertion or unexpected exception causes the test to exit early."
  },
  {
   "t": "2026-07-16T12:07:29Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/feature_io_errors.py",
   "commit": "fac83ef8e9f9f6baac7049e5246da244187ee434",
   "in_reply_to": 3595197636,
   "text": "[quoted text omitted]\n\nWhy would it be safer to rename a file that will be deleted a tick later?"
  },
  {
   "t": "2026-07-16T13:10:07Z",
   "kind": "review_comment",
   "who": "musaHaruna",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/feature_io_errors.py",
   "commit": "fac83ef8e9f9f6baac7049e5246da244187ee434",
   "in_reply_to": 3595197636,
   "text": "Apologies not sure if I understand the question, but what I mean by wrapping in `try...finally` is:\n```python\nblk_dat.rename(blk_dat_moved)\n\nmsg = \"I/O error while opening block file via txindex\"\nassert_raises_rpc_error(-32603, msg, node.getrawtransaction, txid)\n\n# Suppose the assertion above fails.\n# Execution never reaches this line.\nblk_dat_moved.rename(blk_dat)\n```\nIf `node.getrawtransaction(txid)` does not return the expected RPC error (for example, due to a regression).\nNow the code is left with `blk00000.dat.moved`.\n\nSo my thought is with `try...finally` even if the assertion fails, the `finally` block executes first, and the node is restored to its original state.\n\nSo my initial comment is more of a question; would it be safer if we wrap the assertations in a  `try...finally`."
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-15T12:09:36Z",
   "action": "labeled",
   "label": "RPC/REST/ZMQ",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-15T12:15:41Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-15T14:17:36Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-15T15:38:26Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-15T12:09:35Z",
   "kind": "renamed",
   "who": "DrahtBot",
   "from": " rpc: Properly throw on internal I/O errors in GetTransaction ",
   "to": "rpc: Properly throw on internal I/O errors in GetTransaction"
  },
  {
   "t": "2026-08-07T11:33:42Z",
   "kind": "convert_to_draft",
   "who": "maflcko"
  }
 ],
 "text_chars": 4749,
 "text_tokens_estimate": 1187,
 "changed_paths": [
  "src/index/txindex.cpp",
  "src/index/txindex.h",
  "src/node/transaction.cpp",
  "src/node/transaction.h",
  "src/rest.cpp",
  "src/rpc/rawtransaction.cpp",
  "src/rpc/txoutproof.cpp",
  "src/test/txindex_tests.cpp",
  "test/functional/feature_io_errors.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "src/index/txindex.cpp",
   "add": 7,
   "del": 6
  },
  {
   "path": "src/index/txindex.h",
   "add": 4,
   "del": 3
  },
  {
   "path": "src/node/transaction.cpp",
   "add": 10,
   "del": 4
  },
  {
   "path": "src/node/transaction.h",
   "add": 5,
   "del": 2
  },
  {
   "path": "src/rest.cpp",
   "add": 5,
   "del": 1
  },
  {
   "path": "src/rpc/rawtransaction.cpp",
   "add": 10,
   "del": 11
  },
  {
   "path": "src/rpc/txoutproof.cpp",
   "add": 5,
   "del": 1
  },
  {
   "path": "src/test/txindex_tests.cpp",
   "add": 7,
   "del": 13
  },
  {
   "path": "test/functional/feature_io_errors.py",
   "add": 54,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 75,
 "git": {
  "head": "fac83ef8e9f9f6baac7049e5246da244187ee434",
  "head_matches_backup": true,
  "base": "cf0368fb76e4ee07fc1a461d73eaff3e4ddb5e6b",
  "commits": [
   {
    "sha": "cccc39d4b6",
    "subject": "rpc: Properly throw on internal FindTx IO errors in ProcessPSBT",
    "files": 7,
    "add": 74,
    "del": 25
   },
   {
    "sha": "222207dcc1",
    "subject": "rpc: Properly throw on internal I/O errors in GetTransaction",
    "files": 6,
    "add": 37,
    "del": 9
   },
   {
    "sha": "fac83ef8e9",
    "subject": "refactor: Use GetTransaction in ProcessPSBT",
    "files": 1,
    "add": 5,
    "del": 15
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "ac55828218a9ec01",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}