{
 "number": 34489,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34489",
 "title": "index: batch db writes during initial sync",
 "author": "furszy",
 "author_association": "MEMBER",
 "created_at": "2026-02-03T04:06:34Z",
 "updated_at": "2026-08-28T16:30:42Z",
 "age_days": 226,
 "draft": true,
 "labels": [
  "UTXO Db and Indexes",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "1497086cdb0e64df861100e0715ffd975d4c41cf",
 "head_ref": "2026_index_batch_processing",
 "head_repo": "furszy/bitcoin-core",
 "head_history": [
  {
   "t": "2026-02-03T04:42:15Z",
   "sha": "cf065aa603769458f475e1180fd4924d68023695"
  },
  {
   "t": "2026-02-03T19:44:16Z",
   "sha": "dd76491174b3a3b60c174822f75abb03fda3b1d4"
  },
  {
   "t": "2026-02-03T20:25:04Z",
   "sha": "a2f84476bf3cfdb4bb20193d1253a9ca131f93a9"
  },
  {
   "t": "2026-02-04T15:17:49Z",
   "sha": "8826900ddc5e4a57b10d4757da76acf4e5cec7f6"
  },
  {
   "t": "2026-02-08T16:55:13Z",
   "sha": "6f66ff7e0d4424c48e0e27e9fa72d26033ba6851"
  },
  {
   "t": "2026-02-09T19:58:37Z",
   "sha": "d0f793ac61a0659f4f1e89f7943813244499bf7d"
  },
  {
   "t": "2026-02-10T01:11:54Z",
   "sha": "7ab454d82f4328d44f7821c8e1b8183672b47adc"
  },
  {
   "t": "2026-02-10T01:22:27Z",
   "sha": "3687ead892d207ddbdfec3f2ef5c086130414d9a"
  },
  {
   "t": "2026-02-14T16:55:06Z",
   "sha": "c2953e446d53a8ac86ae3e4801208df5c9c6d2cc"
  },
  {
   "t": "2026-02-16T01:46:23Z",
   "sha": "79e19f5fb5eb94933517219a60a5e17056fab783"
  },
  {
   "t": "2026-02-16T02:16:09Z",
   "sha": "bb3e4a0c07d7a37645c789053bfecbd029e26f84"
  },
  {
   "t": "2026-02-16T04:44:10Z",
   "sha": "ede2eabfe218fe98994c694e2c52af98a94d0120"
  },
  {
   "t": "2026-02-18T17:17:59Z",
   "sha": "0bebf13c0eed05c9b641e18b7294edad20903e12"
  },
  {
   "t": "2026-03-02T14:28:20Z",
   "sha": "47b02354c97ac7b0442b2ccbd02d66f212537219"
  },
  {
   "t": "2026-03-02T14:34:04Z",
   "sha": "224a638a9cff1fda9cf28438989acb1817458cba"
  },
  {
   "t": "2026-03-02T21:32:38Z",
   "sha": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d"
  },
  {
   "t": "2026-03-04T20:25:57Z",
   "sha": "5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a"
  },
  {
   "t": "2026-03-08T18:10:01Z",
   "sha": "1497086cdb0e64df861100e0715ffd975d4c41cf"
  }
 ],
 "additions": 300,
 "deletions": 74,
 "changed_files": 11,
 "commit_count": 10,
 "size_bucket": "M",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "arejula27",
      "url": "https://github.com/bitcoin/bitcoin/pull/34489#issuecomment-3902437705"
     },
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/34489#pullrequestreview-3744459738"
     },
     {
      "login": "polespinasa",
      "url": "https://github.com/bitcoin/bitcoin/pull/34489#pullrequestreview-4372795917"
     }
    ],
    "stale_ack": [
     {
      "login": "optout21",
      "url": "https://github.com/bitcoin/bitcoin/pull/34489#issuecomment-3874191155"
     }
    ]
   },
   "conflicts": [
    {
     "number": 34440,
     "title": "Refactor CChain methods to use references, tests",
     "author": "optout21"
    },
    {
     "number": 32875,
     "title": "index: handle case where pindex_prev equals chain tip in NextSyncBlock()",
     "author": "HowHsu"
    }
   ]
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-03-09T17:37:39Z",
   "stale": false
  },
  "optout21": {
   "kind": "ack",
   "hash": "3687ead892d207ddbdfec3f2ef5c086130414d9a",
   "t": "2026-02-09T22:24:52Z",
   "stale": true
  },
  "arejula27": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-02-12T17:39:50Z",
   "stale": false
  },
  "polespinasa": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-05-27T14:21:36Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 3,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 1,
  "distinct_reviewers": [
   "Jhackman2019",
   "arejula27",
   "bvbfan",
   "fjahr",
   "hebasto",
   "l0rinc",
   "maflcko",
   "optout21",
   "polespinasa",
   "sedited",
   "sipa"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-08-23T22:10:07Z",
  "last_reviewer_activity": "2026-08-28T16:30:42Z",
  "last_reviewer": "l0rinc",
  "author_silent_days": 24,
  "waiting_on_author_days": 19,
  "days_since_update": 19
 },
 "refs": {
  "mentioned": [
   26966,
   32875,
   32878,
   33306,
   34897,
   35531,
   35847
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34897,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-09",
    "title": "indexes: Don't commit ahead of the flushed chainstate"
   },
   {
    "number": 26966,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "index: initial sync speedup, parallelize process"
   },
   {
    "number": 32875,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-03-30",
    "title": "index: handle case where pindex_prev equals chain tip in NextSyncBlock()"
   },
   {
    "number": 32878,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-08-19",
    "title": "index: fix wrong assert of current_tip == m_best_block_index"
   },
   {
    "number": 33306,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "index: Force database compaction in coinstatsindex"
   },
   {
    "number": 35531,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-15",
    "title": "txindex: hash keys and pack positions to reduce disk usage"
   },
   {
    "number": 35847,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-14",
    "title": "test: move more tests to `baseindex_tests` and run them for all indexes"
   }
  ],
  "conflicts": [
   34440,
   32875
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/index/base.cpp",
  "src/index/base.h",
  "src/index/blockfilterindex.cpp",
  "src/index/blockfilterindex.h",
  "src/index/txindex.cpp",
  "src/test/blockfilter_index_tests.cpp"
 ],
 "body": "Pending on #34897 review. Please go there first.\n\n---------------------------------\n\nDecouples part of #26966.\n\nRight now, index initial sync writes to disk for every block, which is not the best for HDD. This change batches those, so disk writes are less frequent during initial sync. This also cuts down `cs_main` contention by reducing the number of `NextBlockSync` calls (instead of calling it for every block, we will call it once per block window), making the node more responsive (IBD and validation) while indexes sync up. On top of that, it lays the groundwork for the bigger speedups, since part of the parallelization pre-work is already in place.\n\nJust as a small summary:\n\n* Batch DB writes instead of flushing per block, which improves sync time on HDD due to the reduced number of disk write operations.\n* Reduce `cs_main` lock contention, which improves the overall node responsiveness (and primarily IBD) while the indexes threads are syncing.\n* Lays the groundwork for the real speedups, since part of #26966 parallelization pre-work is introduced here as well.\n* Reduces the number of generated LDB files. See l0rinc's [comment](https://github.com/bitcoin/bitcoin/pull/34489#issuecomment-3876972773).",
 "commits": [
  {
   "sha": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "date": "2026-03-04T20:19:53Z",
   "message": "index: sync, update iterator only after processing block\n\nNo behavior change. This is just for correctness."
  },
  {
   "sha": "0cbe84d36d418ed4a401643e2b36d9d325167111",
   "date": "2026-03-04T20:19:53Z",
   "message": "index: refactor, decouple interruption from sync loop\n\nNo behavior change."
  },
  {
   "sha": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "date": "2026-03-04T20:19:53Z",
   "message": "index: add method to process block ranges\n\nNo behavior change.\n\nIntroduce ProcessBlocks(start, end) to handle a range of blocks\nin forward order. Currently used per-block, but this lays the\nfoundation for future batch processing and parallelization.\n\nThis has the nice property of allowing us to collect the block\nindexes we are about to process without locking 'cs_main'. Just\nby traversing the chain backwards via each block index pprev."
  },
  {
   "sha": "0e9f2aa7eaa9888884a2f6280726188ce3abcad7",
   "date": "2026-03-08T18:09:07Z",
   "message": "index: prepare NextSyncBlock to return block ranges\n\nThis is a refactoring that makes NextSyncBlock easily adaptable to\nreturn block ranges instead of single blocks in the subsequent commit.\n\nIt also adds a fast-path that avoids calling 'FindFork()' and 'Next()'\nwhen the index is synced.\n\nCo-authored-by: Hao Xu <hao.xu@linux.dev>"
  },
  {
   "sha": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "date": "2026-03-08T18:09:08Z",
   "message": "index: make NextSyncBlock return block ranges\n\nNo behavior change."
  },
  {
   "sha": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "date": "2026-03-08T18:09:08Z",
   "message": "index: add block range computation\n\nSet end of the window of blocks an index will process at time.\nStopping at either the configured window size or the chain tip.\n\nThis is the first step toward batch and parallel processing.\nThese block windows become units of work and live in memory until\nflushed to disk in one go.\n\nSince each index has a different data size, the range size\nis configurable so it can be tuned considering memory\nconsumption."
  },
  {
   "sha": "e4b1fd09e49d2840317300fa6ad89a18aa094411",
   "date": "2026-03-08T18:09:08Z",
   "message": "index: enable DB writes batching during sync\n\nPass CDBBatch to subclasses so writes can be accumulated\nand committed together instead of flushed per block\n\nNote:\nBatch writes were intentionally not tied to the existing Commit() method.\nThe rationale is to bound memory consumption as batches accumulate in RAM\nuntil flushed to disk. This leaves two separate workflows with different\npurposes:\n\n1) Batch writes flushes data for a block range atomically: either all\nblocks in the batch land in LevelDB or none do.\n\n2) Commit() is about progress checkpointing: it writes the best block\nlocator alongside any other index state position (e.g. last written\nfile for the block filter index - which requires an fsync call), so the\nnode always has a consistent recovery point after an interruption or crash.\n\nReprocessing data after a crash is not really a problem because we would\njust overwrite the existing one."
  },
  {
   "sha": "7c82cc88e8e04c2c32070bca32aaddc15f1fb7ac",
   "date": "2026-03-08T18:09:08Z",
   "message": "test: index, add initial sync batch writes coverage"
  },
  {
   "sha": "28d8b04defeabecd0b7bb5d0d25d1700ade57c78",
   "date": "2026-03-08T18:09:09Z",
   "message": "test: index, add coverage for initial sync reorgs"
  },
  {
   "sha": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "date": "2026-03-08T18:09:09Z",
   "message": "test: Add coverage for index locator persistence during shutdown\n\nVerifies the index persists the last processed index when interrupted."
  }
 ],
 "timeline": [
  {
   "t": "2026-02-03T04:42:15Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "cf065aa603769458f475e1180fd4924d68023695"
  },
  {
   "t": "2026-02-03T09:42:49Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "cf065aa603769458f475e1180fd4924d68023695",
   "in_reply_to": null,
   "text": "Why keep `ProcessBlock` around? It should be simpler if we just have one function and it can be handed one or many blocks."
  },
  {
   "t": "2026-02-03T09:45:14Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "Has this been optimized with benchmarks or are you planning to do that? This might be something differs between the indexes so it might make sense to let each index define their own value."
  },
  {
   "t": "2026-02-03T09:47:14Z",
   "kind": "review",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "cf065aa603769458f475e1180fd4924d68023695",
   "text": "This relates to what you wrote [here](https://github.com/bitcoin/bitcoin/pull/33306#issuecomment-3832258602) right? It would be helpful that you check if it solves the LevelDB file issue in coinstatsindex and I am also curious about your benchmark results because @l0rinc did not find that this was  leading to a speed up."
  },
  {
   "t": "2026-02-03T10:06:56Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "cf065aa603769458f475e1180fd4924d68023695",
   "in_reply_to": 2758171790,
   "text": "Or, if you just want to keep it for encapsulation, consider making it private at least?"
  },
  {
   "t": "2026-02-03T10:38:27Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK, I prefer this over https://github.com/bitcoin/bitcoin/pull/33306 - and will investigate if this solves that problem once my benchmarking servers free up.\n\n[quoted text omitted]\nI experimented with something similar in https://github.com/l0rinc/bitcoin/pull/37/changes, will compare it against this change. I wrote:\n[quoted text omitted]\nIt solved the fragmentation, but didn't speed up anything.\nI still think this is a better direction than adding manual compactions.\n\nMost likely since writing isn't the bottleneck but MuHash calculations were."
  },
  {
   "t": "2026-02-03T15:02:16Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\n@fjahr the comment was merely an excuse to decouple the DB writes batching code out of #26966 rather than me having a formed opinion in favor or against #33306. That's why I didn't mention it in the PR description. Maybe the changes complement each other.\n\nTo be crystal clear, just updated the PR description with further details on why this change worth alone, independently on #33306.\n\nTo summarize it here, the goal of the changes are:\n* Batch DB writes instead of flushing per block, which will improve sync time on HDD due to the reduced number of IO operations.\n* Reduce `cs_main` lock contention, which orthogonally improves the overall node responsiveness (and primarily IBD) while the indexes threads are syncing.\n*  Lays the groundwork for the real speedups, since part of #26966 parallelization pre-work is introduced here as well."
  },
  {
   "t": "2026-02-03T15:13:10Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "[quoted text omitted]\n\nLet's handle it in a focused follow-up so we don't lose momentum here tuning a parameter.\n\nI'd rather land the structural improvements first, since they unblock other improvements and allow us to continue working on the parallelization goal (which introduces the major sync speedup)."
  },
  {
   "t": "2026-02-03T15:30:08Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "cf065aa603769458f475e1180fd4924d68023695",
   "in_reply_to": 2758171790,
   "text": "[quoted text omitted]\n\nBecause `ProcessBlocks` will be specialized in a follow-up commit (please see #26966) to enable additional speedups, such as processing blocks out-of-order for indexes that support it.\n\nAlso, I wouldn't merge them because these two methods solve different problems. `ProcessBlocks` handles ordering, `ProcessBlock` handles a single unit of work. I think keeping them separate keeps the code clean and easy to follow while allowing us to introduce improvements in the future.\n\n[quoted text omitted]\n~~Yeah, could do that.~~\n\njust realized that `ProcessBlocks` is private already."
  },
  {
   "t": "2026-02-03T19:44:16Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "dd76491174b3a3b60c174822f75abb03fda3b1d4"
  },
  {
   "t": "2026-02-03T20:25:04Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "a2f84476bf3cfdb4bb20193d1253a9ca131f93a9"
  },
  {
   "t": "2026-02-03T20:26:03Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Updated per feedback from @hebasto (thanks!).\nChanged `<cinttypes>` include for `<cstdint>` to make IWYU happy."
  },
  {
   "t": "2026-02-03T20:38:31Z",
   "kind": "comment",
   "who": "hebasto",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\n~I guess, this is a bug in IWYU caused by https://github.com/include-what-you-use/include-what-you-use/commit/44480a2de0d8ef039b13391997f274ea33750be9.~\n\n**UPDATE:** Fixed in https://github.com/bitcoin/bitcoin/pull/34498."
  },
  {
   "t": "2026-02-04T13:57:24Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Can you run all unit and functional tests on all commits? Or do they time out?"
  },
  {
   "t": "2026-02-04T15:17:49Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "8826900ddc5e4a57b10d4757da76acf4e5cec7f6"
  },
  {
   "t": "2026-02-04T15:22:03Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nBad squash, my bad. Thanks for the heads up. Fixed.\nAlso rebased the branch to pick up the CI changes."
  },
  {
   "t": "2026-02-08T02:54:45Z",
   "kind": "comment",
   "who": "Jhackman2019",
   "assoc": "NONE",
   "text": "Builds clean on ARM64 (Pi 5, Debian Bookworm). All index-related functional tests and blockfilter_index unit tests pass.\n:)"
  },
  {
   "t": "2026-02-08T08:31:24Z",
   "kind": "review_comment",
   "who": "bvbfan",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/txindex.cpp",
   "commit": "8826900ddc5e4a57b10d4757da76acf4e5cec7f6",
   "in_reply_to": null,
   "text": "This function doesn't exist anymore."
  },
  {
   "t": "2026-02-08T16:55:13Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "6f66ff7e0d4424c48e0e27e9fa72d26033ba6851"
  },
  {
   "t": "2026-02-08T16:55:21Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/txindex.cpp",
   "commit": "8826900ddc5e4a57b10d4757da76acf4e5cec7f6",
   "in_reply_to": 2778899461,
   "text": "Done, thanks."
  },
  {
   "t": "2026-02-09T14:06:09Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120",
   "in_reply_to": null,
   "text": "The start and end are set here, according to the desired size. However, `block_batch.start_index` can change below. Wouldn't it be better/cleaner to set it just before the `ProcessBlocks` call? (I mean to keep the logic up to the deleted `pindex = pindex_next;` line, and put the bactch setup there.)"
  },
  {
   "t": "2026-02-09T14:29:36Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "I am confused as to how this batching relates to the batching logic of the orginal code.\n~~Looking at the pre-change `BaseIndex::Sync()`, it seems that Commit/WriteBatch occured only every 30 seconds (or at the chain tip), not after each block. This change does a `WriteBatch` after every 500 blocks, which in fact may be even more frequent. The original commit-after-every-30-secs is still there, so I don't see how this change makes _less_ writes. Maybe I misunderstand the change.~~\n\n```\n            if (!ProcessBlock(pindex)) return; // error logged internally\n            ...\n            if (current_time - last_locator_write_time >= SYNC_LOCATOR_WRITE_INTERVAL) {\n                SetBestBlockIndex(pindex);\n                last_locator_write_time = current_time;\n                // No need to handle errors in Commit. See rationale above.\n                Commit();\n            }\n```"
  },
  {
   "t": "2026-02-09T16:39:51Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120",
   "in_reply_to": 2782838686,
   "text": "[quoted text omitted]\n\nIt seems the code I had for this case got dropped in the previous bad squash. Good catch.\nSee the missing line https://github.com/furszy/bitcoin-core/commit/54874bf43b2df6846061fbb844b7026f69e7bc74#diff-5251d93616c116c364d2d502ad2a59e901a3512194462fabacc9756f96fd8dddR274. Will re-add it. Thanks.\n\n[quoted text omitted]\nThat would not only require taking `cs_main` an extra time, but would also introduce race conditions.\nThe extra locking comes from the chain lookup needed to determine the window\u2019s end index. And, more importantly, if the tip changes between setting the start index and computing the end, we could end up on a fork at a lower height, resulting in an invalid window (start_index > end_index)."
  },
  {
   "t": "2026-02-09T18:02:26Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120",
   "in_reply_to": 2782838686,
   "text": "[quoted text omitted]\n\nWhat I had in mind is setting both the start and end a bit later, after the `pindex` is finalized.\nI will try it out and provide a concrete code snippet a bit later."
  },
  {
   "t": "2026-02-09T18:48:02Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\n`Commit()` only persists the locator (i.e. \"how far the index has synced\") so we can resume from that point after a restart. It does not flush or batch the per-block digested data.\n\nThe block data is written inside `CustomAppend()` which is called from `ProcessBlock()` per block, and it writes to the db immediately.\n\nWe're not replacing locator update time nor merging it with something else here, we're only batching something that previously wasn't batched at all.\n\nAlso, using a block window instead of reusing the existing time-based window is intentional. Time-based flushing makes memory usage hard to predict, and difficult to test (see #32878), because the amount of buffered data depends on runtime speed, which does not necessarily correlate with available memory (batched data stays in memory until it's flushed..). A fixed block window provides predictable memory usage and deterministic behavior, which is overall better and also integrates nicely with multiple workers sync (#26966). I would argue in favor of ditching the time window in the future, but.. that's not something related to this PR."
  },
  {
   "t": "2026-02-09T19:00:26Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120",
   "in_reply_to": 2782838686,
   "text": "[quoted text omitted]\n\nI see the same two problems there, with an extra requirement of a reorg check and another rewind call. Which makes it even worse."
  },
  {
   "t": "2026-02-09T19:58:37Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "d0f793ac61a0659f4f1e89f7943813244499bf7d"
  },
  {
   "t": "2026-02-09T20:00:10Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Updated per feedback, plus added test coverage for the introduced changes. Thanks @optout21."
  },
  {
   "t": "2026-02-09T21:52:24Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks for the explanations, and apologies for asking trivial/irrelevant questions.\n\n[quoted text omitted]\nThese are two important points, I can see now.\n\nI was puzzled by the fact that there is a loop in `Sync()`, and another loop is introduced in `ProcessBlocks()`. But the two complement each other.\n\nAnother approach would be to keep the outer loop in `Sync()` block-by-block, store a `CDBBatch` variable, process one block, count the blocks in the batch, and flush the batch if batch size is reached (outside of `ProcessBlock`). This would be slightly less change (no secondary loop), but -- if I'm correct -- the `cs_main` lock would be still accessed at every block.\n\n[quoted text omitted]\nMaybe `Commit()` could be called with the same granularity as batch writes, but that's out of scope here."
  },
  {
   "t": "2026-02-09T21:56:23Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120",
   "in_reply_to": 2782838686,
   "text": "I acknowledge that it's important to take the cs_main lock only once in the typical scenario.\nAlso, the missing update of end was identified and fixed! Thx"
  },
  {
   "t": "2026-02-09T22:11:44Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYes. That would still lock `cs_main` on every block, which harms the overall node responsiveness, and it is also not very helpful for the parallelization goal, which requires certain isolation between batches so worker threads can process them concurrently."
  },
  {
   "t": "2026-02-09T22:17:24Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "360c43d7c572f8cace50f1592ea4179f9f5c8663",
   "in_reply_to": null,
   "text": "Code review shows that by this point `start_index` and `end_index` are not null, but there is several if's to ensure that, it's not trivial. I propose to document/ensure this invariant with an `Assume` before this line, especially having in mind that `ProcessBlocks` crashes if start or end is `nullptr`."
  },
  {
   "t": "2026-02-09T22:18:25Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0d980feb9552ff50b9232c7c7151638c694057e3",
   "in_reply_to": null,
   "text": "nit: the \"// error logged internally\" comment could be passed on here as well."
  },
  {
   "t": "2026-02-09T22:22:37Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "A minor comment on the commit description: I feel an ambiguity or typo around \"during\".\n```\nPass CDBBatch to subclasses during so writes can be accumulated and committed together instead of flushing per block.\n```"
  },
  {
   "t": "2026-02-09T22:24:52Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "crACK 3687ead892d207ddbdfec3f2ef5c086130414d9a\n\nLGTM after clarifications and changes! Code review, no assessment on performance.\n\nPrev:\ncrACK d0f793ac61a0659f4f1e89f7943813244499bf7d"
  },
  {
   "t": "2026-02-10T01:11:54Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "7ab454d82f4328d44f7821c8e1b8183672b47adc"
  },
  {
   "t": "2026-02-10T01:12:04Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "0d980feb9552ff50b9232c7c7151638c694057e3",
   "in_reply_to": 2784804029,
   "text": "Done as suggested."
  },
  {
   "t": "2026-02-10T01:12:16Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2784820993,
   "text": "Fixed."
  },
  {
   "t": "2026-02-10T01:15:40Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "360c43d7c572f8cace50f1592ea4179f9f5c8663",
   "in_reply_to": 2784799921,
   "text": "Probably better to just pass them by ref instead. Will do if have to re-touch. Otherwise it can be tackled in a quick follow-up too."
  },
  {
   "t": "2026-02-10T01:19:15Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "nits addressed, thanks @optout21.\nAlso, just for completeness, added test coverage for the \"reorg during initial sync\" scenario that was not covered before."
  },
  {
   "t": "2026-02-10T01:22:27Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "3687ead892d207ddbdfec3f2ef5c086130414d9a"
  },
  {
   "t": "2026-02-10T11:15:26Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "I just finished measuring the size and file count for this PR (it was running previously for a week with the wrong `tail -F` grep I reused from https://github.com/bitcoin/bitcoin/pull/26966#pullrequestreview-2965376357)\n\nCoinStatsIndex DB: Before vs After Batch Writes (47c9297 \u2192 dd76491)\n\n```bash\nCOMMITS=\"47c9297172b0866d2b5d0de6b4fd35b798db3929 dd76491174b3a3b60c174822f75abb03fda3b1d4\"; \\\nDATA_DIR=\"/mnt/my_storage/BitcoinData\"; export DATA_DIR; \\\nwait_index() { tail -F ${DATA_DIR}/debug.log 2>/dev/null | grep -q -m1 'coinstatsindex is enabled'; killall bitcoind 2>/dev/null || true; sleep 2; }; export -f wait_index; \\\nfor COMMIT in $COMMITS; do \\\n  killall bitcoind 2>/dev/null || true; rm -rf \"$DATA_DIR/indexes\" \"$DATA_DIR/debug.log\"; \\\n  git fetch -q origin \"$COMMIT\" >/dev/null 2>&1 || true && git checkout -q \"$COMMIT\" && git log -1 --pretty='%h %s' \"$COMMIT\"; \\\n  cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C build >/dev/null 2>&1; \\\n  ./build/bin/bitcoind -datadir=\"$DATA_DIR\" -connect=0 -coinstatsindex=1 -printtoconsole=0 & wait_index; \\\n  du -h \"$DATA_DIR/indexes/coinstatsindex/db\"; \\\n  find \"$DATA_DIR/indexes/coinstatsindex/db\" -type f -name '*.ldb' | wc -l; \\\ndone\n```\n\n```bash\n[1]+  Done                    ./build/bin/bitcoind -datadir=\"$DATA_DIR\" -connect=0 -coinstatsindex=1 -printtoconsole=0\n47c9297172 Merge bitcoin/bitcoin#32420: mining, ipc: omit dummy extraNonce from coinbase\n[1] 3876795\n[1]+  Done                    ./build/bin/bitcoind -datadir=\"$DATA_DIR\" -connect=0 -coinstatsindex=1 -printtoconsole=0\n216M    /mnt/my_storage/BitcoinData/indexes/coinstatsindex/db\n144\n```\nand\n```bash\ndd76491174 index: enable DB writes batching during sync\n[1] 3885937\n[1]+  Done                    ./build/bin/bitcoind -datadir=\"$DATA_DIR\" -connect=0 -coinstatsindex=1 -printtoconsole=0\n216M    /mnt/my_storage/BitcoinData/indexes/coinstatsindex/db\n10\n```\n\nI haven't measured the speeds yet but will do that next."
  },
  {
   "t": "2026-02-11T23:31:42Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Interesting, cool thanks. Another good point in favor of this change then. Will add it to the PR description."
  },
  {
   "t": "2026-02-12T08:32:45Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "I have finished benchmarking them:\n\nindexes | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD\n\n```bash\nBEFORE=\"64294c89094d5ab10d87236729cc267fde0a24ca\"; AFTER=\"3687ead892d207ddbdfec3f2ef5c086130414d9a\"; \\\nDATA_DIR=\"/mnt/my_storage/BitcoinData\"; export DATA_DIR; \\\nRESULTS_FILE=\"${DATA_DIR}/index_benchmark_results.txt\"; export RESULTS_FILE; \\\nwait_index() { \\\n  tail -F ${DATA_DIR}/debug.log 2>/dev/null | grep -q -m1 'index is enabled\\|is enabled at height'; \\\n  sleep 2; \\\n  killall bitcoind 2>/dev/null || true; \\\n  wait; \\\n}; export -f wait_index; \\\nlog_size() { \\\n  local label=\"$1\"; local index_dir=\"$2\"; \\\n  local size=$(du -sh \"$index_dir\" 2>/dev/null | cut -f1); \\\n  local files=$(find \"$index_dir\" -type f -name '*.ldb' 2>/dev/null | wc -l); \\\n  echo \"$label: size=$size, ldb_files=$files\" | tee -a \"$RESULTS_FILE\"; \\\n}; export -f log_size; \\\ngit reset --hard >/dev/null 2>&1 && git clean -fxd >/dev/null 2>&1 && git fetch origin $BEFORE $AFTER >/dev/null 2>&1; \\\nfor c in $BEFORE:build-before $AFTER:build-after; do \\\n  git checkout ${c%:*} >/dev/null 2>&1 && cmake -B ${c#*:} -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C ${c#*:} bitcoind >/dev/null 2>&1; \\\ndone; \\\necho \"indexes | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $DATA_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $DATA_DIR | tail -1) 2>/dev/null | grep -q 0 && echo SSD || echo HDD)\" | tee -a \"$RESULTS_FILE\" && \\\nfor INDEX in txindex blockfilterindex coinstatsindex; do \\\n  echo -e \"\\n--- $INDEX ---\" | tee -a \"$RESULTS_FILE\"; \\\n  if [ \"$INDEX\" = \"blockfilterindex\" ]; then \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/blockfilter/basic\"; \\\n  else \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/${INDEX}\"; \\\n  fi; \\\n  export INDEX_DIR; \\\n  for BUILD in before after; do \\\n    if [ \"$BUILD\" = \"before\" ]; then COMMIT=\"${BEFORE:0:7}\"; else COMMIT=\"${AFTER:0:7}\"; fi; \\\n    hyperfine --runs 1 --shell bash --sort command \\\n      --prepare \"rm -rf ${DATA_DIR}/indexes/* ${DATA_DIR}/debug.log\" \\\n      --cleanup \"log_size '${INDEX} ${BUILD}' '${INDEX_DIR}'\" \\\n      -n \"${BUILD} (${COMMIT})\" \\\n      \"./build-${BUILD}/bin/bitcoind -datadir=${DATA_DIR} -${INDEX}=1 -connect=0 -printtoconsole=0 & wait_index\" \\\n      2>&1 | tee -a \"$RESULTS_FILE\"; \\\n  done; \\\ndone;\n```\n\n[quoted text omitted]\n```python\nBenchmark 1: before (64294c8)\n  Time (abs \u2261):        7665.128 s               [User: 11912.927 s, System: 1194.716 s]\ntxindex before: size=63G, ldb_files=2046\n\nBenchmark 1: after (3687ead)\n  Time (abs \u2261):        7579.151 s               [User: 11918.671 s, System: 1120.304 s]\ntxindex after: size=63G, ldb_files=2049\n```\n[quoted text omitted]\n\n```python\nBenchmark 1: before (64294c8)\n  Time (abs \u2261):        8447.157 s               [User: 8034.353 s, System: 274.633 s]\nblockfilterindex before: size=12G, ldb_files=3\n\nBenchmark 1: after (3687ead)\n  Time (abs \u2261):        8391.136 s               [User: 7992.547 s, System: 270.205 s]\nblockfilterindex after: size=12G, ldb_files=3\n```\n[quoted text omitted]\n\n```python\nBenchmark 1: before (64294c8)\n  Time (abs \u2261):        35219.938 s               [User: 34115.981 s, System: 276.583 s]\ncoinstatsindex before: size=216M, ldb_files=145\n\nBenchmark 1: after (3687ead)\n  Time (abs \u2261):        35229.893 s               [User: 34125.606 s, System: 271.160 s]\ncoinstatsindex after: size=216M, ldb_files=10\n```\n\n--------\n\nSpeed is basically the same and the number of files in `coinstatsindex` were fixed.\nThe `txindex` file count change seems like noise.\nI will review the code soon, thanks for extracting it from the big PR."
  },
  {
   "t": "2026-02-12T15:40:01Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYou have a really nice machine there. The real batching gains should show up on HDDs rather than SSDs.\nStill, great to know this works properly on high end hardware. Thanks for the benchmark."
  },
  {
   "t": "2026-02-12T16:08:40Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nyes, I would expect that to be the case - that will also be part of my review before I can ack it."
  },
  {
   "t": "2026-02-12T16:40:43Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "Bitcoin [blocks do not have a consistent size](https://coinledger.io/research/bitcoin-blockchain-size-and-growth-over-time), so batching by block size will result in irregular batch sizes. I suggest considering other approaches, such as a fixed transaction count (e.g., adding blocks until the number of transactions within them falls within a target range). This could be particularly interesting for txindex, as most batches would have a similar byte size. This would make batches more uniform and lead to more predictable computation and performance.\n\nHowever, this approach would make parallelisation more difficult, since generating the batch would require a precomputation step. This is a considerable trade-off, especially given that [parallelisation of the batch computation (#26966)](https://github.com/bitcoin/bitcoin/pull/26966) would likely be a more impactful enhancement."
  },
  {
   "t": "2026-02-12T16:58:15Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nyou know, even if it runs at the same speed on an HDD (which I doubt), the other improvements this PR brings still make it worth it."
  },
  {
   "t": "2026-02-12T17:37:52Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "I wouldn\u2019t say this test truly *mimics a restart* (i.e., shutting down the node and immediately restarting it, without the blockchain growing). It is closer to a *continuation after a clean shutdown of the whole node for a long period of time*. This test is still interesting, but it does not cover a new bug window introduced by the batch approach (shutting down the node during a batch).\n\nThe test currently does the following:\n\n- Pre-mine 100 blocks\n- Mine 173 additional blocks\n- Create the index\n- Synchronize the index to the tip\n- Mine another 173 blocks\n- Recreate the index\n- Synchronize again to the new tip\n\nIn a real-world scenario, all blocks would already exist on disk, and a restart would likely happen *during* a batch (for example at height 196, where the previous batch ended at 180).\n\nI would like to see a test (either a modification of this one or a new one) that verifies the index is not corrupted and can correctly resume if the node is restarted during a sync, before reaching the tip.\n\nA possible approach for such a test could be:\n\n1. Pre-populate the blockchain (e.g., 300 blocks already on disk)\n2. Start index sync with `batch_size = 30`\n3. Interrupt the sync after several batches but before completion\n4. Simulate a shutdown\n5. Restart the index and verify (with no new blocks added):\n   - It resumes from the last checkpoint (not from genesis or the last processed block)\n   - No corruption occurs in re-processed blocks\n   - The sync successfully completes"
  },
  {
   "t": "2026-02-12T17:39:50Z",
   "kind": "review",
   "who": "arejula27",
   "assoc": "NONE",
   "state": "COMMENTED",
   "commit": "3687ead892d207ddbdfec3f2ef5c086130414d9a",
   "text": "Concept ACK [3687ead892d207ddbdfec3f2ef5c086130414d9a]\nI really like the idea and I think it will be a massive upgrade with your parallelisation proposal #26966"
  },
  {
   "t": "2026-02-13T15:37:00Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "Sorry, I don't see the point here. What you described is essentially a restart. The index gets recreated, initial sync resumes from the last stored index. That's all what we need.\nFeel free to experiment on a follow-up, or I could also add it here if you have some code too."
  },
  {
   "t": "2026-02-13T19:58:32Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "Sorry if I didn\u2019t explain this clearly before, let me try again.\n\nWith this PR, some blocks are being indexed (on disk), but the index pointer is not updated because the `Commit()` function has not been called. The commit is executed every 30 seconds (which won\u2019t happen during the test) or when the tip is processed (batch tip).\n\nWhat I\u2019m suggesting is to test the scenario where a batch is processed but the tip is not processed. In that case, the index should sync starting from the **last committed** block.\n\nAfter debugging your test case, the second round started from the last element of the last batch, as it was the end of the chain at round 0.  I would suggest checking the following cases (with a batch of 30 blocks and at least 101 blocks):\n\n* If the sync process is shut down when block 100 is processed, after a restart, the first block to sync should be 91 (not 0,  91 or 120), verifying that if a batch is interrupted, after a restart, the index will reindex from the last committed batch.\n* If the sync process is shut down when block 90 is processed, after a restart, the first block to sync should be 91, verify and an edge case of the previous scenario.\n\n**Update**\n\nI noticed that in the [code](https://github.com/furszy/bitcoin-core/blob/3687ead892d207ddbdfec3f2ef5c086130414d9a/src/index/base.cpp#L226), interruptions are handled by committing:\n```c++\nif (m_interrupt) {\n    LogInfo(\"%s: m_interrupt set; exiting ThreadSync\", GetName());\n\n    SetBestBlockIndex(pindex);\n    Commit();\n    return;\n}\n```\nBecause of this, the scenarios described above may not be useful, since the last processed block might always be committed before shutdown.\n\nI\u2019m not sure whether this interruption only applies to graceful shutdowns or if it also covers crash scenarios."
  },
  {
   "t": "2026-02-13T20:21:45Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "[quoted text omitted]\n\nJust to be accurate: batch writes and the index locator update are, on purpose, not related procedures. `Commit()` happens every 30 secs, during interruption or when the tip is reached prior to finishing initial sync.\n\n[quoted text omitted]\nSure, we could return an error inside the test child index `CustomAppend()` that finishes the process early.\nIt shouldn't be hard to code. Feel encouraged to experiment with it. I can cherry-pick your changes if you have a test commit for it."
  },
  {
   "t": "2026-02-13T22:08:46Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "[quoted text omitted]\n\nYes, ofc :D\n[quoted text omitted]\n\ud83e\udee1"
  },
  {
   "t": "2026-02-14T01:47:55Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "Having played around with the code, I found that the case i mentioned didnt worked correctly\n- **Round 0**:\n    - Max height: 273\n    - After sync, tip is at 273\n- **Round 1**:\n    - Max height: 446 (273 + 173)\n    - Sync fails at height 310\n- **Round 2**:\n\t- The first batch starts at 273, however, it should be 304 (the previous round synced one batch)\n\n    This happens because CustomAppend() returns false and the process is stpped before the following branch is reached\n    ```c++\n    if (m_interrupt) {\n        LogInfo(\"%s: m_interrupt set; exiting ThreadSync\", GetName());\n\t     SetBestBlockIndex(pindex);\n        Commit();\n        return;\n    }\n\t```\nBelow is the code to reproduce the issue:\n\n```c++\n// Extend BlockFilterIndex to create a custom test function CustomAppend\nclass BlockFilterIndexTest : public BlockFilterIndex\n{\npublic:\n    BlockFilterIndexTest(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory) :\n        BlockFilterIndex(std::move(chain), filter_type, n_cache_size, f_memory) {}\n\n    // Override CustomAppend to simulate an error during append at a specific block height\n    bool CustomAppend(CDBBatch& batch, const interfaces::BlockInfo& block) override\n    {\n        if (block.height == 290) {\n            return false;\n        }\n        return BlockFilterIndex::CustomAppend(batch, block);\n    }\n};\n\n// Ensure the initial sync batch window behaves as expected.\n// Tests sync from the genesis block and from a higher block to mimic a restart.\n// Note: Test runs in /tmp by default, which is usually cached, so timings are not\n// a meaningful benchmark (use -testdatadir to run it elsewhere).\nBOOST_FIXTURE_TEST_CASE(initial_sync_batch_window, BuildChainTestingSetup)\n{\n    constexpr int MINE_BLOCKS = 173;\n    constexpr int BATCH_SIZE = 30;\n\n    int expected_tip = 100; // pre-mined blocks\n    for (int round = 0; round < 3; round++) { // two additional rounds to test sync from genesis and from a higher block\n        mineBlocks(MINE_BLOCKS); // Generate blocks\n        const int tip_height = WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Height());\n        BOOST_REQUIRE(tip_height == MINE_BLOCKS + expected_tip);\n        expected_tip = tip_height;\n\n        BlockFilterIndexTest filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, /*f_memory=*/false);\n        filter_index.SetProcessingBatchSize(BATCH_SIZE);\n        BOOST_REQUIRE(filter_index.Init());\n        BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());\n\n        // Ensure we can sync up to the tip\n        const auto& summary_pre{filter_index.GetSummary()};\n        // breakpoint here for following gdb ouptut\n        filter_index.Sync();\n        if (round == 1) { continue; }\n        const auto& summary{filter_index.GetSummary()};\n        BOOST_CHECK(summary.synced);\n```\n\nGDB output **before** `Sync()` in round 2:\n\n```\ngdb build/bin/test_bitcoin\nb blockfilter_index_tests.cpp:427\nb src/index/base.cpp:255\nrun\n(gdb) p round\n$2 = 2\n(gdb) p expected_tip\n$3 = 619\n(gdb) p summary_pre.best_block_height\n$4 = 273\n```"
  },
  {
   "t": "2026-02-14T02:03:32Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "Haven't gone through this in detail, but the locator won't update in your second round unless you advance the mock time. Also, keep in mind that if any block within the batch fails, the entire process aborts. The process doesn't save intermediate batch states."
  },
  {
   "t": "2026-02-14T02:18:26Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "[quoted text omitted]\n\nyes i didnt want to simulate 30 sec, i wanted the error before the timmer commit (to fail into the error handling `m_interrupt`)\n[quoted text omitted]\n\nI saw that when `CustomAppend` returns false, it executes `FatalErrorf`, which simply stops the node (not gracefully) instead of raising `m_interrupt`, my bad\n\n1. What do you think about modifying the error handling in the sync process to prevent losing stored data by raising the `m_interrupt` flag? In the worst case, we lose 29.59 seconds of execution, it might not be worth it and let everything as it currently is\n\n2. I will try to modify the `CustomAppend` to raise the `m_interrupt` and create a test to show how it is correctly processed, i think it is more interesting this approach"
  },
  {
   "t": "2026-02-14T10:50:59Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "Returning `false` here causes the interrupt to be handled incorrectly as it will be handled in the Sync function like this:\n\n```cpp\nif (!ProcessBlocks(db_batch, block_batch.start_index, block_batch.end_index)) {\n    return; // error logged internally\n}\n```\n\nThis early return prevents execution from reaching line 226, where the correct interrupt-handling logic resides. As a result, the expected recovery path is skipped.\n\nSpecifically, this makes the following block effectively unreachable in this scenario:\n\n```cpp\nif (m_interrupt) {\n    LogInfo(\"%s: m_interrupt set; exiting ThreadSync\", GetName());\n\n    SetBestBlockIndex(pindex);\n\n    Commit();\n    return;\n}\n```\n\nI didn't find in the file a place where m_interrupt is handled in this way, might be interesting to try to make the code fall on the graceful path. A possible solution might be changing the return by continue, so it will go to the start of the loop and fall into the error handling logic"
  },
  {
   "t": "2026-02-14T10:58:41Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2807338683,
   "text": "I debbuged and with this code, the restart point is ok:\n```\n if (!ProcessBlocks(db_batch, block_batch.start_index, block_batch.end_index)){\n      if (!m_interrupt) {\n           return;\n}\n                continue;\n}\n```"
  },
  {
   "t": "2026-02-14T16:55:06Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "c2953e446d53a8ac86ae3e4801208df5c9c6d2cc"
  },
  {
   "t": "2026-02-14T17:10:37Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2807338683,
   "text": "Nice catch. Pushed an update that fixes it and adds test coverage.\n\nI'm considering alternative options for the structure, like removing the interruption update, but will let the ideas settle over the weekend. At least we have some nice test coverage for it now.\n\nSome context on \"removing the interruption update\":\nSince we save progress every 30 seconds, saving it during interruptions makes little sense, as the work lost is minimal. The code is probably there merely because it was introduced on the early index days, where things were way more chaotic, and no one really paid much attention to it afterwards."
  },
  {
   "t": "2026-02-14T17:20:28Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "[quoted text omitted]\n\nAs you said, since we already save progress every 30 seconds, saving it during interruptions makes very little sense. The amount of work lost is minimal. Yet, this has been the case since the indexes were introduced, and I suspect it hasn't received much attention, which is probably why no one removed it before..\nIt\u2019s something we could consider."
  },
  {
   "t": "2026-02-14T19:47:27Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2800198266,
   "text": "Nice, if you agree, you can mark this thread as resolved then"
  },
  {
   "t": "2026-02-14T20:04:09Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2807338683,
   "text": "[quoted text omitted]\n\nNice. I finally managed to explain the scenario I\u2019ve been trying to describe from the beginning, sorry for not expressing it clearly before (I\u2019m still new as a core reviewer), at least I found a potential bug.\n\nI don\u2019t want to take up more of your time on it, so feel free to resolve this thread. I think the test implemented for the scenario is enough.\n\n[quoted text omitted]\nThat\u2019s a fair point. However, I\u2019m not sure I see the benefit of removing it. I agree that this could be an argument for not introducing the code in the first place, since the potential work lost is minimal. But given that it\u2019s already there, I\u2019m not sure whether removing it (even if the performance impact is small) is an improvement. Do you have something specific in mind? Perhaps something related to the parallelisation of the indexing process?"
  },
  {
   "t": "2026-02-14T20:06:38Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "re Concept ACK [c2953e446d53a8ac86ae3e4801208df5c9c6d2cc]"
  },
  {
   "t": "2026-02-16T01:46:23Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "79e19f5fb5eb94933517219a60a5e17056fab783"
  },
  {
   "t": "2026-02-16T02:16:09Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "bb3e4a0c07d7a37645c789053bfecbd029e26f84"
  },
  {
   "t": "2026-02-16T04:44:10Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "ede2eabfe218fe98994c694e2c52af98a94d0120"
  },
  {
   "t": "2026-02-18T17:17:59Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "0bebf13c0eed05c9b641e18b7294edad20903e12"
  },
  {
   "t": "2026-02-18T17:18:59Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "360c43d7c572f8cace50f1592ea4179f9f5c8663",
   "in_reply_to": 2784799921,
   "text": "Done as suggested."
  },
  {
   "t": "2026-02-18T17:21:18Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Made a few improvements to the commits descriptions with additional rationale for the changes, and rebased on master to pick up the latest CI updates."
  },
  {
   "t": "2026-02-18T20:49:55Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "965d4299a76badcf2257f390f5c02e6007ec6244",
   "in_reply_to": null,
   "text": "In commit 965d4299a76badcf2257f390f5c02e6007ec6244\n\nNit: Why introduce the `BlockBatch` at all when we don't pass it on here? I don't think we gain much by de-referencing here."
  },
  {
   "t": "2026-02-18T20:55:06Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "965d4299a76badcf2257f390f5c02e6007ec6244",
   "in_reply_to": null,
   "text": "Just a question: Why do we need to define all of these?"
  },
  {
   "t": "2026-02-18T21:21:59Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "b67920269c133ba8ae4ffc9667de46a1af7f7ac4",
   "in_reply_to": null,
   "text": "Why not re-apply the same logic from before here? Is there a reason why we should only ever process one block here? If that is possible could we make `NextSyncBlock` absorb this logic and return a `BlockBatch` directly?"
  },
  {
   "t": "2026-02-18T22:03:35Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "Nit (clang-format): could drop the empty line after this too."
  },
  {
   "t": "2026-02-28T21:29:14Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "965d4299a76badcf2257f390f5c02e6007ec6244",
   "in_reply_to": 2824390185,
   "text": "[quoted text omitted]\n\nMainly for the block range computation, it's less error-prone to use a range structure instead of two independent variables. In the concurrent future (#26966), many block ranges will be computed at once and submitted to the pool (which will avoid further `cs_main` locking). This requires storing them in a container, and I wasn't really a fan of using a less readable `std::pair`. But complain taken, have reworked it so `NextBlockToSync` returns the range, which gives this struct a more powerful use case.\n\nRegarding `ProcessBlocks` usage, I thought about it a few times. In #26966, the structure takes on a more meaningful role, it becomes the work unit state (we call it `Task` there), and we expand it to store intermediate results there as well. Because of that, I didn't think that making `ProcessBlocks` to use it was the best, the function should only receive what it will use, not an struct that contains other stuff.\n\nOn a side comment, I'm going to rebase #26966 on top of this PR now it is mature enough, so we use the same terms on both PRs and what I described above is super clear there as well."
  },
  {
   "t": "2026-02-28T21:45:25Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.h",
   "commit": "965d4299a76badcf2257f390f5c02e6007ec6244",
   "in_reply_to": 2824411175,
   "text": "[quoted text omitted]\n\nNot needed here, will remove them.\n\nBut, just to describe why these methods exist in the parent PR (and refresh my memory while doing it):\n\nIn #26966, this struct is extended to hold the block range processed data, it essentially becomes the work unit that flows through the entire pipeline:\n\n1) It is computed by the index initial sync thread.\n2) It is submitted to the thread pool, as a task there.\n3) Once processed, it is pushed into a shared \"ready\" queue, which is then opportunistically processed.\n4) Workers that finish their work units, post-processes \"ready\" units (this is where we flush data to disk in order, so the block filter index can build the headers chain correctly, since each header depends on the previous header's hash, same will happen with the coinfilterindex in the future, etc).\n\nThe whole rationale behind this design is that work units are processed in parallel, no ordering required. And once a worker finishes a task, it opportunistically checks whether the next in-order unit is ready for post-processing. If it is, that worker performs the post-processing step and continues draining any subsequent ready units in order. This basically ensures we can process blocks in parallel while still preserving the ordered persistence requirement that some of the indexes require."
  },
  {
   "t": "2026-03-02T14:28:20Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "47b02354c97ac7b0442b2ccbd02d66f212537219"
  },
  {
   "t": "2026-03-02T14:32:24Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2824679645,
   "text": "Done as suggested."
  },
  {
   "t": "2026-03-02T14:34:04Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "224a638a9cff1fda9cf28438989acb1817458cba"
  },
  {
   "t": "2026-03-02T14:34:30Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "b67920269c133ba8ae4ffc9667de46a1af7f7ac4",
   "in_reply_to": 2824531151,
   "text": "Done as suggested. Thanks!"
  },
  {
   "t": "2026-03-02T14:35:23Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Updated per feedback, thanks @sedited!\nAlso had to rebase the PR due to silent conflicts with the new `TxoSpender` index."
  },
  {
   "t": "2026-03-02T21:32:38Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d"
  },
  {
   "t": "2026-03-03T13:56:09Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "CI failure is unrelated."
  },
  {
   "t": "2026-03-03T21:30:02Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThis PR only touches indexes code and nothing else. CI was failing in `interface_ipc.py` this morning, and now after the restart fails on `p2p_orphan_handling.py`. None of these tests have any index enabled."
  },
  {
   "t": "2026-03-03T21:36:33Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYeah, I guess this is a good point. It is just odd that the `p2p_orphan_handling.py` happened twice here, but nowhere else so far. The IPC one is tracked in https://github.com/bitcoin/bitcoin/issues/34711\n\nEdit: Let's see if the CI fails in https://github.com/bitcoin/bitcoin/pull/34726 ..."
  },
  {
   "t": "2026-03-04T01:40:06Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nCould add some debug logging to see where it's getting stuck. At first glance, it looks like it stalls during block creation.\n\nAlso, just to be clear to other reviewers. Since the issue happens there as well, we know for sure it is not related to this PR."
  },
  {
   "t": "2026-03-04T16:09:44Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "looks like CI passed now"
  },
  {
   "t": "2026-03-04T16:45:49Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/blockfilterindex.h",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": null,
   "text": "Im not sure if removing `final` from `BlockFilterIndex` only to allow `IndexBlockSim` to subclass  it for **testing** is the better decision, i undertsand that it was used final for a reason, and i do not think modifying the code fo fit a test is the best option.\n\nRestoring final makes the compilation to fail with error:\n```\n  [ 82%] Building CXX object src/test/CMakeFiles/test_bitcoin.dir/checkqueue_tests.cpp.o\n/home/arejula27/workspaces/bitcoin/src/test/blockfilter_index_tests.cpp:440:7: error: cannot derive from \u2018final\u2019 base \u2018BlockFilterIndex\u2019 in derived type \u2018blockfilter_index_tests::IndexBlockSim\u2019\n  440 | class IndexBlockSim : public BlockFilterIndex\n      |       ^~~~~~~~~~~~~\ngmake[2]: *** [src/test/CMakeFiles/test_bitcoin.dir/build.make:348: src/test/CMakeFiles/test_bitcoin.dir/blockfilter_index_tests.cpp.o] Error 1\ngmake[2]: *** Waiting for unfinished jobs....\ngmake[1]: *** [CMakeFiles/Makefile2:2217: src/test/CMakeFiles/test_bitcoin.dir/all] Error 2\ngmake: *** [Makefile:146: all] Error 2\n\n```\n\nMaybe we should base `BlockFilterIndex` from  `BaseIndex` like `IndexReorgCrash`"
  },
  {
   "t": "2026-03-04T17:37:01Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nyes, review can continue."
  },
  {
   "t": "2026-03-04T17:38:29Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/txindex.cpp",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": null,
   "text": "Since `WriteTxs` was moved out of `TxIndex::DB`, it no longer has static or anonymous namespace scoping, which gives it external linkage. To avoid potential linker conflicts with other translation units, it might be worth wrapping it in an anonymous namespace as this function is not expected to be used outside this file\n\n```suggestion\n namespace {\n  void WriteTxs(CDBBatch& batch, const std::vector<std::pair<Txid,\n  CDiskTxPos>>& v_pos)\n  {\n      ...\n  }\n }\n```\n\nI compiled and passed all tests with this change"
  },
  {
   "t": "2026-03-04T19:05:35Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "Two small comments, I feel the one about removing `final` is relevant, the other one not really but I saw the same pattern on other files of the project so if it is not an inconvenient you can add it, if not just resolve it.\n\nWaiting for the HDD benchmark of @l0rinc. I can do it if he is busy"
  },
  {
   "t": "2026-03-04T19:07:29Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/txindex.cpp",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": 2885157664,
   "text": "why not using `static` instead?"
  },
  {
   "t": "2026-03-04T19:22:57Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nHaving more benchmarks does not hurt :). Feel more than welcome adding your results as well!"
  },
  {
   "t": "2026-03-04T20:12:49Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/blockfilterindex.h",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": 2884888266,
   "text": "Yeah, I went back and forth on this decision for a while before pushing it. The idea was to actually cover `BlockFilterIndex` methods during reorgs, not just the `BaseIndex` behavior, even if they aren't strictly required for this PR changes.\n\nIdeally, we should have both testing scenarios: reorgs during initial sync against `BaseIndex`, and reorgs against `BlockFilterIndex`. But.. doing that would require extracting `BuildChainTestingSetup` into a separate file, and I didn't want to expand the scope of this PR any further. This test, and even the existing `index_reorg_crash` (the one that uses `IndexReorgCrash`) do not belong to `blockfilter_index_tests.cpp`. They only test against the base class. But well, that is something that already exists in our codebase, and not something we are including here, so it is better to leave it for a different PR.\n\nAlso, the `final` declaration here (and in all other indexes) seems really unnecessary. I cannot think of anything we gain from it. It only restricts our unit testing capabilities. I would remove it from all indexes.\n\nThat being said, most of this is just me ranting about improvements we could make. So will take your suggestion and use the `BaseIndex` class here for now. Let's go step by step. Thanks for the feedback."
  },
  {
   "t": "2026-03-04T20:25:57Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a"
  },
  {
   "t": "2026-03-04T20:27:07Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "Updated per feedback. Thanks @arejula27.\nAlso rebased on master to pull the CI fixes."
  },
  {
   "t": "2026-03-04T22:37:10Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/txindex.cpp",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": 2885157664,
   "text": "idm,   i read that anonymous namespaces are more standarized in  C++ and  `static` is more C-compatible. I'm fine keeping it as static"
  },
  {
   "t": "2026-03-04T22:42:43Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/blockfilterindex.h",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": 2884888266,
   "text": "[quoted text omitted]\n\nWhy? Isn't that redundant?\n\n[quoted text omitted]\nSounds reasonable to discuss in another PR. I like the idea of removing `final` if it is not needed, but as you said, it should be consistent across all indexes."
  },
  {
   "t": "2026-03-05T13:46:25Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/blockfilterindex.h",
   "commit": "80aea07a6e74a2316160c2aafe50ee8ba45ac08d",
   "in_reply_to": 2884888266,
   "text": "[quoted text omitted]\n\nWe can test at different levels.\nThe `BaseIndex` test can verify that we always follow the best chain during initial sync, that the appropriate child methods are called in order (append - remove), and check the db chain locator updates. Then, `BlockFilterIndex` test, and others, should only focus on manually connecting and disconnecting blocks, verifying that its data is consistent in memory and disk."
  },
  {
   "t": "2026-03-07T16:55:43Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "Due to this new `if` this is not a straight refactor, maybe this is worth mentioning in the commit description. This if is a minor optimization or readability improvement, as in this special case the `FindFork/Next` combo below would return `nullptr`. Discussed at length in #32875"
  },
  {
   "t": "2026-03-08T10:21:54Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "Did another index benchmark for the latest push on an HDD:\n\n---\n\nindexes | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | ext4 | HDD\n\n```bash\nBEFORE=\"4c40a923f003420193aa574745f70788bcf35265\"; AFTER=\"5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a\"; \\\nDATA_DIR=\"/mnt/my_storage/BitcoinData\"; export DATA_DIR; \\\nRESULTS_FILE=\"${DATA_DIR}/index_benchmark_results.txt\"; export RESULTS_FILE; \\\nwait_index() { \\\n  tail -F ${DATA_DIR}/debug.log 2>/dev/null | grep -q -m1 'index is enabled\\|is enabled at height'; \\\n  sleep 2; \\\n  killall bitcoind 2>/dev/null || true; \\\n  wait; \\\n}; export -f wait_index; \\\nlog_size() { \\\n  local label=\"$1\"; local index_dir=\"$2\"; \\\n  local size=$(du -sh \"$index_dir\" 2>/dev/null | cut -f1); \\\n  local files=$(find \"$index_dir\" -type f -name '*.ldb' 2>/dev/null | wc -l); \\\n  echo \"$label: size=$size, ldb_files=$files\" | tee -a \"$RESULTS_FILE\"; \\\n}; export -f log_size; \\\ngit reset --hard >/dev/null 2>&1 && git clean -fxd >/dev/null 2>&1 && git fetch origin $BEFORE $AFTER >/dev/null 2>&1; \\\nfor c in $BEFORE:build-before $AFTER:build-after; do \\\n  git checkout ${c%:*} >/dev/null 2>&1 && cmake -B ${c#*:} -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C ${c#*:} bitcoind >/dev/null 2>&1; \\\ndone; \\\necho \"indexes | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $DATA_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $DATA_DIR | tail -1) 2>/dev/null | grep -q 0 && echo SSD || echo HDD)\" | tee -a \"$RESULTS_FILE\" && \\\nfor INDEX in txindex blockfilterindex coinstatsindex txospenderindex;; do \\\n  echo -e \"\\n--- $INDEX ---\" | tee -a \"$RESULTS_FILE\"; \\\n  if [ \"$INDEX\" = \"blockfilterindex\" ]; then \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/blockfilter/basic\"; \\\n  else \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/${INDEX}\"; \\\n  fi; \\\n  export INDEX_DIR; \\\n  for BUILD in before after; do \\\n    if [ \"$BUILD\" = \"before\" ]; then COMMIT=\"${BEFORE:0:7}\"; else COMMIT=\"${AFTER:0:7}\"; fi; \\\n    hyperfine --runs 1 --shell bash --sort command \\\n      --prepare \"rm -rf ${DATA_DIR}/indexes/* ${DATA_DIR}/debug.log\" \\\n      --cleanup \"log_size '${INDEX} ${BUILD}' '${INDEX_DIR}'\" \\\n      -n \"${BUILD} (${COMMIT})\" \\\n      \"./build-${BUILD}/bin/bitcoind -datadir=${DATA_DIR} -${INDEX}=1 -connect=0 -printtoconsole=0 & wait_index\" \\\n      2>&1 | tee -a \"$RESULTS_FILE\"; \\\n  done; \\\ndone;\n\n```\n\n[quoted text omitted]\nBenchmark 1: before (4c40a923f003420193aa574745f70788bcf35265)\n  Time (abs \u2261):        23757.916 s               [User: 13234.906 s, System: 1697.284 s]\n\nBenchmark 1: after (5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a)\n  Time (abs \u2261):        21134.433 s               [User: 12627.245 s, System: 1483.096 s]\n\n[quoted text omitted]\nBenchmark 1: before (4c40a923f003420193aa574745f70788bcf35265)\n  Time (abs \u2261):        14205.566 s               [User: 8328.684 s, System: 418.981 s]\n\nBenchmark 1: after (5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a)\n  Time (abs \u2261):        14046.610 s               [User: 8286.885 s, System: 415.464 s]\n\n[quoted text omitted]\nBenchmark 1: before (4c40a923f003420193aa574745f70788bcf35265)\n  Time (abs \u2261):        40075.612 s               [User: 34320.885 s, System: 403.669 s]\n\nBenchmark 1: after (5be65dbcdceaafb6f5a0e34c69efc2b3dfcfe27a)\n  Time (abs \u2261):        39869.235 s               [User: 34307.695 s, System: 398.322 s]\n\n[quoted text omitted]\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        36324.897 s               [User: 23490.919 s, System: 2227.044 s]\n\nBenchmark 1: after (5be65db)\n  Time (abs \u2261):        26375.223 s               [User: 21783.716 s, System: 1690.540 s]\n\nEdit: added `txospenderindex`"
  },
  {
   "t": "2026-03-08T10:27:19Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "[quoted text omitted]\n\nIt looks like the improvement is not very noticeable for two of the indexes. It might be interesting to try increasing the batch size. Do you have any suggestions on what could be causing this?"
  },
  {
   "t": "2026-03-08T16:50:00Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nI'm not sure this is really surprising given the difference in the amount of data written to the latter two. I would expect a jump more similar to the txindex for the txospenderindex.\n\nWhat is the expectation on the increase of memory usage here? I tried profiling it with massif, but usage when running with this patch seemed a bit erratic, which I guess is still an indication that it is likely to be higher. Can we really batch operations in this way without putting more memory pressure on the OS?"
  },
  {
   "t": "2026-03-08T17:54:45Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThe batch size is intentionally small. I was very conservative with the chosen value to avoid having to worry about it here. The goal of this PR is to land the structural improvements, with a net positive: less `cs_main` locking, merging prerequisites for the parallelization goal, and the last discovery of reducing the number of created LBD files.\n\nIt's expected that any batching implementation adds some memory overhead, but that shouldn't be a problem, it pays off in many other areas, not only with a faster sync promise. And, if we ever wanted to, we could add a config flag to disable batching by setting the batch size to 1. But realistically speaking, I don't think anyone running indexes would want them to take hours to sync while locking `cs_main` and slowing down the whole node. People who run indexes want them to sync-up quickly so they can start using them.\n\nAlso, an important point. All these changes only matter during the index initial sync period. Once the index is synced, they are no longer relevant.\n\nWe can benchmark this on small devices like the Pi 4 to see the benefits more clearly, but I honestly wouldn't spend too much time on it. If we agree this PR lands structural, scalable improvements with many benefits and no noticeable downside, and also lets us move forward with the major parallelization speedup path, it seems reasonable to just move forward. This also has a good number of tests we currently lack from."
  },
  {
   "t": "2026-03-08T18:10:01Z",
   "kind": "force_push",
   "who": "furszy",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf"
  },
  {
   "t": "2026-03-08T18:10:41Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2900064598,
   "text": "Sure, done. Added mention to the `Tip()` fast-path in the commit description."
  },
  {
   "t": "2026-03-09T12:28:15Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWe're changing behavior in a commit that claims it's not a behavior change, without adding context for why this change was needed or how to validate that it is correct. Given that it wasn't obvious to the original authors, an explanation is needed. Also, No test was added to exercise this behavior, to explain exactly why it was necessary, and make sure it doesn't happen again.\n\nIt also seems independent enough to be pushed in a separate PR, with a test documenting the current behavior in the first commit and, in the next commit, a fix and a test update that reflects the new behavior."
  },
  {
   "t": "2026-03-09T12:51:53Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0cbe84d36d418ed4a401643e2b36d9d325167111",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe commit claims there's no behavior change, but it doesn't explain why the move is safe. Given all the internal `return`s and `break`s it's not self-evident.\n\n------\n\nIt seems to me that previously a synced, and later interrupted, path could hit\n```C++\nLogInfo(\"%s is enabled at height %d\", GetName(), pindex->nHeight);\n```\n, but after the change it hits\n```C++\nLogInfo(\"%s: m_interrupt set; exiting ThreadSync\", GetName());\n```\nand returns.\n\nI'm not sure there is any other change, but I would appreciate a commit message explanation, and maybe a test exercising this path, preferably a fuzz test to cover all these weird combinations."
  },
  {
   "t": "2026-03-09T13:35:43Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nnit: for consistency we could use the same comment style as below:\n```suggestion\n    // Collect all block indexes from end to start\n```"
  },
  {
   "t": "2026-03-09T13:48:37Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWhen can `block` be `nullptr` here? Wouldn't it be a fatal error if we walk past genesis? And what if the caller mixes up begin and end? Or if `start` and `end` have valid heights but are on different forks.\n\nGiven how intertwined the code is here, a serious mess-up could cause an infinite loop and would be really hard to trace back to the source.\n\nI would probably sleep better if we asserted that `end->nHeight >= start->nHeight` and used a bounded `for` loop here instead.\n```C++\nconst int range_size{end->nHeight - start->nHeight + 1};\nAssert(range_size > 0);\n\n// Collect all block indexes from end to start\nstd::vector<const CBlockIndex*> ordered_blocks;\n{\n    ordered_blocks.reserve(range_size);\n    CBlockIndex* it = end;\n    for (int i{0}; i < range_size; ++i) {\n        ordered_blocks.emplace_back(Assert(it));\n        it = it->pprev;\n    }\n    Assert(it == start->pprev);\n}\n```"
  },
  {
   "t": "2026-03-09T13:59:27Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe method comment states:\n[quoted text omitted]\n\nBut if the interruption happens before we call the first `ProcessBlock`, we should be safe since no state was changed - i.e. nothing to recover from."
  },
  {
   "t": "2026-03-09T14:09:52Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nHow can we tell this failed \"due to an interruption\"? `m_interrupt` could have been set independently"
  },
  {
   "t": "2026-03-09T14:14:43Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "0336c61aacf01a503f75a8e35d6bec9954ea6840",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\n```suggestion\n    /// Processes blocks in the range [start, end], calling `ProcessBlock`.\n```"
  },
  {
   "t": "2026-03-09T14:27:45Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0e9f2aa7eaa9888884a2f6280726188ce3abcad7",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\n```suggestion\n// Returns the next block to sync, or `nullptr` if fully synced\n```"
  },
  {
   "t": "2026-03-09T14:43:04Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0e9f2aa7eaa9888884a2f6280726188ce3abcad7",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe diff would be a lot simpler if we kept the early return guard here instead:\n```suggestion\n        return chain.Genesis();\n```\nwhich would reduce the indentation of the following code and the awkward naming of \"first\" for \"next\""
  },
  {
   "t": "2026-03-09T14:47:55Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0e9f2aa7eaa9888884a2f6280726188ce3abcad7",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nI find it a bit confusing that the return value of `Next` is called `first`, which is clearly the next of the prev."
  },
  {
   "t": "2026-03-09T14:51:17Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2900064598,
   "text": "[quoted text omitted]\n\nI still find it confusing, as mentioned, this is just an optimization, not sure how it fits into and a clearer special case for \"already at tip\". It's a reasonable change, but another one without any test coverage - please see the mentioned https://github.com/bitcoin/bitcoin/pull/32875#discussion_r2758807954 and https://github.com/bitcoin/bitcoin/pull/32875#discussion_r2758871724 for how this could be tested."
  },
  {
   "t": "2026-03-09T15:03:23Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "0e9f2aa7eaa9888884a2f6280726188ce3abcad7",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe `Assume(first)` at the end is non-obvious. After the `pindex_prev == chain.Tip()` fast path returns `nullptr`, the remaining path reaches `chain.Next(chain.FindFork(pindex_prev))`.\nA reader needs to convince themselves this can't be `nullptr`, i.e., that `FindFork` can never return the tip here. Could you add a comment explaining why? Or add it to the commit message to help with review.\n\nIs it safe because the fork point can't be the tip, because that would mean `pindex_prev` is in the active chain, which contradicts `chain.Next(pindex_prev)` having returned `nullptr` above?"
  },
  {
   "t": "2026-03-09T15:12:45Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCan you please avoid modifying the same line in different commits? The change is already quite difficult to review."
  },
  {
   "t": "2026-03-09T15:14:19Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCan we squash this with 0336c61aacf01a503f75a8e35d6bec9954ea6840? We just introduced it and now we're modifying the same code area."
  },
  {
   "t": "2026-03-09T15:17:57Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nnit: `const CBlockIndex*` is confusing in a mutable struct whose fields are reassigned every iteration. I understand that it's not `CBlockIndex* const last`, but a \"pointer-to-const block index\". Would seem simpler to me to just have:\n```C++\nstruct BlockRange {\n    CBlockIndex* first{nullptr};\n    CBlockIndex* last{nullptr};\n};\n```"
  },
  {
   "t": "2026-03-09T15:20:54Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nWe don't usually const primitive arguments"
  },
  {
   "t": "2026-03-09T15:23:54Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "[quoted text omitted]\n\nI don't think that matters. Whichever is simpler.\n\n[quoted text omitted]\nI agree with @fjahr that tuning this based on measurements should be part of the PR. This seems like a random number, not based on actual behavior. What are the memory implications of this batch? What would be the speedup with bigger ones, and what's the effect on LevelDB compaction and the number of files, etc.?\n\n[quoted text omitted]\nI personally find this hand-wavy approach concerning.\n\n[quoted text omitted]\nI also personally find this troubling. We should be concerned with correctness here, and not even mention parallelization before we're sure batching is actually correct.\nAnd since I have concerns about the current implementation (reorgs, save + load, state + tip writing inconsistencies) and the lack of testing, I don't think we should treat this as just a quick hack that helps us get to parallelization.\n\n----\n\nnit: `inline constexpr` is more idiomatic for headers:\n```C++\nstatic inline constexpr int INDEX_BATCH_SIZE{500};\n```"
  },
  {
   "t": "2026-03-09T15:34:02Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "e4b1fd09e49d2840317300fa6ad89a18aa094411",
   "in_reply_to": null,
   "text": "I would focus in the commit message (and PR description) on batching reducing write amplification - i.e. fewer `LevelDB` compactions because the payload is more predictable"
  },
  {
   "t": "2026-03-09T15:40:46Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "e4b1fd09e49d2840317300fa6ad89a18aa094411",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nIs `ProcessBlocks` guaranteed to make only temporary changes that we can just revert if this gets interrupted?\n\nBefore this PR, `CustomAppend` wrote directly to the DB per block, so `m_muhash` and the DB were always in sync. With batching, in-memory state advances ahead of what's persisted, and the interrupt handler's `Commit()` can write the advanced `m_muhash` alongside the old locator. After reload it could probably crash, or worse, maybe just continue with incorrect state (not sure, please confirm).\n\nIf this is accurate, it directly contradicts the code comment:\n[quoted text omitted]\n// DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.\n\n---\n\n@dergoegge, is this something we can test currently? I'm surprised this wasn't caught by our fuzzers. Maybe I'm missing some constraint in the review, since I haven't tried reproducing this race explicitly."
  },
  {
   "t": "2026-03-09T16:35:10Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis comment seem stale now and mismatches the new API."
  },
  {
   "t": "2026-03-09T16:38:35Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nAre we exposing this for testing only? An incorrect call to this could possibly cause an infinite loop, or at least continue until genesis. Not sure, but I would prefer if we made the public interface safer."
  },
  {
   "t": "2026-03-09T16:47:24Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "unrelated nit:\n```suggestion\n    // No need to handle errors in Commit. If it fails, the error will already be logged. The\n```"
  },
  {
   "t": "2026-03-09T16:52:08Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis should have probably caught the partial write regression.\nCan we extend `IndexCommitStateSim` to implement `CustomCommit` as well?"
  },
  {
   "t": "2026-03-09T17:05:52Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "7c82cc88e8e04c2c32070bca32aaddc15f1fb7ac",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nAdding tests at the very end doesn't help document the progression of the changes introduced in this PR, commit by commit.\nWe could add this test as a first commit:\n```C++\n// Ensure initial sync can be restarted cleanly without overwriting earlier\n// results. Tests sync from genesis and from a higher block to mimic a restart.\nBOOST_FIXTURE_TEST_CASE(initial_sync_restart, BuildChainTestingSetup)\n```\n\nand later, in the batch size commit, just add `filter_index.SetProcessingBatchSize(BATCH_SIZE);` to the test to prove that it still passes. That way, we are asserting that the previous behavior is retained, not just that the new behavior is covered."
  },
  {
   "t": "2026-03-09T17:10:52Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "28d8b04defeabecd0b7bb5d0d25d1700ade57c78",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis test almost completely passes on `master` - we should add as much coverage before the refactor.\nBut we need to make it more realistic, likely by overriding `CustomAppend` as well."
  },
  {
   "t": "2026-03-09T17:12:48Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/blockfilter_index_tests.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThis passes before the change with:\n```C++\n    // The index will be interrupted while processing block 45. Since sync is\n    // still per-block here, the last persisted block is expected to be 45.\n    constexpr int SHUTDOWN_HEIGHT = 45;\n    constexpr int EXPECTED_LAST_SYNCED_BLOCK = 45;\n```\n(and no `SetProcessingBatchSize`, of course)"
  },
  {
   "t": "2026-03-09T17:23:01Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/blockfilterindex.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": null,
   "text": "We seem to be writing to disk to eagerly here, this also is misaligned with the batching."
  },
  {
   "t": "2026-03-09T17:37:39Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "state": "CHANGES_REQUESTED",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "text": "Concept ACK on the goal: batching is the right direction for reducing write amplification, `cs_main` contention, and LDB file proliferation.\n\nHowever, I think it's important to stress my concern that this change is more complex than it's being treated as.\nIt alters the invariants every index subclass relies on: in-memory state can now advance ahead of what's persisted, and the interrupt/shutdown paths haven't been fully thought through for each subclass.\nThe most serious issue seems to me that `Commit()` during interrupt writes advanced `m_muhash` alongside a stale locator, which causes `CoinStatsIndex::CustomInit` to reject the index as corrupted on restart. `blockfilterindex` has a milder variant (orphaned flat file data). I wonder if fuzzing could catch these.\n\nMore generally, several \"no behavior change\" refactoring commits don't explain why the restructuring is safe, and memory concerns are brushed away with \"shouldn't be a problem\".\nI also left a ton of comments about the `ProcessBlocks` loop needing tighter bounds and the shutdown test only exercising a no-op `BaseIndex` subclass. It doesn't catch the actual subclass corruption paths.\nSome of the earlier commits (e.g. the iterator ordering fix) could land as standalone PRs to reduce the review surface here.\n\nSee inline comments for details on these and other issues (interrupt handler scoping, `NextSyncBlock` invariants, loop safety, stale comments, etc.).\n\n[quoted text omitted]"
  },
  {
   "t": "2026-03-14T12:43:56Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "FWIW, I didn't say that tweaking the value has to be part of this PR. I was just curious how this value was picked and didn't judge it. It isn't even related to this comment direct but the time of my initial pass there were no benchmarks as far as I can remember and I had a bit of fear that this might get merged quickly and could end up in v31 without benchmarks and without the multithreading for which this is part of the setup. In that case I would have been a bit concerned that this might still introduce a performance penalty in a release. But the benchmarks have been run now and quite extensively and I haven't seen any penalty from this change. I think the follow-up that tweaks the value (if it turns out it can be optimized) as well as the multithreading should be tagged accordingly so they can make it into the same release as this change, if that is the case then I don't think my comment should be considered a blocker."
  },
  {
   "t": "2026-03-15T16:37:21Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "[quoted text omitted]\n\nI'm puzzled here. You are claiming this is a behavior change without explaining how, and based on that, you write a strong affirmation for other reviewers rather than engaging with me about what you think this change introduces. That is not something I can act on, and it risks misleading other reviewers.\n\nThis is not a behavior change, it is a correctness change. The loop iterator that tracks the last successfully processed block was modified to be updated after processing the block, not before. The reordering of `pindex = pindex_next` to after the `ProcessBlock` call has zero effects on any execution path, because we immediately return if block processing fails, and at that point `pindex` is never read again. If you believe otherwise, please point to the specific code path you think is affected. This change was made to prepare the ground for the batch end block set, which is the actual focus of this PR. Splitting it out into a separate PR would add unnecessary overhead for a one-line reordering with no behavioral effect.\n\nOn the commit message: if the description was not clear enough, just say so. I would have updated it. Engaging directly about what is unclear is far more constructive than making assertions aimed at other reviewers.\n\nOn tone: \"it wasn't obvious to the original authors\" is truly unhelpful. It does not engage with the code, it does not help improve anything, and it makes it genuinely harder to engage with your other comments in good faith, even the ones that may be perfectly valid. The purpose of review is to work with the author to improve the code, not to editorialize for other readers."
  },
  {
   "t": "2026-03-15T16:40:51Z",
   "kind": "review",
   "who": "furszy",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "text": "Clarifying one of the comments."
  },
  {
   "t": "2026-03-15T23:39:54Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "[quoted text omitted]\n\nNot sure what you mean, I commented on your PR, meant it for you to read it.\n\n[quoted text omitted]\nI'm not sure about the past tense, I reviewed it because I'm expecting further changes, especially because of the bugs introduced here.\n\n[quoted text omitted]\nHow so? I don't know or care who wrote the original code, they had an intention here, if we're changing it, it needs explanation and preferably tests."
  },
  {
   "t": "2026-03-16T14:32:28Z",
   "kind": "review_comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "[quoted text omitted]\n\nPlease stick to the topic in question. I have not yet read your other comments on this PR. This is the first one, and it makes a strong claim without a proper explanation to back it up.\n\nCan you please point to the specific code path where this commit introduces a behavior change? Thanks."
  },
  {
   "t": "2026-03-16T14:45:38Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "[quoted text omitted]\n\nThat explains the confusion.\n\n[quoted text omitted]\nPlease read the rest of my comments, that's why I posted them..."
  },
  {
   "t": "2026-03-16T15:05:36Z",
   "kind": "review_comment",
   "who": "sipa",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "It seems obvious to me that 95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be cannot change behavior; it's only swapping the change of local variable with a return statement, at which point the local variable disappears anyway. So I agree with @furszy that I don't understand what the comment in https://github.com/bitcoin/bitcoin/pull/34489/changes/95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be#r2905123979 is about, independently from all other comments left on this PR."
  },
  {
   "t": "2026-03-16T15:33:17Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.cpp",
   "commit": "95fc91fc99a14c6635c13ba7b6a8e7c2a1efe7be",
   "in_reply_to": 2905123979,
   "text": "This commit does not explain why the swap is needed. That only becomes clear later, when a non-adjacent commit relies on `pindex` meaning \"last successfully processed block\" for the new interrupt path, see [#34489 (comment)](https://github.com/bitcoin/bitcoin/pull/34489/changes/0336c61aacf01a503f75a8e35d6bec9954ea6840#r2905237631).\n\nSo the issue is not this line movement in isolation, but that it is a preparatory change whose purpose is only revealed several commits later, in a commit that changes behavior while claiming to be a refactor. I think those commits should be squashed and the behavior change either fixed or documented."
  },
  {
   "t": "2026-03-17T06:14:51Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "[quoted text omitted]\n\nOk, but I did, we can't just add a random value here, we have to back it by some measurements.\nI have measured a batch size of 1000 and it doesn't seem to reduce the size further. The implementation still contains some bugs, so we probably have to remeasure after they're fixed. I'm also curious about the peak memory usage here.\n\n[quoted text omitted]\nINDEX_BATCH_SIZE = 1000\n\n```bash\nBEFORE=\"4c40a923f003420193aa574745f70788bcf35265\"; AFTER=\"1497086cdb0e64df861100e0715ffd975d4c41cf\"; \\\nDATA_DIR=\"/mnt/my_storage/BitcoinData\"; export DATA_DIR; \\\nRESULTS_FILE=\"${DATA_DIR}/index_benchmark_results.txt\"; export RESULTS_FILE; \\\nwait_index() { \\\n  tail -F ${DATA_DIR}/debug.log 2>/dev/null | grep -q -m1 'index is enabled\\|is enabled at height'; \\\n  sleep 2; \\\n  killall bitcoind 2>/dev/null || true; \\\n  wait; \\\n}; export -f wait_index; \\\nlog_size() { \\\n  local label=\"$1\"; local index_dir=\"$2\"; \\\n  local size=$(du -sh \"$index_dir\" 2>/dev/null | cut -f1); \\\n  local files=$(find \"$index_dir\" -type f -name '*.ldb' 2>/dev/null | wc -l); \\\n  echo \"$label: size=$size, ldb_files=$files\" | tee -a \"$RESULTS_FILE\"; \\\n}; export -f log_size; \\\ngit reset --hard >/dev/null 2>&1 && git clean -fxd >/dev/null 2>&1 && git fetch origin $BEFORE $AFTER >/dev/null 2>&1; \\\nfor c in $BEFORE:build-before $AFTER:build-after; do \\\n  git checkout ${c%:*} >/dev/null 2>&1 && \\\n  sed -i 's/INDEX_BATCH_SIZE = 500/INDEX_BATCH_SIZE = 1000/' src/index/base.h && \\\n  cmake -B ${c#*:} -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C ${c#*:} bitcoind >/dev/null 2>&1; \\\ndone; \\\necho \"indexes | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $DATA_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $DATA_DIR | tail -1) 2>/dev/null | grep -q 0 && echo SSD || echo HDD)\" | tee -a \"$RESULTS_FILE\" && \\\nfor INDEX in txindex blockfilterindex coinstatsindex txospenderindex; do \\\n  echo -e \"\\n--- $INDEX ---\" | tee -a \"$RESULTS_FILE\"; \\\n  if [ \"$INDEX\" = \"blockfilterindex\" ]; then \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/blockfilter/basic\"; \\\n  else \\\n    INDEX_DIR=\"${DATA_DIR}/indexes/${INDEX}\"; \\\n  fi; \\\n  export INDEX_DIR; \\\n  for BUILD in before after; do \\\n    if [ \"$BUILD\" = \"before\" ]; then COMMIT=\"${BEFORE:0:7}\"; else COMMIT=\"${AFTER:0:7}\"; fi; \\\n    hyperfine --runs 1 --shell bash --sort command \\\n      --prepare \"rm -rf ${DATA_DIR}/indexes/* ${DATA_DIR}/debug.log\" \\\n      --cleanup \"log_size '${INDEX} ${BUILD}' '${INDEX_DIR}'\" \\\n      -n \"${BUILD} (${COMMIT})\" \\\n      \"./build-${BUILD}/bin/bitcoind -datadir=${DATA_DIR} -${INDEX}=1 -connect=0 -printtoconsole=0 & wait_index\" \\\n      2>&1 | tee -a \"$RESULTS_FILE\"; \\\n  done; \\\ndone;\nindexes | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | ext4 | HDD\n\n--- txindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        23766.792 s               [User: 13249.392 s, System: 1700.229 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        21667.695 s               [User: 12747.999 s, System: 1444.935 s]\n\n--- blockfilterindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        14074.504 s               [User: 8335.840 s, System: 419.536 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        13987.488 s               [User: 8275.042 s, System: 416.125 s]\n\n--- coinstatsindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        40104.717 s               [User: 34332.346 s, System: 408.091 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        39729.018 s               [User: 34321.404 s, System: 400.228 s]\n\n--- txospenderindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        36491.202 s               [User: 23663.602 s, System: 2216.541 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        26654.635 s               [User: 22664.639 s, System: 1659.436 s]\n```\n\nI will measure `INDEX_BATCH_SIZE = 100` next,"
  },
  {
   "t": "2026-03-19T22:52:02Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "Finished `INDEX_BATCH_SIZE = 100`:\n\nDetails\n\n```bash\nBEFORE=\"4c40a923f003420193aa574745f70788bcf35265\"; AFTER=\"1497086cdb0e64df861100e0715ffd975d4c41cf\"; DATA_DIR=\"/mnt/my_storage/BitcoinData\"; export DATA_DIR; RESULTS_FILE=\"${DATA_DIR}/index_benchmark_results.txt\"; export RESULTS_FILE; wait_index() {   tail -F ${DATA_DIR}/debug.log 2>/dev/null\n| grep -q -m1 'index is enabled\\|is enabled at height';   sleep 2;   killall bitcoind 2>/dev/null || true;   wait; }; export -f wait_index; log_size() {   local label=\"$1\"; local index_dir=\"$2\";   local size=$(du -sh \"$index_dir\" 2>/dev/null | cut -f1);   local files=$(find \"$index_dir\" -type f -name '*.ldb' 2>/dev/null | wc -l);   echo \"$label: size=$size, ldb_files=$files\" | tee -a \"$RESULTS_FILE\"; }; export -f log_size; git reset --hard >/dev/null 2>&1 && git clean -fxd >/dev/null 2>&1 && git fetch origin $BEFORE $AFTER >/dev/null 2>&1; for c in $BEFORE:build-before $AFTER:build-after; do   git checkout ${c%:*} >/dev/null 2>&1 &&   sed -i 's/INDEX_BATCH_SIZE = 500/INDEX_BATCH_SIZE = 100/' src/index/base.h &&   cmake -B ${c#*:} -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C ${c#*:} bitcoind >/dev/null 2>&1; done; echo \"indexes | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $DATA_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $DATA_DIR | tail -1) 2>/dev/null | grep -q 0 && echo SSD || echo HDD)\" | tee -a \"$RESULTS_FILE\" && for INDEX in txindex blockfilterindex coinstatsindex txospenderindex; do   echo -e \"\\n--- $INDEX ---\" | tee -a \"$RESULTS_FILE\";   if [ \"$INDEX\" = \"blockfilterindex\" ]; then     INDEX_DIR=\"${DATA_DIR}/indexes/blockfilter/basic\";   else     INDEX_DIR=\"${DATA_DIR}/indexes/${INDEX}\";   fi;   export INDEX_DIR;   for BUILD in before after; do     if [ \"$BUILD\" = \"before\" ]; then COMMIT=\"${BEFORE:0:7}\"; else COMMIT=\"${AFTER:0:7}\"; fi;     hyperfine --runs 1 --shell bash --sort command       --prepare \"rm -rf ${DATA_DIR}/indexes/* ${DATA_DIR}/debug.log\"       --cleanup \"log_size '${INDEX} ${BUILD}' '${INDEX_DIR}'\"       -n \"${BUILD} (${COMMIT})\"       \"./build-${BUILD}/bin/bitcoind -datadir=${DATA_DIR} -${INDEX}=1 -connect=0 -printtoconsole=0 & wait_index\"       2>&1 | tee -a \"$RESULTS_FILE\";   done; done;\nindexes | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | ext4 | HDD\n\n--- txindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        23968.560 s               [User: 13214.417 s, System: 1732.668 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        22914.283 s               [User: 13153.074 s, System: 1706.380 s]\n\n--- blockfilterindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        14056.593 s               [User: 8332.068 s, System: 414.447 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        14055.667 s               [User: 8282.042 s, System: 413.627 s]\n\n--- coinstatsindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        40062.595 s               [User: 34294.097 s, System: 401.118 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        39953.151 s               [User: 34338.162 s, System: 394.930 s]\n\n--- txospenderindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        36106.469 s               [User: 23462.741 s, System: 2216.890 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        32262.354 s               [User: 22250.924 s, System: 1963.755 s]\n```"
  },
  {
   "t": "2026-03-22T13:30:01Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "Finished `INDEX_BATCH_SIZE = 300`:\n\nDetails\n\n```bash\n--- txindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        23916.303 s               [User: 13219.568 s, System: 1732.442 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        22366.743 s               [User: 12985.083 s, System: 1581.649 s]\n\n--- blockfilterindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        14148.605 s               [User: 8334.721 s, System: 415.524 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        14078.675 s               [User: 8292.390 s, System: 414.992 s]\n\n--- coinstatsindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        40208.707 s               [User: 34359.943 s, System: 401.578 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        40035.152 s               [User: 34408.491 s, System: 395.890 s]\n\n--- txospenderindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        36154.602 s               [User: 23454.551 s, System: 2234.643 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        27110.711 s               [User: 21378.361 s, System: 1703.907 s]\n```\n\nI'd be interested in the extra memory consumption of these to help with choosing a good batch value."
  },
  {
   "t": "2026-03-22T22:01:15Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "I was thinking to buy and external disk (HDD) and try to benchmark the memory impact, would it be interesting or having the blockchain and the index on different disks make the test not useful?"
  },
  {
   "t": "2026-03-22T23:19:29Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nfor memory profiling it shouldn't matter - just know that these measurements are notoriously slow"
  },
  {
   "t": "2026-03-25T12:27:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2758183422,
   "text": "Finished batch=700:\n\nDetails\n\n```\n--- txindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        24201.390 s               [User: 13227.946 s, System: 1742.760 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        21362.662 s               [User: 12664.631 s, System: 1473.880 s]\n\n--- blockfilterindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        14179.578 s               [User: 8343.194 s, System: 421.597 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        14020.574 s               [User: 8288.567 s, System: 420.177 s]\n\n--- coinstatsindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        40028.484 s               [User: 34260.584 s, System: 409.004 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        39803.985 s               [User: 34369.698 s, System: 403.067 s]\n\n--- txospenderindex ---\nBenchmark 1: before (4c40a92)\n  Time (abs \u2261):        36574.351 s               [User: 23483.284 s, System: 2245.537 s]\n\nBenchmark 1: after (1497086)\n  Time (abs \u2261):        26134.814 s               [User: 21970.124 s, System: 1648.552 s]\n```\n\n----\n\nSummarizing the measurements so far looks like this:\n\nIndex | batch=100 | batch=300 | batch=500 | batch=700 | batch=1000\n-- | -- | -- | -- | -- | --\ntxindex | -4.4% | -6.5% | -11.0% | -11.7% | -8.8%\nblockfilterindex | -0.0% | -0.5% | -1.1% | -1.1% | -0.6%\ncoinstatsindex | -0.3% | -0.4% | -0.5% | -0.6% | -0.9%\ntxospenderindex | -10.6% | -25.0% | -27.4% | -28.5% | -27.0%"
  },
  {
   "t": "2026-03-29T11:30:50Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "@furszy Do you think #34897 has any implications on this PR and #26966? If indexes can only commit up to the chainstate's last flushed block, the batching and parallelisation speedups might be limited by the flush interval (~50-70 min) rather than by index processing itself. Would it make sense to keep the current per-block behaviour during IBD (where the index is catching up and flush lag matters most) and use batching only for fully synced nodes processing new blocks  using new methods?? Another option could be each index sets its own batch size. For example, `blockfilterindex` (required during IBD) could use a batch size of 1 to keep commit granularity tight, while optional indexes like `coinstatsindex` or `txindex` could use larger batches since their commit timing is less critical.\n\nPS: I got my chain synced, will try to do the memory benchmark this week"
  },
  {
   "t": "2026-05-27T13:39:11Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "in 64578a7888ed78a11e8ccf1742ed2e6a8a033b46 index: make NextSyncBlock return block ranges\nProbably worth not using auto here so it's visible that it can not have a value and that means it is the tip.\nAlso I think you meant if block_range is not set? not range.first"
  },
  {
   "t": "2026-05-27T13:43:51Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/index/base.cpp",
   "commit": "64578a7888ed78a11e8ccf1742ed2e6a8a033b46",
   "in_reply_to": null,
   "text": "in 64578a7888ed78a11e8ccf1742ed2e6a8a033b46 index: make NextSyncBlock return block ranges\nI guess `ProcessBlocks` can just take the new `BlockRange` struct."
  },
  {
   "t": "2026-05-27T14:08:56Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/index/base.h",
   "commit": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "in_reply_to": null,
   "text": "in 1628b1005a74becbd82e128023bcdd70b86cc834 index: add block range computation\nwhy 500? is it an arbitrary number?"
  },
  {
   "t": "2026-05-27T14:21:36Z",
   "kind": "review",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "text": "concept ACK\n\nJust did a swift code review for the Spanish PR review club :)\n\nI'm 0 familiar with this part of the codebase, but left some comments that \u00bfmight? be useful.\n\nI found the first two commits a bit difficult to understand because, although it is obvious that it is not a behavior change, there's no explanation in the commit message on why those changes are needed. I think a small sentence saying why that helps is useful :)"
  },
  {
   "t": "2026-05-27T15:20:22Z",
   "kind": "review_comment",
   "who": "arejula27",
   "assoc": "NONE",
   "path": "src/index/base.h",
   "commit": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "in_reply_to": 3311341587,
   "text": "I think that was decided as an arbitrary small number just for the first approach, then each index will have different batch sizes based on the benchmarks"
  },
  {
   "t": "2026-05-30T12:11:04Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/base.h",
   "commit": "1628b1005a74becbd82e128023bcdd70b86cc834",
   "in_reply_to": 3311341587,
   "text": "https://github.com/bitcoin/bitcoin/pull/34489#discussion_r2944626098"
  },
  {
   "t": "2026-08-23T19:30:58Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "I working on rebasing this PR onto current master. I think i am quite advanced. Hoping to have it ready in two weeks(not a promise cuz i will go on vacation next week).\n\nThis PR nows not only have conflicts but also requires hard changes due to some merged PR. Things I'm keeping in mind/focusing:\n- Index tests moved to `baseindex_tests `and now run against every index type. #35847\n- `txindex `block sequence counter now lives in memory. Reading it from disk per block collides once writes are batched. #35531\n- Commit() no longer persists ahead of the flushed chainstate, which changes what the shutdown test can show. #34897"
  },
  {
   "t": "2026-08-23T22:10:07Z",
   "kind": "comment",
   "who": "furszy",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\n@arejula27 let's talk via DM to organize the remaining work here better. Rebasing this one shouldn't take me more than an hour, there is just one missing check regarding the race condition that we should consider properly. I just didn't do it waiting for #35847, and other feature freeze priorities. Should have more availability these coming weeks, happy to tackle the remaining work together."
  },
  {
   "t": "2026-08-23T22:23:07Z",
   "kind": "comment",
   "who": "arejula27",
   "assoc": "NONE",
   "text": "I have the PR already rebased but i wanted to review it more before sending it to u, i can share it but if it only requires one hour of your time maybe it is faster if you do it directly instead of reviewing mine.\n\nHappy to discuss this on DM and organise the remaining work to collaborate \ud83d\ude01, idk which chat do you prefer"
  },
  {
   "t": "2026-08-28T16:30:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/index/blockfilterindex.cpp",
   "commit": "1497086cdb0e64df861100e0715ffd975d4c41cf",
   "in_reply_to": 2906884044,
   "text": "@furszy, could we fix this as well (in a separate PR or here) before we continue?\nNot sure how to fix it before this PR, but maybe we could fix it here. Let me know how I can help."
  }
 ],
 "labels_log": [
  {
   "t": "2026-02-03T04:06:39Z",
   "action": "labeled",
   "label": "UTXO Db and Indexes",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-03T04:42:45Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-04T16:19:20Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-10T01:23:20Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-10T02:42:28Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-14T18:04:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-16T06:01:30Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-18T18:33:02Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-18T20:45:08Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-02T14:35:44Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-03T22:08:34Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-30T13:24:37Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-05-08T14:48:34Z",
   "kind": "convert_to_draft",
   "who": "fanquake"
  }
 ],
 "text_chars": 76278,
 "text_tokens_estimate": 19069,
 "changed_paths": [],
 "files": [],
 "test_lines": null,
 "git": null,
 "input_hash": "37fbca92412652a4",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}