fix(L1): freeze schedule changes within sequencer drift of activation - #409
Merged
Conversation
`ProtocolVersions` allowed the owner to clear or move an activation, and the incident responder to delay one, right up until L1 time reached the activation timestamp. Post-Fjord batch validation lets an L2 block carry a timestamp up to 1800s ahead of its L1 origin, so for the final 30 minutes before an activation the sequencer can already have produced the block that activates the upgrade while L1 still allows that activation to be cancelled or moved. Adds FREEZE_WINDOW = 30 minutes and requires a preexisting activation to still be more than that away before its timestamp can change, in both `setTimestamp` and `delayTimestamp`. This enforces onchain the operator rule that an upgrade timestamp is not touched close to activation. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
✅ Heimdall Review Status
|
The freeze added in the previous commit only covered the paths that change an activation already on the schedule. `registerUpgrade` writes its first timestamp directly, so appending an upgrade activating inside the sequencer-drift horizon still moved `activatedScheduleId` for L2 timestamps the sequencer may already have produced, and registration accepted timestamps arbitrarily far in the past. Registration now clears the same MIN_NOTICE floor `setTimestamp` applies, which exceeds FREEZE_WINDOW and so covers the drift horizon. Registering without a timestamp stays unconstrained, since `activatedScheduleId` skips zero entries. Importing a schedule that is already in the past is a real need for a chain with hardfork history, so `initialize` now takes the initial schedule. That confines unconstrained writes to deployment, before any proof game can have pinned a commitment from the registry, and the Base mainnet goldens confirm the import builds the same hash chain the equivalent registrations did. Co-authored-by: Cursor <cursoragent@cursor.com>
The initializer's schedule import was unreachable from the deploy scripts, which always passed an empty array. Since activations already in the past cannot be registered afterwards and the AggregateVerifier binds the registry immutably, a chain with existing hardfork history would have been stuck with an empty registry short of redeploying the proof stack. Take the schedule as a deploy input, sourced from an optional `protocolVersionsInitialSchedule` config key. Co-authored-by: Cursor <cursoragent@cursor.com>
The zero-timestamp exemption from MIN_NOTICE is already exercised by every test that registers an unscheduled upgrade, so the standalone case added no coverage beyond assertions made elsewhere. Co-authored-by: Cursor <cursoragent@cursor.com>
refcell
approved these changes
Aug 20, 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.
What changed? Why?
setTimestampanddelayTimestampnow reject mutations at or afteractivationTimestamp - FREEZE_WINDOW, preventing L1 from changing the schedule after an L2 block may already have activated it.MIN_NOTICEfloor to non-zeroregisterUpgradetimestamps, so appending an upgrade cannot change the activated schedule for L2 timestamps that are already reachable.protocolVersionsInitialScheduledeploy-config field and pass it into the initializer; omitted config retains an empty schedule.Notes to reviewers
How has it been tested?