perf(e2e): shrink e2e slot times#24570
Merged
Merged
Conversation
Set DEFAULT_L1_BLOCK_TIME to a plain 8 instead of `process.env.CI ? 12 : 8`. Local runs already used 8, so this only removes a CI-vs-local cadence asymmetry: default single-node L2 slots drop from 2 x 12 = 24s to 2 x 8 = 16s (-33%) across the fees / cross-chain / bot single-node families, exactly where the remaining inclusion-latency cost lives. eth=8 sits AT the fast-profile boundary (FAST_PROFILE_ETHEREUM_SLOT_DURATION = 8): fast-profile budget clamping only applies strictly below 8s, so the proposer keeps the production operational budgets, identical to the old eth=12 path. Guard math (budgets.ts: maxBlocksPerCheckpoint = floor((S - init - D - 2P - prepCp) / D) >= 1), default single-node config (D = DEFAULT_BLOCK_DURATION_MS/1000 = 3s, production budgets P=2, prepCp=1, init=1): before (eth=12): S = 2x12 = 24 -> floor((24 - 1 - 3 - 4 - 1)/3) = floor(15/3) = 5 blocks/checkpoint after (eth=8): S = 2x8 = 16 -> floor((16 - 1 - 3 - 4 - 1)/3) = floor(7/3) = 2 blocks/checkpoint Both >= 1. The after-config is what every single-node suite already runs locally. Only tests that rely on the default eth slot are affected. Tests that set ethereumSlotDuration explicitly (PIPELINING_SETUP_OPTS/bot at eth 4, the reorg and block-production profiles at eth 4/6/12) are unchanged. The only other process.env.CI timing coupling in the fixtures (multi-node/slashing/inactivity_setup.ts, `process.env.CI ? 8 : 4`) sets its own eth slot and does not read DEFAULT_L1_BLOCK_TIME, so it is untouched.
Shrink REORG_TIMING_BASE from a 36s L2 slot / 8s blocks to a 24s L2 slot / 5s blocks (epoch stays 4
slots). This cuts every reorg / prune / HA suite by a third: proving/optimistic's reorg describes and
l1-reorgs/ (via FAST_REORG_TIMING, eth 4s) and recovery/proposal_failure_recovery,
recovery/equivocation_recovery, high-availability/ha_sync, high-availability/ha_checkpoint_handoff
(via MULTI_VALIDATOR_REORG_TIMING, eth 6s).
blockDurationMs drops 8000 -> 5000 (not left at 8000) specifically to preserve blocks-per-checkpoint.
Both reorg profiles run below the fast-profile boundary (eth 4s and 6s < 8s), so budgets clamp to
p2p 0.5s, prepare 0.5s, min-block 1s; init stays 1s.
Guard math (maxBlocksPerCheckpoint = floor((S - init - D - 2P - prepCp) / D) >= 1):
FAST_REORG_TIMING (eth 4, P=min(2,0.5)=0.5, prepCp=0.5, init=1):
before: S=36, D=8 -> floor((36 - 1 - 8 - 1 - 0.5)/8) = floor(25.5/8) = 3
after: S=24, D=5 -> floor((24 - 1 - 5 - 1 - 0.5)/5) = floor(16.5/5) = 3
MULTI_VALIDATOR_REORG_TIMING (eth 6, attestationPropagationTime 0.5 -> P=0.5, prepCp=0.5, init=1):
before: S=36, D=8 -> floor((36 - 1 - 8 - 1 - 0.5)/8) = 3
after: S=24, D=5 -> floor((24 - 1 - 5 - 1 - 0.5)/5) = 3
Both preserve exactly 3 blocks per checkpoint. Keeping blockDurationMs at 8000 would have yielded
floor(13.5/8) = 1 block/checkpoint, breaking the l1-reorgs suites' assertMultipleBlocksPerSlot(2)
(they send TX_COUNT=8 with maxTxsPerBlock=1 to force multi-block checkpoints).
Everything else scales off aztecSlotDuration automatically: the tests wait on checkpoint/epoch/proof
boundaries via test.constants.slotDuration and warp via the same. equivocation_recovery's manual
fit calc still holds at the new cadence (3 blocks x 5s = 15s + 0.5 init + 5s final block + 2.5s
finalization = 23s <= 24s); its comment is updated. Doc-only: the profiles' JSDoc, the l1-reorgs and
recovery/HA cadence comments, and optimistic's now-stale "12s CI / 24s" default-cadence note (left
over from the DEFAULT_L1_BLOCK_TIME=8 change) are refreshed to the new numbers.
Shrink MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING from aztecSlotDurationInL1Slots 3 (36s L2 slot at
eth 12) to 2 (24s), with blockDurationMs 6000 -> 4000. eth stays 12 (production budgets, not fast
profile). Cuts the block-production/simple, first_slot, and proof_boundary suites by a third.
These tests run at eth=12 (>= 8), so budgets are the production defaults (init 1s, prepare 1s,
min-block 2s) with p2p = per-test attestationPropagationTime.
Guard math (maxBlocksPerCheckpoint = floor((S - init - D - 2P - prepCp) / D) >= 1), S=24, D=4:
simple (P=default 2): floor((24 - 1 - 4 - 4 - 1)/4) = floor(14/4) = 3 (was 4 at 36/6)
proof_boundary (P=default 2): floor((24 - 1 - 4 - 4 - 1)/4) = 3 (was 4)
first_slot (P=0.5): floor((24 - 1 - 4 - 1 - 1)/4) = floor(17/4) = 4 (was 4; its
"4 blocks per checkpoint" comment stays accurate)
All >= 1. simple and proof_boundary assert no fixed block-count (simple checks no sequencer failures;
proof_boundary checks proof-vs-boundary timing that scales off test.constants.slotDuration), so the
drop from 4 to 3 blocks/checkpoint is harmless.
high_tps is PINNED to the old 36s/6s cadence at its call site (aztecSlotDurationInL1Slots: 3,
blockDurationMs: 6000 in its setupOpts, which override the shared profile). Its checkpoint-packing is
2 txs x 2.5s = 5s per block, which needs a 6s block sub-slot; a 4s block cannot hold it, and the
suite hard-asserts max-checkpoint-length == 4 and max-txs-per-block == 2. Pinning keeps high_tps
byte-for-byte at its tuned cadence rather than rewriting its timing model, so its header timing budget
stays correct. The other three suites still take the cut.
Doc-only: profile JSDoc + simple/proof_boundary cadence comments refreshed to 24s/4s.
4222567 to
01cdc99
Compare
PhilWindle
approved these changes
Jul 8, 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
PR 5 of the round-4 e2e speedup effort: shrink e2e slot times, the highest-leverage remaining lever
now that the warp lever is exhausted (PR 4) and the fees setup costs are pure inclusion latency at
CI cadence (PR 6). Every cadence-bound cost — setup txs, inclusion waits, epoch walks — scales
linearly with what these commits cut.
Config-only. Three commits, in ascending risk order so the CI-survey/fix phase can revert from
the top. A fourth candidate (WIDE_SLOT 72s → 48s) was investigated and dropped — see below.
All guard math is recomputed from
stdlib/src/timetable/budgets.ts+stdlib/src/timetable/proposer_timetable.ts:where
S= aztecSlotDuration,init= checkpointProposalInitTime (1s, never clamped),D=blockDurationMs/1000,
P= p2pPropagationTime,prepCp= checkpointProposalPrepareTime. Fast-profileclamping (
P -> 0.5,prepCp -> 0.5,minBlock -> 1) applies only whenethereumSlotDuration < 8(
FAST_PROFILE_ETHEREUM_SLOT_DURATION). At or above 8s the production budgets apply verbatim.Commit 1 (lowest risk) —
perf(e2e): cut default CI L1 block time to 8sDEFAULT_L1_BLOCK_TIME:process.env.CI ? 12 : 8→8. Local dev already ran at 8, so this onlyremoves a CI-vs-local cadence asymmetry. Default single-node L2 slot goes 24s → 16s.
Guard (default single-node: D = DEFAULT_BLOCK_DURATION_MS/1000 = 3, production budgets P=2,
prepCp=1, init=1):
Both >= 1. eth=8 is AT the fast-profile boundary, so budgets stay at production values (no clamping).
Blast radius is much smaller than the round-4 plan assumed. The plan expected this to cut the
fees / cross-chain / bot families, but the base has drifted: those suites now run on
PIPELINING_SETUP_OPTS/AUTOMINE_E2E_OPTS(both setethereumSlotDuration: 4explicitly) and nolonger read
DEFAULT_L1_BLOCK_TIME. The genuine default-cadence consumers (eth fromSingleNodeTestContext.getSlotDurations, no explicit eth) are four single-node proving/recoverysuites:
proving/default_node,proving/cross_chain_public_message,recovery/sync_after_reorg,partial-proofs/multi_root. The only otherprocess.env.CItiming coupling(
multi-node/slashing/inactivity_setup.ts,process.env.CI ? 8 : 4) sets its own eth slot and doesnot read
DEFAULT_L1_BLOCK_TIME, so it is untouched.Commit 2 (medium risk) —
perf(e2e): cut reorg cadence to 24s slotsREORG_TIMING_BASE: aztecSlotDuration 36 → 24, blockDurationMs 8000 → 5000 (epoch stays 4 slots).Shared by both reorg profiles, both below the fast-profile boundary:
FAST_REORG_TIMING(eth 4): proving/optimistic reorg describes, l1-reorgs/MULTI_VALIDATOR_REORG_TIMING(eth 6, attestationPropagationTime 0.5): recovery/proposal_failure_recovery,recovery/equivocation_recovery, high-availability/ha_sync, high-availability/ha_checkpoint_handoff
Both run fast-profile: P = 0.5, prepCp = 0.5, init = 1.
Guard, FAST_REORG_TIMING and MULTI_VALIDATOR_REORG_TIMING (identical budgets):
blockDurationMs drops 8000 → 5000 specifically to preserve 3 blocks/checkpoint. Leaving it at 8000
would give floor(13.5/8) = 1, which would break the l1-reorgs suites'
assertMultipleBlocksPerSlot(2)(they push TX_COUNT=8 with maxTxsPerBlock=1 to force multi-block checkpoints). All other timing scales
off
test.constants.slotDuration(waits, warps). equivocation_recovery's manual fit calc still holds(3 x 5s = 15 + 0.5 init + 5 final block + 2.5 finalization = 23s <= 24s); its comment is updated.
Commit 3 (higher risk) —
perf(e2e): cut multi-validator block-production cadence to 24s slotsMULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING: aztecSlotDurationInL1Slots 3 → 2 (36s → 24s at eth 12),blockDurationMs 6000 → 4000. eth stays 12 (production budgets). Consumers: block-production/simple,
first_slot, proof_boundary.
Guard (eth 12, production budgets prepCp=1, init=1; P = per-test attestationPropagationTime), S=24, D=4:
All >= 1. simple asserts no block-count (only no-sequencer-failures); proof_boundary asserts
proof-vs-boundary timing that scales off
test.constants; first_slot keeps 4 blocks/checkpoint(its comment stays accurate). So the drop 4 → 3 is harmless for those three.
high_tpsis pinned to the old 36s/6s cadence at its call site (aztecSlotDurationInL1Slots: 3,blockDurationMs: 6000in its setupOpts, overriding the shared profile). Its checkpoint packing is2 txs x 2.5s = 5s per block, which needs a 6s block sub-slot; a 4s block cannot hold it, and the suite
hard-asserts
max-checkpoint-length == 4andmax-txs-per-block == 2. Pinning keeps high_tps at itstuned cadence rather than rewriting its whole timing model. The other three suites still take the cut.
Dropped candidate — WIDE_SLOT 72s → 48s
Investigated per the plan's stretch item and dropped.
blob_promotionhard-asserts a checkpointwith >=
PIPELINE_EXPECTED_BLOCKS_PER_CHECKPOINT = 8blocks. At the profile's D = 5.5s:Forcing 8 blocks would require shrinking D to ~4.5s, which lands the guard at exactly 8 with zero
margin — under the deliberate
mockGossipSubNetworkLatency: 500msthe suite runs with, and on top ofthe profile's documented A-914 constraint (pipelined multiple-blocks-per-slot starves non-proposer
nodes with
CheckpointNumberNotSequentialErrorwhen the L2 slot is tightened, worsened by a shorterslot). This is the "provably can't fit" case, and it cannot be verified locally (heavy multi-node
pipelining), so it is not shipped. proposed_chain / cross_chain_messages (only need 2 blocks/cp) would
survive the guard but share the A-914 risk; not worth the exposure for one profile.
Local verification
single-node/proving/default_node.test.ts -t 'returns initial block data'locally (eth=8 = the new CI cadence, which local already uses). The node came up at
ethereumSlotDuration: 8, aztecSlotDuration: 16, blockDurationMs: 3000and loggedSequencer timetable initialized with 2 blocks per slot {"maxNumberOfBlocks":2}— exactly the guardvalue above. No "Invalid timing configuration", no missed-slot warnings, test passed. Budgets stayed
at production values (
attestationPropagationTime: 2, minBlockDuration: 2), confirming eth=8 does nottrip fast-profile clamping.
too heavy for the dev machine (multi-node + real-time waits + proving). The guard math above is the
verification; CI validates end-to-end. The formula was confirmed exact against commit 1's live run.
Revert protocol for the fix phase
Commits are ordered by ascending risk; revert individual commits from the top on flake rather than
fighting them:
proof_boundary is the most real-time-sensitive (it warps to N-3 then runs the boundary in real time
at the tighter 24s cadence); watch it first.
recovery/proposal_failure_recovery, recovery/equivocation_recovery, high-availability/*) → revert
commit 2. Watch the l1-reorgs
assertMultipleBlocksPerSlot(2)assertions and equivocation_recovery'sslashing-round timing.
probability; the cadence is what local already runs).
Notes for PR 7 (bot suite cadence)
The bot suite (
single-node/bot/bot.test.ts) usessetup(0, { ...PIPELINING_SETUP_OPTS, ... }), whichsets
ethereumSlotDuration: 4explicitly. It does not readDEFAULT_L1_BLOCK_TIME, so commit 1 doesnot affect the bot suite — its cadence lever remains its own PIPELINING preset, unchanged here.
Measured impact
The cadence cuts land a run-wide −6.4%: −1,600s summed across shards over the 135 suites present in
both runs (−2,046s of improvements against +446s of regressions). The wall-clock benefit is smaller,
since shards run in parallel across workers.
Controls confirm the deltas are real cadence effects, not run-type artifacts.
high_tps(deliberatelypinned at the old cadence) is unchanged (+0.1s), and the WIDE_SLOT / explicit-preset proving suites are
flat — pipeline_prune −1.2s, blob_promotion −0.1s, long_proving_time −1.1s, prune_when_cannot_build
−0.1s. Only the default-cadence and reorg/block-production timings moved.
Largest per-suite wall cuts (default-cadence proving/recovery from commit 1; reorg from commit 2):
cross_chain_public_message −48.2s; equivocation_recovery −46.9s; late_prover_tx_collection −43.3s
Regressions, all on high-variance multi-node suites that are not cadence-cut here (run-to-run noise,
not attributable to this change): multi_root +74.1s, inactivity_slash +64.4s,
attested_invalid_proposal.parallel +32.1s, proposed_chain.parallel +26.1s, bot +22.3s.
The metric is whole-suite wall time (sum of test durations + hooks); a cadence change carries no new
span to attribute against, so these are run-vs-run wall deltas — the flat pinned/preset controls above
are what make the cadence attribution defensible.
Baseline CI run 1783417125211175 (merge-base 13a53f1, full). PR CI run 1783426164237985
(x-fast).
Fixes A-1184