fix(derivation): clamp deriveForce skipNumber to batch tip#983
Merged
Conversation
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
d02fbc2 to
ba8d358
Compare
Scenario-C dispatch (lastBlockNumber missing locally + !l2Grew) is decided in derivationBlock BEFORE reactors are quiesced; the localLatest passed into deriveForce is read AFTER StopReactorsBeforeReorg. In the window between the dispatch decision and the Stop, blocksync can backfill past the batch tip. When that happens skipNumber >= rollupData.lastBlockNumber, the existing loop short-circuits every block via the `Number <= skipNumber` continue, and the function returns header(skipNumber) — a block past the batch. Upstream verifyBatchRoots and tagAdvancer.advanceSafe then run against that wrong header: roots compared against post-batch state (false stateException) and safe head pushed past the actual batch tip. With the clamp, the race materialising degrades to the same outcome scenario A would have produced once P2P caught up — verifyBatchRoots sees header(lastBlockNumber), advanceSafe pins safe to the correct batch tip. Targets PR #966 review (CodeRabbit comment r3340287543). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ac04c28 to
34bd70d
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
Targets the CodeRabbit review comment on #966 (r3340287543).
In
deriveForce,skipNumber >= rollupData.lastBlockNumberis reachable due to a small race between scenario-C dispatch and reactor quiesce; without a guard it returns a header past the batch tip and corrupts downstreamverifyBatchRoots/advanceSafe.Why the race exists
scenario-C entry in
derivationBlockis decided beforewithReactorsQuiescedstops the blocksync / broadcast reactors:Between T0 and T1+, blocksync can deliver blocks up to and past
lastBlockNumber(peers already hold them; we were just lagging).localLatestread at T2 reflects that catchup, soskipNumber >= lastBlockNumberis physically reachable even though it should not be by the dispatch logic.l2Grewis a coarse cross-poll growth signal — single misjudgements are tolerated by design. The clamp here is what makes that tolerance actually safe at the deriveForce layer.Symptom without the fix
When the race materialises:
if blockData.SafeL2Data.Number <= skipNumber { continue }skips every block.deriveForcereturnsheader(skipNumber)— a block strictly pastrollupData.lastBlockNumber.verifyBatchRoots(batchInfo, lastHeader)compares the batch's expected post-state / withdrawal roots against a later block → mismatch →SetBatchStatus(stateException)(spurious alarm).tagAdvancer.advanceSafe(batchIndex, lastHeader)pushes safe head past the batch tip →(batchIndex, safe)association is wrong.Fix
Add an early return in
deriveForcewhenskipNumber >= rollupData.lastBlockNumber:header(rollupData.lastBlockNumber)from the local node (it must exist by definition of the race).The early return runs inside
withReactorsQuiesced'sbody, so the deferredStartReactorsAfterReorgstill fires — reactors are restarted normally.Test plan
BatchInfowithlastBlockNumber <= skipNumberreturnsheader(lastBlockNumber)and writes nothing.stateExceptionis raised andsafeHeadmatchesbatchInfo.lastBlockNumber.skipNumber < lastBlockNumber) still write blocks as before.🤖 Generated with Claude Code