{
 "number": 36629,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36629",
 "title": "Add output script index and corresponding script history RPC",
 "author": "andrewtoth",
 "author_association": "MEMBER",
 "created_at": "2026-10-23T17:19:11Z",
 "updated_at": "2026-10-31T16:09:01Z",
 "description_hash": "b74dbaaaef836fa7",
 "age_days": 9,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
 "head_ref": "txoscriptindex",
 "head_repo": "andrewtoth/bitcoin",
 "head_history": [
  {
   "t": "2026-10-23T17:40:00Z",
   "sha": "614302e7d14a16fe28a2e8945fa99165693fcf94"
  },
  {
   "t": "2026-10-25T20:14:32Z",
   "sha": "cf9b981fcc7ae52c7864beae35df5d60b5c322b4"
  },
  {
   "t": "2026-10-29T03:26:02Z",
   "sha": "5735ea91cd6f9527fea2c521c6d3a6bf855cd4f6"
  },
  {
   "t": "2026-10-29T14:30:13Z",
   "sha": "1fc5b618e3693c824124dd4ee4c3c95e100e645d"
  }
 ],
 "additions": 2234,
 "deletions": 31,
 "changed_files": 30,
 "commit_count": 5,
 "size_bucket": "XL",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "kravens",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5702703273"
     }
    ],
    "concept_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6372429119"
     },
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6372628097"
     },
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6373603191"
     },
     {
      "login": "romanz",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5649167214"
     },
     {
      "login": "ViniciusCestarii",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5657759713"
     },
     {
      "login": "xyzconstant",
      "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6502969751"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "sedited": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-23T18:54:49Z",
   "stale": false
  },
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-23T19:05:41Z",
   "stale": false
  },
  "kravens": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-23T22:56:57Z",
   "stale": false
  },
  "romanz": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-26T18:42:43Z",
   "stale": false
  },
  "ViniciusCestarii": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-26T22:06:43Z",
   "stale": false
  },
  "xyzconstant": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-29T16:53:41Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 6,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "ViniciusCestarii",
   "kravens",
   "l0rinc",
   "romanz",
   "sedited",
   "w0xlt",
   "xyzconstant"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-29T14:32:38Z",
  "last_reviewer_activity": "2026-10-29T18:10:27Z",
  "last_reviewer": "kravens",
  "author_silent_days": 3,
  "waiting_on_author_days": 3,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   2802,
   3652,
   6835,
   10370,
   14053
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 2802,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Add an address-based transaction index"
   },
   {
    "number": 3652,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Adds transaction index by address (updates #2802 to current master)"
   },
   {
    "number": 6835,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Add address-based index"
   },
   {
    "number": 10370,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "[pull request idea] addressindex, spentindex, timestampindex (Bitcore patches)"
   },
   {
    "number": 14053,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Add address-based index (attempt 4?)"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/index/txoscriptindex_key.h",
  "src/rpc/blockchain.cpp"
 ],
 "body": "Add a new index `txoscriptindex` which indexes all output scripts. Introduce a new `getscripthistory` RPC that returns all transactions that create outputs corresponding to the provided scripts, and optionally the transactions that spend those outputs if `txospenderindex` is also configured.\n\nThis has been a long-requested feature, attempted in previous PRs (#2802, #3652, #6835, #10370, #14053).\nSome concerns raised were about the ability for an index like this to scale. I think the new techniques being used to build the indexes (https://github.com/bitcoin/bitcoin/pull/35531, https://github.com/bitcoin/bitcoin/pull/36002) alleviate this concern. This index can be synced in a few hours and the current disk usage is ~52 GB. This is less than the ~66 GB `txindex` disk usage in v31 and earlier. This index can also be used with a pruned node, so a full node can be supported in < 100 GB of disk space. Missing blocks are returned by hash and can be requested from peers using `getblockfrompeer`.\n\nSome other previous concerns were more conceptual, that this type of index could encourage address reuse and chain analysis. IMO this ship has sailed, and the current status quo of not shipping an address index is turning off users who expect this functionality. Workarounds involve installing, configuring, and syncing other third-party software, making the UX of using Bitcoin Core in a self-sovereign way unnecessarily difficult.\n\nThe index hashes each output script with SHA256, then applies salted SipHash-1-3-UJ and stores a six-byte prefix. This is to support the popular Electrum protocol, which supplies only the script hash. The RPC can also look up by address or descriptors. Ranged descriptors are also supported, so a single RPC call can return the entire history of a receive/change xpub pair.\n\nBecause there are some scripts with millions of outputs, we introduce a new `OrderedVarInt` which serializes the block height after the script hash prefix. This lets LevelDB seek directly to the prefix + block height, so we can page by block height range.\n\nTo prevent DoS concerns by supplying an address with millions of outputs, the RPC tracks a byte budget of reads and throws immediately if too many reads are attempted. The error message instructs the user to narrow the block range.\n\nImprovements to `txospenderindex` (https://github.com/bitcoin/bitcoin/pull/36480) will also allow us to use that pruned. The end result would allow users to run with all optional indexes (including this one) in < 200 GB of disk space. Of course a larger pruned setting would be desired if many historical block lookups were being requested.",
 "commits": [
  {
   "sha": "f8a98778bbf69709e2ab1947e7ca66be6786715b",
   "date": "2026-10-25T20:05:38Z",
   "message": "serialize: add OrderedVarIntFormatter"
  },
  {
   "sha": "f9d4d0af818b004a7fa6958878e5341cf0384818",
   "date": "2026-10-29T03:25:56Z",
   "message": "index: add txoscriptindex"
  },
  {
   "sha": "bf0377cc8285901ead16460a8f46f9d6401d03f5",
   "date": "2026-10-29T03:25:56Z",
   "message": "index: expose spender transaction offsets"
  },
  {
   "sha": "0843f7b5c971913a28958c2c1441410fa8ae954c",
   "date": "2026-10-29T03:25:56Z",
   "message": "rpc: share transaction JSON helpers"
  },
  {
   "sha": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
   "date": "2026-10-29T14:30:06Z",
   "message": "rpc: add getscripthistory"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-23T17:40:00Z",
   "kind": "force_push",
   "who": "andrewtoth",
   "commit": "614302e7d14a16fe28a2e8945fa99165693fcf94",
   "id": "push:614302e7d14a16fe28a2e8945fa99165693fcf94"
  },
  {
   "t": "2026-10-23T18:54:49Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Concept ACK",
   "id": "c:6372429119",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6372429119",
   "hash": "e8334cc43879b73c",
   "edited": null
  },
  {
   "t": "2026-10-23T19:05:41Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Concept ACK",
   "id": "c:6372628097",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6372628097",
   "hash": "e8334cc43879b73c",
   "edited": null
  },
  {
   "t": "2026-10-23T20:00:31Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Strong Concept ACK",
   "id": "c:6373603191",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6373603191",
   "hash": "8dffa6cbd75a4165",
   "edited": null
  },
  {
   "t": "2026-10-23T22:56:57Z",
   "kind": "review",
   "who": "kravens",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "614302e7d14a16fe28a2e8945fa99165693fcf94",
   "text": "Concept ACK.\n\nReviewed 614302e7d14a16fe28a2e8945fa99165693fcf94. I built it and ran `txoscriptindex_tests`, `baseindex_tests`, `rpc_getscripthistory.py`, `rpc_misc.py`, `feature_init.py` and `feature_assumeutxo.py`, all passing. I also did a manual regtest run: address, Electrum scripthash, ranged `wpkh()` descriptor, verbosity 0/1/2, and spender linking. Results matched expectations. My comments are mostly about structure:\n\n**1. The lookup budget does not count block and undo reads.** At verbosity 2, `tx_to_json` reads the full block and its undo data for every block that holds a non-coinbase match (`rpc/blockchain.cpp:4116`). Step 10 also reads a full block whenever two or more extra spenders share one (`rpc/blockchain.cpp:4064`). Neither read is charged. I ran this on regtest with 150 matches, each in its own block padded to ~60 kB. Bytes read by bitcoind during one call (`rchar`):\n\n| verbosity | matching tx bytes | bytes read by node |\n|---|---|---|\n| 0 | 52,039 | 0.94 MB |\n| 2 | 52,039 | 21.2 MB |\n\nOn mainnet that means one full block plus its undo data per matching block. A query that fits in the 50 MiB budget can therefore read far more than 50 MiB. Either charge these reads, or say in the help text that the budget does not cover them.\n\n**2. `OrderedVarInt` could be `BigEndianFormatter<3>`.** Big-endian already sorts by height, and the same key already uses it for the tx offset. Both encodings are 3 bytes for heights 16,512 through 2,113,663 (roughly until 2048). After that, BE3 stays at 3 bytes while `OrderedVarInt` grows to 4, and BE3 goes up to height 16,777,215. I tried it locally. It removes `ordered_varint.h` and its tests (+4/\u2212210), and the PR's unit and functional tests still pass:\n\n```diff\n+    static constexpr uint32_t HEIGHT_SIZE{3};\n ...\n-                  Using<OrderedVarIntFormatter>(obj.height),\n+                  Using<BigEndianFormatter<HEIGHT_SIZE>>(obj.height),\n```\n\n**3. Ordering spenders doesn't need a block read.** `txospenderindex` keys already store a `CDiskTxPos`, and its `nTxOffset` grows with the transaction's position in the block. I added it to `TxoSpender` and sorted extra spenders by `(height, tx_offset)`. That removes the grouping loop and the `GetBlockChecked` call (+6/\u221220). `test_confirmed_transaction_order` still passes. It fails if the offset is dropped from the comparison, so the test does cover this.\n\n**4. `Find` could take the whole set of script hashes.** Today the RPC calls `Find` once per hash and merges results by `(height, tx_offset)`. A transaction paying two scripts of one descriptor is read and charged twice; `rpc_getscripthistory.py` relies on this (\"repeated reads of overlapping transactions exceed the work budget\"). A multi-hash `Find` would read each candidate once and drop both `ScriptHistoryMatch::tx_offset` and the `by_position` merge. The budget would then trip later than it does now, never earlier, so that test case would need updating. Returning pruned blocks as their own set instead of null-`tx` placeholders would also remove the duplicated branch at `index/txoscriptindex.cpp:163` and `:174`, and the matching filter in the RPC.\n\n**5. One budget, one error.** Index lookups charge a `uint64_t&` and fail with \"Script history index lookup limit exceeded\u2026\". Extra transaction bodies throw a `std::runtime_error` with \"Script history exceeds the \u2026 byte budget\" (`rpc/blockchain.cpp:3933`). Both enforce the same limit through two code paths with two messages. The index-layer message also names RPC options (`index/txoscriptindex.cpp:138`). A small budget type that both layers charge, with the user-facing wording kept in the RPC, would be simpler.\n\n**6. Could the RPC go in its own file?** `blockchain.cpp` grows by ~530 lines to 4,238. The handler is one ~400-line lambda that opens with a 12-step index comment. `src/rpc/scripthistory.cpp` with one function per step would be easier to read and review.\n\n**7. `TxToJSON` is now declared in `rawtransaction_util.h`.** That header belongs to `bitcoin_common`, which the wallet also links. `TxToJSON` takes a `Chainstate` and is still defined in `rawtransaction.cpp` in `bitcoin_node`. Declaring it in a node-side header would keep that boundary clean.\n\nNits:\n- The mempool scan matches against `scripts` or `script_hashes` depending on how the query was given (`rpc/blockchain.cpp:3986`). Hashing everything up front would leave one set and remove the branch.\n- When `range` is given, the descriptor is parsed twice: once at `rpc/blockchain.cpp:3896` and again inside `EvalDescriptorStringOrObject`.\n- `RawTransactionDoc` changes the `getrawtransaction` help text:\n  - `blockhash` moves below `vsize_adjusted` and gains \"(omitted for mempool transactions)\".\n  - The `hex` description changes.\n  - At verbosity 2, `fee` moves to the end.\n\n  Probably fine, but worth a line in the commit message.\n\n---\nDisclosure: AI (Claude Opus 5.5) was used to find these issues and to draft this review.",
   "id": "r:5635280324",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5635280324",
   "hash": "bfb3806f81ac78e6",
   "edited": null
  },
  {
   "t": "2026-10-25T20:14:32Z",
   "kind": "force_push",
   "who": "andrewtoth",
   "commit": "cf9b981fcc7ae52c7864beae35df5d60b5c322b4",
   "id": "push:cf9b981fcc7ae52c7864beae35df5d60b5c322b4"
  },
  {
   "t": "2026-10-26T08:24:50Z",
   "kind": "review_comment",
   "who": "romanz",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/txoscriptindex_key.h",
   "commit": "a221edb4cb7ae066133766e310abfe5b29de1656",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nElectrum 1.7.x protocol is now using [scriptPubKey-based subscription and queries](https://electrum-protocol.readthedocs.io/en/latest/protocol-changes.html#version-1-7), so maybe it would simpler to avoid SHA256 here?",
   "id": "rc:4375204072",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#discussion_r4375204072",
   "hash": "a8950496634f1e89",
   "edited": "2026-10-26T18:42:44Z"
  },
  {
   "t": "2026-10-26T18:42:43Z",
   "kind": "review",
   "who": "romanz",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "cf9b981fcc7ae52c7864beae35df5d60b5c322b4",
   "text": "Concept ACK, thanks!",
   "id": "r:5649167214",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5649167214",
   "hash": "432f9270b8df3098",
   "edited": null
  },
  {
   "t": "2026-10-26T19:23:24Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/rpc/blockchain.cpp",
   "commit": "cf9b981fcc7ae52c7864beae35df5d60b5c322b4",
   "in_reply_to": null,
   "text": "In \"rpc: add getscripthistory\" cf9b981fcc7ae52c7864beae35df5d60b5c322b4\n\nnit: `getscripthistory` help writes \"Bitcoin byte order\" which doesn't seem to be used elsewhere in the repo. I believe \"reversed byte order\" is a better fit",
   "id": "rc:4381511296",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#discussion_r4381511296",
   "hash": "d4675ec2892f30e4",
   "edited": "2026-10-26T22:06:43Z"
  },
  {
   "t": "2026-10-26T19:27:08Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/rpc/blockchain.cpp",
   "commit": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
   "in_reply_to": null,
   "text": "In \"rpc: add getscripthistory\" cf9b981fcc7ae52c7864beae35df5d60b5c322b4\n\nnit: `getscripthistory` help could say `pruned_blocks` blocks can be fetched with `getblockfrompeer` and then retry. It also could also state that `pruned_blocks` can contain false positives (blocks where no tx match the output script).",
   "id": "rc:4381546376",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#discussion_r4381546376",
   "hash": "957572ae19ac5094",
   "edited": "2026-10-26T22:06:43Z"
  },
  {
   "t": "2026-10-26T22:06:43Z",
   "kind": "review",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "cf9b981fcc7ae52c7864beae35df5d60b5c322b4",
   "text": "Concept ACK. I skimmed the code but plan to take a deeper look. Commented 2 nits.",
   "id": "r:5657759713",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5657759713",
   "hash": "15d72580f53b79c9",
   "edited": null
  },
  {
   "t": "2026-10-29T03:26:02Z",
   "kind": "force_push",
   "who": "andrewtoth",
   "commit": "5735ea91cd6f9527fea2c521c6d3a6bf855cd4f6",
   "id": "push:5735ea91cd6f9527fea2c521c6d3a6bf855cd4f6"
  },
  {
   "t": "2026-10-29T14:30:13Z",
   "kind": "force_push",
   "who": "andrewtoth",
   "commit": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
   "id": "push:1fc5b618e3693c824124dd4ee4c3c95e100e645d"
  },
  {
   "t": "2026-10-29T14:31:35Z",
   "kind": "comment",
   "who": "andrewtoth",
   "assoc": "MEMBER",
   "text": "Thanks @kravens, @romanz, and @ViniciusCestarii for the reviews, and @sedited, @l0rinc, and @w0xlt for the concept ACKs!\n\nI took some of the suggestions, and made some additional updates. This changes the index format so anyone who was testing with the previous version needs to rebuild it.\n\n- More compact index layout for duplicate scripts in the same block. Instead of one row per output, we group all identical outputs in the same block and serialize the offsets between each of them as the value corresponding to a single key for that script and block height.\n- Increase the prefix size from 5 bytes to 6 bytes. While 5 bytes is sufficient for the txindex, a collision in this index has a much worse case. If a script with none or a few outputs being looked up collides with an output that has millions of outputs, it will require scanning through all the block ranges and reading all the false positive outputs.\n- Added an `asof` optional parameter to fix a blockheight when paging through requests. This let's a caller make repeated queries that set the spentness of outputs based on the `asof` block.\n- Bound the undo and block reads to the lookup byte budget and throw if exceeded.\n- Expose `tx_offset` from `txospenderindex` and sort spenders with it.\n- Moved `TxToJSON` to `rpc/rawtransaction.h`\n- Various documentation changes, including release notes.\n- Added more test coverage to kill some mutants.",
   "id": "c:6499492095",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6499492095",
   "hash": "24c423776cb530a0",
   "edited": null
  },
  {
   "t": "2026-10-29T14:32:38Z",
   "kind": "review_comment",
   "who": "andrewtoth",
   "assoc": "MEMBER",
   "path": "src/index/txoscriptindex_key.h",
   "commit": "a221edb4cb7ae066133766e310abfe5b29de1656",
   "in_reply_to": 4375204072,
   "text": "I'm not sure it will make it much simpler. I think it's worth it to keep this for backwards compatibility.",
   "id": "rc:4413653606",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#discussion_r4413653606",
   "hash": "30fb49a22be259c1",
   "edited": null
  },
  {
   "t": "2026-10-29T16:53:41Z",
   "kind": "comment",
   "who": "xyzconstant",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK",
   "id": "c:6502969751",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#issuecomment-6502969751",
   "hash": "e8334cc43879b73c",
   "edited": null
  },
  {
   "t": "2026-10-29T18:10:27Z",
   "kind": "review",
   "who": "kravens",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
   "text": "tACK 1fc5b618e3693c824124dd4ee4c3c95e100e645d\n\nRe-reviewed the changes since 614302e7 (same base, no rebase). Built it and ran `txoscriptindex_tests`, `txospenderindex_tests`, `ordered_varint_tests`, `caches_tests`, `baseindex_tests`, `rpc_getscripthistory.py`, `rpc_misc.py`, `rpc_gettxspendingprevout.py`, `feature_init.py` and `feature_assumeutxo.py`. All pass.\n\nFrom my earlier comments, 1 (block and undo reads outside the budget), 3 (spender ordering without a block read) and 7 (`TxToJSON` header) are fixed: `tx_to_json` now charges the serialized undo and block (`rpc/blockchain.cpp:4154`, `:4159`), `test_undo_size_limit` covers the limit, and the step-10 block read is gone. 2, 4, 5 and 6 were suggestions; no objection to leaving them.\n\nWhat I checked in the new layout:\n\n- One row per (prefix, height); the value is a run of VARINT offset deltas read until the stream is empty. `Serialize` rejects empty, non-increasing and out-of-block offsets, and `Unserialize` caps the count at `MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT`. Keys, values and transaction bodies are all charged to the budget.\n- `Find` keeps one block file open across rows and seeks with `SEEK_SET`, which keeps the `AutoFile` obfuscation position correct (`streams.cpp:41`). Matches still come back in (height, offset) order, so the `by_position` merge in the RPC holds.\n- `CustomRemove` erases the (prefix, height) key and a competing block at the same height overwrites it; `txoscriptindex_reorg_erases_rows` covers the rewind.\n- 6-byte prefix: with roughly 2^31 distinct scripts on mainnet, the chance that a queried script shares its bucket with any other script drops from about 2^-9 to 2^-17. Agree with the change.\n\n`asof`: I read it with the paging use case in mind and could not break it. Spentness at asof comes only from the matches and txospenderindex; spenders above asof are dropped by the ancestor check at `rpc/blockchain.cpp:4097` and the output stays unspent. Reorgs above asof leave the answer unchanged, reorgs through asof fail the final check, and confirmations and times come from the asof block via `TxToUniv`, so a page stays identical while the tip moves. `test_asof` exercises all of this.\n\nObservations, none blocking:\n\n1. **asof could be served during the initial index sync.** `BlockUntilSyncedToCurrentChain()` returns false until the whole sync finishes (`index/base.cpp:444`), so a node still building either index answers nothing, even for an asof block both indexes passed long ago. The ancestor checks at `rpc/blockchain.cpp:3954` and `:3960` already guarantee that every row up to asof exists (the summary's best block lags the DB by up to 30 s during the initial sync and never leads it). Skipping the two `BlockUntilSyncedToCurrentChain()` calls when asof is given would let a server start paging old history while the index catches up. Fine as a follow-up.\n2. **Explicit `include_mempool=true` together with `asof` is silently ignored** (`rpc/blockchain.cpp:3890`). The help text says so, but an `RPC_INVALID_PARAMETER` would make the conflict visible to a caller who set both on purpose.\n3. **Release notes.** The summary mentions release notes, but the branch only touches `doc/files.md`. Did a `doc/release-notes-36629.md` miss the push?\n4. **`rpc: share transaction JSON helpers` still has an empty commit body.** It changes the `getrawtransaction` help layout (`blockhash` moves below `vsize_adjusted`, `fee` moves last at verbosity 2). One line in the message would help whoever bisects a help-text diff later.\n5. The \"test ancestor commits\" CI job died in `p2p_segwit.py` on an assertion that looks unrelated, before it reached the later commits, so the per-commit build check needs a re-run.\n\n---\nDisclosure: AI (Claude Fable 5.1) was used to find these issues and to draft this review.",
   "id": "r:5702703273",
   "url": "https://github.com/bitcoin/bitcoin/pull/36629#pullrequestreview-5702703273",
   "hash": "3ef8eb116b9c9b59",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-23T17:40:59Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-23T18:42:42Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-29T15:38:18Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-30T17:28:45Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 14109,
 "text_tokens_estimate": 3527,
 "changed_paths": [
  "doc/files.md",
  "src/CMakeLists.txt",
  "src/index/ordered_varint.h",
  "src/index/txoscriptindex.cpp",
  "src/index/txoscriptindex.h",
  "src/index/txoscriptindex_key.h",
  "src/index/txospenderindex.cpp",
  "src/index/txospenderindex.h",
  "src/init.cpp",
  "src/node/caches.cpp",
  "src/node/caches.h",
  "src/rpc/blockchain.cpp",
  "src/rpc/client.cpp",
  "src/rpc/node.cpp",
  "src/rpc/rawtransaction.cpp",
  "src/rpc/rawtransaction.h",
  "src/rpc/rawtransaction_util.cpp",
  "src/rpc/rawtransaction_util.h",
  "src/test/CMakeLists.txt",
  "src/test/baseindex_tests.cpp",
  "src/test/caches_tests.cpp",
  "src/test/fuzz/rpc.cpp",
  "src/test/ordered_varint_tests.cpp",
  "src/test/txoscriptindex_tests.cpp",
  "src/test/txospenderindex_tests.cpp",
  "test/functional/feature_assumeutxo.py",
  "test/functional/feature_init.py",
  "test/functional/rpc_getscripthistory.py",
  "test/functional/rpc_misc.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "doc/files.md",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/CMakeLists.txt",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/index/ordered_varint.h",
   "add": 87,
   "del": 0
  },
  {
   "path": "src/index/txoscriptindex.cpp",
   "add": 200,
   "del": 0
  },
  {
   "path": "src/index/txoscriptindex.h",
   "add": 101,
   "del": 0
  },
  {
   "path": "src/index/txoscriptindex_key.h",
   "add": 98,
   "del": 0
  },
  {
   "path": "src/index/txospenderindex.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/index/txospenderindex.h",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 11,
   "del": 0
  },
  {
   "path": "src/node/caches.cpp",
   "add": 6,
   "del": 0
  },
  {
   "path": "src/node/caches.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/rpc/blockchain.cpp",
   "add": 587,
   "del": 1
  },
  {
   "path": "src/rpc/client.cpp",
   "add": 9,
   "del": 0
  },
  {
   "path": "src/rpc/node.cpp",
   "add": 5,
   "del": 0
  },
  {
   "path": "src/rpc/rawtransaction.cpp",
   "add": 7,
   "del": 23
  },
  {
   "path": "src/rpc/rawtransaction.h",
   "add": 21,
   "del": 0
  },
  {
   "path": "src/rpc/rawtransaction_util.cpp",
   "add": 29,
   "del": 3
  },
  {
   "path": "src/rpc/rawtransaction_util.h",
   "add": 7,
   "del": 0
  },
  {
   "path": "src/test/CMakeLists.txt",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/test/baseindex_tests.cpp",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/test/caches_tests.cpp",
   "add": 14,
   "del": 0
  },
  {
   "path": "src/test/fuzz/rpc.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/test/ordered_varint_tests.cpp",
   "add": 95,
   "del": 0
  },
  {
   "path": "src/test/txoscriptindex_tests.cpp",
   "add": 302,
   "del": 0
  },
  {
   "path": "src/test/txospenderindex_tests.cpp",
   "add": 9,
   "del": 1
  },
  {
   "path": "test/functional/feature_assumeutxo.py",
   "add": 10,
   "del": 1
  },
  {
   "path": "test/functional/feature_init.py",
   "add": 11,
   "del": 0
  },
  {
   "path": "test/functional/rpc_getscripthistory.py",
   "add": 609,
   "del": 0
  },
  {
   "path": "test/functional/rpc_misc.py",
   "add": 3,
   "del": 2
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 1064,
 "git": {
  "head": "1fc5b618e3693c824124dd4ee4c3c95e100e645d",
  "head_matches_backup": true,
  "base": "df0dc1e2d2059213e49a9f115fd55a9f0119d26c",
  "patch_id": "86865abb6149af5c",
  "commits": [
   {
    "sha": "f8a98778bb",
    "subject": "serialize: add OrderedVarIntFormatter",
    "files": 3,
    "add": 183,
    "del": 0
   },
   {
    "sha": "f9d4d0af81",
    "subject": "index: add txoscriptindex",
    "files": 16,
    "add": 760,
    "del": 3
   },
   {
    "sha": "bf0377cc82",
    "subject": "index: expose spender transaction offsets",
    "files": 3,
    "add": 12,
    "del": 1
   },
   {
    "sha": "0843f7b5c9",
    "subject": "rpc: share transaction JSON helpers",
    "files": 4,
    "add": 64,
    "del": 26
   },
   {
    "sha": "1fc5b618e3",
    "subject": "rpc: add getscripthistory",
    "files": 6,
    "add": 1215,
    "del": 1
   }
  ],
  "patch_truncated": true
 },
 "patch_id": "86865abb6149af5c",
 "input_hash": "14aa835746ccb911",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}