fix(slasher): anchor watcher scans at archiver synced L2 slot#23394
Merged
Conversation
Sentinel and BroadcastedInvalidCheckpointProposalWatcher both drove their periodic scans off the wallclock, so an L1 stall would let them speculate into slots whose L1 windows had not been ingested yet. Anchor all four sentinel call sites and the watcher's scan loop at `archiver.getSyncedL2SlotNumber() ?? epochCache.getSlotNow()`, mirroring the pattern already in DataWithholdingWatcher. Resolves A-709.
spalladino
approved these changes
May 19, 2026
| * L1 actually is. | ||
| */ | ||
| protected async getCurrentSlot(): Promise<SlotNumber> { | ||
| return (await this.archiver.getSyncedL2SlotNumber()) ?? this.epochCache.getSlotNow(); |
Contributor
There was a problem hiding this comment.
Should we start storing the "last L1 synced timestamp" in the archiver's db rather than just in memory, so we don't need the fallback?
This was referenced May 19, 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.
Summary
A-709 audit found two slashing watchers that drove their periodic scans off the wallclock rather than the archiver's last fully-synced L2 slot. An L1 stall (e.g. the fusaka scenario) would let them speculate into slots whose L1 windows had not yet been ingested, producing false-positive slashes or scanning gaps. This PR moves both onto
archiver.getSyncedL2SlotNumber() ?? epochCache.getSlotNow(), matching the pattern already inDataWithholdingWatcher.Changes
Sentinel— all fourgetSlotNow()call sites (init,work,computeStats,getValidatorStats) now go through a newgetCurrentSlot()helper that prefers the archiver synced slot, with wallclock fallback only at cold start. The redundant synced gate insideisReadyToProcessis kept as a defensive guard for the fallback path.BroadcastedInvalidCheckpointProposalWatcher— constructor now takes aPick<L2BlockSource, 'getSyncedL2SlotNumber'>(wired toarchiverat the construction site);scan()uses it instead ofepochCache.getCurrentAndNextSlot().Audit
Other
WANT_TO_SLASHemitters were checked and already synced-safe:DataWithholdingWatcher— already uses synced slot.AttestationsBlockWatcher— event-driven from archiverInvalidAttestationsCheckpointDetected.validator-clientemitters (BROADCASTED_INVALID_BLOCK_PROPOSAL,ATTESTED_TO_INVALID_CHECKPOINT_PROPOSAL,DUPLICATE_PROPOSAL,DUPLICATE_ATTESTATION) — event-driven from live p2p observations.Test plan
describe('init')with two red/green cases covering synced-slot floor and wallclock fallback. Red/green verified.slasherandaztec-nodeTypeScript builds clean.