Skip to content

Commit 6874e44

Browse files
authored
test(e2e): anchor e2e_amm PXE to checkpointed tip under pipelining (#23336)
## Why PR #23253 was dequeued (4th attempt) when `merge-queue-heavy` caught an `e2e_amm.test.ts` setup tx getting dropped by a pipelining-driven chain prune. CI log: `baec5a7453c20089`. The wait-for-parent gate in `CheckpointProposalJob.waitForValidParentCheckpointOnL1` (`sequencer-client/src/sequencer/checkpoint_proposal_job.ts:398`) **should** have blocked the discard, but it didn't — because a `TestDateProvider` time warp from `AnvilTestWatcher.syncDateProviderToL1IfBehind` landed **between** the two `epochCache` reads in `Sequencer.work` (`sequencer.ts:217-218`) and broke the pipelining invariant. | step | wall-clock | `nowSeconds` | result | |---|---|---|---| | 1st `getEpochAndSlotInNextL1Slot` (`slot`) | ≈14:34:32.385 (pre-warp) | `1778942079` | next L1 ts `1778942080` → **slot 18** | | (warp at 14:34:32.390 sets offset 7611 → 7610) | | | | | 2nd `getTargetEpochAndSlotInNextL1Slot` (`targetSlot`) | ≈14:34:32.395 (post-warp) | `1778942080` | next L1 ts `1778942084` → **slot 19** → `+offset=1` → **targetSlot 20** | Logged confirmation (gap = 2 instead of 1): ``` 14:34:32.612 Preparing checkpoint proposal 19 for target slot 20 during wall-clock slot 18 {nowSeconds=1778942079, slot=18, targetSlot=20, …} ``` With `slotNow = 18`, the gate at `checkpoint_proposal_job.ts:402` waits on `waitForSyncedL2SlotNumber(slotNow)`. The archiver had already synced past slot 18 — the wait returns immediately, far too early to see parent ckpt 18 (which lands four seconds later at 14:34:36). The gate then sees `checkpointedNumber=17, parentCheckpointNumber=18`, declares the parent absent, and discards. Slot 20 expires uncheckpointed, archiver prunes blocks 19/20, the inflight setup tx anchored to block 19 dies with `Block header not found`. Full timeline + log evidence: https://gist.github.com/AztecBot/4863d10084dd20587bffcc43fd61dfee ## What Scoped, test-only — per direction from Santiago. The previous "make `checkpointed` the global PXE default" approach is reverted; only `e2e_amm` is opted in: ```diff - } = await setup(4, { ...PIPELINING_SETUP_OPTS })); + } = await setup(4, { ...PIPELINING_SETUP_OPTS }, { syncChainTip: 'checkpointed' })); ``` The PXE option exists already (`yarn-project/pxe/src/config/index.ts`, added in `75df5b5d44`). This is the same approach every other pipelining-aware test uses (`e2e_p2p/*`, `e2e_epochs/*`, `e2e_slashing/attested_invalid_proposal`). It anchors inflight txs to the L1-confirmed tip so prunes on the proposed tip can't invalidate them. `PIPELINING_SETUP_OPTS` is left untouched — the pipelining migration of `e2e_amm` in #23275 stays. ## Recommended follow-up (separate PR) The real bug is the race in `Sequencer.work`. Worth fixing properly: - **Snapshot the time once.** Add `EpochCache.getCurrentAndTargetSlotInNextL1Slot()` that returns `{slot, targetSlot, epoch, targetEpoch, ts, nowSeconds}` from a single `dateProvider.nowInSeconds()` read; replace the two-call site in `Sequencer.work`. Pipelining offset is a constant, so deriving `targetSlot = slot + offset` from the same snapshot is trivial. - **Defensive: wait on `targetSlot - 1`.** `waitForValidParentCheckpointOnL1` should key off the parent's expected build slot (`targetSlot - 1`) instead of `slotNow`, so the gate is robust even if the invariant is broken upstream. These aren't in this PR because they touch sequencer production code and want their own review; the test-side workaround unblocks the merge-train without changing the global PXE default. ## Test plan The failure requires `merge-queue-heavy`'s 10-grind L1 contention to surface reliably (single dev box can't reproduce). Change is a single-arg addition; TS-trivial. Analysis: https://gist.github.com/AztecBot/4863d10084dd20587bffcc43fd61dfee ClaudeBox log: https://claudebox.work/s/166e664eab264b04?run=3
1 parent b38f967 commit 6874e44

1 file changed

Lines changed: 6 additions & 1 deletion

File tree

yarn-project/end-to-end/src/e2e_amm.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,17 @@ describe('AMM', () => {
4141
const INITIAL_TOKEN_BALANCE = 1_000_000_000n;
4242

4343
beforeAll(async () => {
44+
// Anchor the PXE to the checkpointed tip rather than the proposed tip. Under pipelining the
45+
// proposed tip can be pruned when a slot ends without a checkpoint landing on L1 (e.g. when a
46+
// time warp races `Sequencer.work`'s two epoch-cache reads and the wait-for-parent gate ends up
47+
// pointing at the wrong slot). The checkpointed tip is L1-confirmed and cannot be pruned, so
48+
// inflight setup txs survive the race.
4449
({
4550
teardown,
4651
wallet,
4752
accounts: [adminAddress, liquidityProviderAddress, otherLiquidityProviderAddress, swapperAddress],
4853
logger,
49-
} = await setup(4, { ...PIPELINING_SETUP_OPTS }));
54+
} = await setup(4, { ...PIPELINING_SETUP_OPTS }, { syncChainTip: 'checkpointed' }));
5055

5156
({ contract: token0 } = await deployToken(wallet, adminAddress, 0n, logger));
5257
({ contract: token1 } = await deployToken(wallet, adminAddress, 0n, logger));

0 commit comments

Comments
 (0)