Repository navigation
Forward-port #914 and #915 (0.6.1 StartOS fixes) to master - #926
Merged
Merged
Conversation
…914) * Stop a startup block replay cleanly on SIGTERM, also as PID 1 (#907) satd registered its SIGTERM and SIGINT handlers only once startup had finished, and the block replays of -reindex, -reindex-chainstate and -upgradechainstate run inside startup. Until then a stop signal had its default effect: it killed the replay part-way through a batch, or, with satd as PID 1 of its PID namespace (a container with no init), it was ignored until the supervisor's SIGKILL. Start9's review of the StartOS package hit the second: docker stop ran out its 600 s grace period. Both signals are now registered at the top of run(). Until startup finishes, a watcher acts on them through node::shutdown::StartupStop: - During a replay the stop is left to the replay, which polls before each block, and between block files while scanning. It stops after the block in hand, flushes durably, and returns Stopped (flat-file reindex) or stopped (chainstate replay). satd keeps the rebuild marker and exits 0. If that takes longer than -maxshutdownsecs, the watcher exits without it. - Anywhere else in startup the process ends at once, as before: the default action is restored and the signal raised again. As PID 1 the raise is ignored, so satd exits with 128 + the signal number. The wait loop takes the same registrations once startup has finished. A shutdown after startup now also logs each wait before it starts (the block connector, the UTXO cache flush), with its budget, so a stop that runs long says in its last line what it is waiting on. Tests: unit tests stop each replay at a chosen poll, and each poll fails its test when removed. Integration tests send SIGTERM as the connect phase of -reindex and -reindex-chainstate starts, against a 6000-block fixture; satd as PID 1 under sudo unshare --pid (CI only; skipped without passwordless sudo, failing under CI); and SIGTERM during IBD of 1 MB blocks, which must exit 0 within 10 s with the shutdown waits logged in order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Abandon the block in hand when a replay is asked to stop (#907) A stop request used to wait for the block being connected to finish. On a slow disk that is the slow part: the flat-file replay has no prefetcher, so every coin lookup that misses the cache is a random read. On a hard drive a block's lookups alone can take seconds. connect_block only builds a batch and writes nothing, so a block it abandons leaves the chainstate at the block before. ConnectParams gains `interrupt`, which the two startup replays set to their stop request. connect_block polls it: - after each chunk of 1024 coin lookups (one batch when `interrupt` is None, so the IBD path is unchanged); - after the lookups; - every 64 transactions of script verification, in the serial and the parallel path. It returns ConnectError::Interrupted, which is not a verdict on the block. The replays turn it into the same flushed stop as one between blocks. A script failure another verification worker reached outranks the stop. Tests: connect_block's three polls each stop it with no batch. Both replays abandon block 6 when the poll inside its connect requests the stop, and end at block 5. Each poll and each mapping fails a named test when removed. The parallel-path poll is not covered: the fixture block has a single transaction to verify, so it takes the serial path. The SIGTERM-during-IBD test no longer asserts that the node stopped below the tip. The signal is sent mid-sync, which the test still checks, but on a fast CI runner the connector had connected the remaining large blocks before it saw the stop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Mine the replay test fixture until it is long enough, tolerating timeouts The shared 6000-block fixture was mined in calls of 100 blocks through rpc_ok, which panics on a transport error. With other regtest tests running beside it, a call can outlast the harness's 10 s RPC timeout: the fixture failed twice that way locally, taking a test with it. It now mines 25 blocks a call and loops on getblockcount until the chain is long enough, ignoring a failed call: one that timed out may still have mined its blocks, so the count, not the call, decides. 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 7ad4d89)
) (#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.
Brings the two Start9 fixes merged into
release/0.6for 0.6.1 back tomaster, one commit each, cherry-picked with-x:7ad4d894, fixes SIGTERM during -reindex / -reindex-chainstate is ignored or kills mid-batch: no handler until startup finishes #907): SIGTERM or SIGINT during-reindex,-reindex-chainstateor-upgradechainstatestops the replay at once. satd abandons the block it is connecting, flushes what it had connected, keeps the rebuild marker and exits 0. That includes satd running as PID 1 with no init. Anywhere else in startup a stop signal ends satd at once, as PID 1 too.a0b69c6f, fixes An interrupted full -reindex starts over instead of resuming from the block files #906): any start continues an interrupted full-reindexfrom the height it had flushed. Before replaying the rest, it checks every stored entry against the block files. A reindex that cannot be continued runs again from genesis.Both commits applied cleanly except for the release-notes files. The code patch is identical to the one on
release/0.6: thegit patch-idof7ad4d894^..a0b69c6f(all paths exceptCHANGELOG.mdanddocs/release-notes/) equals that of this branch's diff. The manual changes (ibd.md,packaging.md) come along.Release notes
The two
CHANGELOG.mdbullets and theirdocs/release-notes/0.6.1-pre.mdsections stay onrelease/0.6. They describe 0.6.1 and will reachmasterwith the 0.6.1 cut, as in #921. Nothing is added to0.7.0-pre.md.Merging
"Rebase and merge" keeps the two commits separate, each with its
cherry picked fromline.Verified
Ran locally on this branch:
cargo clippy --all-targets --all-features --locked -- -D warningscargo test --test e2e --no-run --locked --features e2enodelib suite: 2147 passedsatdbin tests: 307 passedOn
release/0.6, each PR passed CI and a Devin review before it merged.A follow-up, #916 (the reindex scan reads only block headers), is in review against
release/0.6and will need its own forward-port.🤖 Generated with Claude Code