fix(hotblocks): enforce finalized fork floor#96
Open
mo4islona wants to merge 1 commit into
Open
Conversation
0737e03 to
45bf5ea
Compare
A fork whose common ancestor lies inside a finality-straddling chunk cannot resume at fin+1: that is a mid-chunk position, and insert_fork only replaces whole chunks, so the commit wedges the dataset on a 60s restart loop. Resuming below fin instead — the fallback's behaviour — silently rewrites the finalized prefix (GAP-22). An honest tip reorg triggers this whenever fin sits inside the head chunk, which is the common case, not just equivocation. Resolve the fork at the stored chunk boundary (which may sit at or below fin) and move the finalized-prefix guard to the write path: a replacement reaching into the finalized region is admitted only when it reproduces the finalized block's hash there, and must span fin so the whole-chunk rewrite never transiently drops it; otherwise it is refused atomically. This keeps the existing whole-chunk replace — no chunk-splitting primitive — and routes every equivocation shape through one loud, bounded 60s loop instead of the clamp's silent retry spin. Honest reorgs above finality now recover. Pinned by seven write-controller unit tests and three ct4_finality black-box scenarios (equivocation refused at both the window floor and a straddling chunk; honest reorg recovers) — the recovery scenario was verified red against the clamp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45bf5ea to
fd80630
Compare
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.
Summary
fininside the head chunk could wedge the datasetfin) instead of clamping to a mid-chunkfin + 1thatinsert_forkcannot satisfy — no chunk-splitting primitive is introducedfinand reproducesfin's hash there; otherwise it is refused atomicallyresume_from/expected_parent_hashWhy not clamp to
fin + 1A fork whose common ancestor lies inside a finality-straddling chunk cannot resume at
fin + 1: that is a mid-chunk position, andinsert_forkonly replaces whole chunks, so the commit wedges the dataset on a 60-second restart loop. Resuming at the chunk boundary belowfinis safe because the write path — not the resume position — guards the finalized prefix: the whole-chunk rewrite is admitted only when it reproduces the finalized block's hash. This routes every equivocation shape through one loud, bounded 60-second loop instead of a silent finalized-prefix replacement, and lets honest reorgs above finality recover.Regression proof
rollback_from_straddling_chunk_resumes_at_chunk_boundary— chunks[0,5],[6,9],fin = 7, hint{8, fork-8}resumes from6(the boundary), not the mid-chunk8the clamp produced andinsert_forkrejectedct4_honest_reorg_into_straddling_chunk_recovers) was verified red against the clamp:fin + 1landed mid-chunk,insert_forkrefused it, and the dataset spun on the deterministic 60-second restart loop until the test timed out; green with the boundary resume — the service converges on the reorged chain and FINALIZED-HEAD is preserved, not rewoundct4_finality_equivocation_does_not_replace_finalized_prefix,ct4_straddling_chunk_rollback_respects_finalized_floor): the source rewrites a suffix includingfinwith a different hash and claims it final; fork resolution resumes at a chunk boundary and the write path refuses the rewrite, so HEAD, FINALIZED-HEAD and the accepted window stay fixed while the SUT keeps re-engaging the faulty sourceKnown limitation
If re-ingest flushes a chunk ending strictly below
finbefore reaching it (e.g. a data-availability-mask change between two adjacent finalized blocks), the write path refuses it and the dataset takes the same bounded 60-second loop. This partial-flush edge is orthogonal to GAP-22 and pre-existed this PR (the priorcan't fork safelybail wedged identically); tracked separately.Test plan
cargo test -p sqd-hotblocks(7 write-controller unit tests + 3ct4_finalityblack-box scenarios)cargo test -p sqd-hotblocks-harnesscargo clippy -p sqd-hotblocks-harness --all-targets -- -D warningscargo +nightly fmt --all -- --checkgit diff --check