From bcaaad285ff7efe7ea5c0be1f6fb26590f8ddaa5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 3 Jul 2026 10:12:21 -0300 Subject: [PATCH] fix(sequencer): do not run fallback actions within build window Sequencer defines a set of fallback actions (voting and pruning) which are meant to be executed if it fails to propose a checkpoint, due to sync errors. However, those actions were being triggered as soon as the sequencer sync check failed, even if in the next sequencer iteration the check succeeded and the sequencer managed to produce a block. Now, the sequencer waits until it's past its build deadline (too late within the build slot to actually build anything), and only then triggers the fallback actions. This gives the sequencer more changes to bundle the fallback actions together into a single multicall. --- .../src/sequencer/sequencer.test.ts | 36 +++++++++++++++---- .../src/sequencer/sequencer.ts | 14 ++++---- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index ffd7a9fc9ea1..6214cf57e6ba 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -863,7 +863,10 @@ describe('sequencer', () => { expect(publisher.sendRequestsAt).toHaveBeenCalled(); }); - it('should vote when sync fails even within the build time limit', async () => { + it('does not run fallback actions when sync fails before the build start deadline', async () => { + // A transient sync miss with time still left to build must not trigger fallback actions: the + // work loop should retry on a later tick once sync recovers. In particular it must not send a + // standalone prune, which would give up the slot prematurely. const startDeadline = sequencer.getTimeTable().getBuildStartDeadline(SlotNumber(newSlotNumber)); dateProvider.setTime((startDeadline - 1) * 1000); @@ -876,12 +879,11 @@ describe('sequencer', () => { await sequencer.work(); - expect(publisher.enqueueSlashingActions).toHaveBeenCalledWith( - mockSlashActions, - SlotNumber(newSlotNumber), - expect.any(EthAddress), - expect.any(Function), - ); + expect(publisher.enqueueSlashingActions).not.toHaveBeenCalled(); + expect(publisher.enqueuePruneIfPrunable).not.toHaveBeenCalled(); + expect(publisher.sendRequestsAt).not.toHaveBeenCalled(); + // The slot is left unmarked so a later work-loop tick can retry once sync recovers. + expect(sequencer.getLastSlotForCheckpointProposalJob()).toBeUndefined(); }); it('should not vote when sync fails but not a proposer', async () => { @@ -980,6 +982,26 @@ describe('sequencer', () => { expect(publisher.sendRequestsAt).not.toHaveBeenCalled(); }); + it('does not enqueue a standalone prune before the build deadline even when the rollup is prunable', async () => { + // Standalone prune is reserved for when we can no longer build the slot (past the build start + // deadline). Before the deadline, a transient sync miss must retry rather than prune the pending + // chain, even if the rollup happens to be prunable at the target slot. + const startDeadline = sequencer.getTimeTable().getBuildStartDeadline(SlotNumber(newSlotNumber)); + dateProvider.setTime((startDeadline - 1) * 1000); + + // Set us as the proposer + validatorClient.getValidatorAddresses.mockReturnValue([signer.address]); + epochCache.getProposerAttesterAddressInSlot.mockResolvedValue(signer.address); + + // The rollup is prunable, but we are still within the build window. + publisher.enqueuePruneIfPrunable.mockResolvedValue(true); + + await sequencer.work(); + + expect(publisher.enqueuePruneIfPrunable).not.toHaveBeenCalled(); + expect(publisher.sendRequestsAt).not.toHaveBeenCalled(); + }); + it('should enqueue prune alongside votes and send a single request', async () => { const startDeadline = sequencer.getTimeTable().getBuildStartDeadline(SlotNumber(newSlotNumber)); dateProvider.setTime((startDeadline + 1) * 1000); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 19451a6f475d..04cb28f2bbf4 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -452,13 +452,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter TypedEventEmitter