Repository navigation
Continue an interrupted -reindex from the block files on any start (#906) - #915
Merged
Merged
Conversation
This was referenced Oct 6, 2026
bkeroack
force-pushed
the
fix/reindex-resume
branch
from
October 6, 2026 18:53
16ce119 to
860b60e
Compare
bkeroack
force-pushed
the
fix/replay-stop-on-sigterm
branch
from
October 6, 2026 19:35
6ce1f3f to
f3e81d7
Compare
bkeroack
force-pushed
the
fix/reindex-resume
branch
from
October 6, 2026 19:35
860b60e to
89a6a09
Compare
bkeroack
force-pushed
the
fix/replay-stop-on-sigterm
branch
from
October 6, 2026 22:44
f3e81d7 to
6b695c1
Compare
bkeroack
changed the base branch from
fix/replay-stop-on-sigterm
to
release/0.6
October 6, 2026 23:31
) 0.6.0 refused every start after an interrupted full -reindex but one with --reindex, which then wiped the database and replayed from genesis. A mainnet replay takes days, so every stop, reboot or settings change during one threw it away, and a package that passes --reindex once (StartOS's migration from Bitcoin Core) was left with a node that would not start. Now any start that finds an unfinished full reindex continues it, whatever the flags. The flat-file replay scans the block files and plans the chain as before. It then checks every block the interrupted run had stored against the scan, before connecting the rest: - the stored tip must be the planned block at its height; - each entry at or below it must be indexed at its planned height as Valid, with the header the files hold, at the position the scan found; - each entry's chainwork must equal the sum of its ancestors' header work. What the continued replay reads from those entries (parent links, chainwork, the timestamps behind median time past) is then backed by the block files, as in a replay from genesis. Nothing is downloaded and no block file is written. A reindex is continued only when its marker says so. The rebuild marker gains `resumable`, set once the wipe has finished and genesis is indexed and durable, just before the scan. A marker without it, from satd 0.6.0 or written before its wipe had finished, or one from another chainstate schema, runs the reindex again from genesis, still on any start. A stored chain that fails the check exits with ReindexCannotResume, having changed nothing; satd clears `resumable`, and the next start runs from genesis. A fresh reindex now refuses a chainstate past genesis. A continued run that had already reached -stopatheight connects nothing more, and indexes no side chain. A stop requested by #907's signal handling is continued by the next start like any other interruption. Tests: unit tests continue a stopped reindex onto the uninterrupted UTXO set, refuse a stored entry the files do not back, refuse a fresh reindex over a stored chain, handle a continued run past -stopatheight, and read a 0.6.0 marker as not resumable. Integration tests SIGKILL a reindex after its durable flush at height 1000, and during its scan, then check that a plain start continues: it logs so, clears nothing, writes no block file, and ends on the fixture's UTXO set. A 0.6.0 marker runs again from genesis on a plain start. #907's SIGTERM tests now finish with a plain start that continues. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dex (#906) The check before a continued reindex now also requires every height row above the stored tip to name the planned block at its height. A connect that moves no coins bypasses the coin cache's buffer, so a kill can leave its rows durable above the tip; over the same block files the replay writes them again, but a row naming another block would outlive it. The check also polls for a stop every 1000 entries. On mainnet it reads some 900k index entries, and a stop that waited for it could overrun -maxshutdownsecs and exit 1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bkeroack
force-pushed
the
fix/reindex-resume
branch
from
October 6, 2026 23:31
e051c34 to
eabd86e
Compare
This was referenced Oct 7, 2026
bkeroack
added a commit
that referenced
this pull request
Oct 7, 2026
) (#915) * Continue an interrupted -reindex from the block files on any start (#906) 0.6.0 refused every start after an interrupted full -reindex but one with --reindex, which then wiped the database and replayed from genesis. A mainnet replay takes days, so every stop, reboot or settings change during one threw it away, and a package that passes --reindex once (StartOS's migration from Bitcoin Core) was left with a node that would not start. Now any start that finds an unfinished full reindex continues it, whatever the flags. The flat-file replay scans the block files and plans the chain as before. It then checks every block the interrupted run had stored against the scan, before connecting the rest: - the stored tip must be the planned block at its height; - each entry at or below it must be indexed at its planned height as Valid, with the header the files hold, at the position the scan found; - each entry's chainwork must equal the sum of its ancestors' header work. What the continued replay reads from those entries (parent links, chainwork, the timestamps behind median time past) is then backed by the block files, as in a replay from genesis. Nothing is downloaded and no block file is written. A reindex is continued only when its marker says so. The rebuild marker gains `resumable`, set once the wipe has finished and genesis is indexed and durable, just before the scan. A marker without it, from satd 0.6.0 or written before its wipe had finished, or one from another chainstate schema, runs the reindex again from genesis, still on any start. A stored chain that fails the check exits with ReindexCannotResume, having changed nothing; satd clears `resumable`, and the next start runs from genesis. A fresh reindex now refuses a chainstate past genesis. A continued run that had already reached -stopatheight connects nothing more, and indexes no side chain. A stop requested by #907's signal handling is continued by the next start like any other interruption. Tests: unit tests continue a stopped reindex onto the uninterrupted UTXO set, refuse a stored entry the files do not back, refuse a fresh reindex over a stored chain, handle a continued run past -stopatheight, and read a 0.6.0 marker as not resumable. Integration tests SIGKILL a reindex after its durable flush at height 1000, and during its scan, then check that a plain start continues: it logs so, clears nothing, writes no block file, and ends on the fixture's UTXO set. A 0.6.0 marker runs again from genesis on a plain start. #907's SIGTERM tests now finish with a plain start that continues. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Check rows above the tip and poll for a stop before continuing a reindex (#906) The check before a continued reindex now also requires every height row above the stored tip to name the planned block at its height. A connect that moves no coins bypasses the coin cache's buffer, so a kill can leave its rows durable above the tip; over the same block files the replay writes them again, but a row naming another block would outlive it. The check also polls for a stop every 1000 entries. On mainnet it reads some 900k index entries, and a stop that waited for it could overrun -maxshutdownsecs and exit 1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit a0b69c6)
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #906. Stacked on #914 (#907): merge #914 first, then retarget this PR to
release/0.6.The problem
0.6.0 refused every start after an interrupted full
-reindexexcept one with--reindex, and that start wiped the database and replayed from genesis. A mainnet replay takes days, so every stop, reboot or settings change during one threw the work away. A package that passes--reindexonce, as StartOS's migration from Bitcoin Core does, was left with a node that would not start.The change
You chose this behaviour: any start that finds an unfinished full reindex continues it, whatever the flags.
How it continues.
resume_reindex_from_flat_filesscans the block files and plans the chain exactly as before. Before connecting anything,check_resumable_prefixchecks every block the interrupted run stored against the scan:Validat its planned height, with the header the files hold, at the file position the scan found;What the continued replay reads from those entries (parent links, chainwork, the timestamps behind median time past) is then backed by the block files, as in a replay from genesis. It then connects the rest. Nothing is downloaded and no block file is written. The check costs one index read per stored block.
When it can't continue. The rebuild marker gains
resumable. It is set once the wipe has finished and genesis is indexed and flushed durably, which is just before the scan. Three cases run the reindex again from genesis instead, still on any start:ReindexCannotResume, having changed nothing, and clearsresumable; the next start runs from genesis.This differs from Core in one case. Core also continues an interrupted reindex on a plain start, but
-reindexpassed again makes Core wipe and start over. Here it continues: you chose that so a package that keeps passing--reindex, or an operator following 0.6.0's "restart with --reindex" message, doesn't lose the replay. To start over, remove thechainstatedirectory.Smaller pieces:
reindex_from_flat_filesnow refuses a chainstate past genesis.-stopatheightconnects nothing more and indexes no side chain.-reindex-chainstateis unchanged: its flags restart it from genesis (reindex: support resuming an interrupted -reindex-chainstate #508).Tests
node:ReindexCannotResumeand connects nothing;-stopatheightconnects nothing;satd: the decision matrix across flag combinations. A resumable marker at the current schema continues; a 0.6.0 marker or one at another schema restarts.regtest):a_killed_full_reindex_continues_from_the_block_files: SIGKILL after the durable flush at height 1000.a_full_reindex_killed_before_it_connects_anything_continues: SIGKILL during the scan. If the kill lands after the first flush (a stalled test thread), it retries on a fresh copy, up to three times.a_full_reindex_left_by_0_6_0_runs_again_on_any_startreplaces the 0.6.0 refusal test.ContinueFullReindexto a restart fails the kill test.BadPrevBlock; either alone still skips it.resumableis set does not fail a kill test. Genesis reaches RocksDB through the cache's pass-through with the WAL on, which a kill doesn't lose, so the flush only adds power-cut safety. The test notes say so.Local gates
cargo test --all --locked: 442 of 453 regtest tests pass. The 11 failures are the address-index backfill tests, whose disk guard wants 80 GiB free (this machine has 76 GB on/tmp).Since opening
resume_fromoffset in its progress count.test_esplora_address_txs_mempool_visibility, which also failed locally under load and passes alone. It is unrelated to this change.Not done
A continued reindex reads every block file in full again before it connects anything: about 700 GB on mainnet, more than an hour on a hard drive. Filed as A continued -reindex reads every block file in full before it connects anything #916, to fix after these two.
No integration test drives the
ReindexCannotResumeexit path end to end. That needs a stored chain the files contradict, which the regtest harness can't produce without writing RocksDB rows by hand. The check itself is unit-tested, and the exit path is a few lines inmain.rs.Reindex throughput (Start9's other point) is untouched.
🤖 Generated with Claude Code