diff --git a/aztec-up/test/amm_flow.sh b/aztec-up/test/amm_flow.sh index d58b05276f70..75fda916c58f 100755 --- a/aztec-up/test/amm_flow.sh +++ b/aztec-up/test/amm_flow.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +export SEQ_ENABLE_PROPOSER_PIPELINING=true + # Start local network and wait for port to open. aztec start --local-network & local_network_pid=$! diff --git a/aztec-up/test/basic_install.sh b/aztec-up/test/basic_install.sh index 497aeda2b873..ce4f7397f506 100755 --- a/aztec-up/test/basic_install.sh +++ b/aztec-up/test/basic_install.sh @@ -10,6 +10,7 @@ echo export LOG_LEVEL=silent export PXE_PROVER=none +export SEQ_ENABLE_PROPOSER_PIPELINING=true # Start local network and wait for port to open. aztec start --local-network & diff --git a/aztec-up/test/bridge_and_claim.sh b/aztec-up/test/bridge_and_claim.sh index b073daadb3c8..f26f5d664338 100755 --- a/aztec-up/test/bridge_and_claim.sh +++ b/aztec-up/test/bridge_and_claim.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +export SEQ_ENABLE_PROPOSER_PIPELINING=true + # Start local network and wait for port to open. aztec start --local-network & local_network_pid=$! diff --git a/docs/examples/bootstrap.sh b/docs/examples/bootstrap.sh index c3d91f209838..9473b0a4507d 100755 --- a/docs/examples/bootstrap.sh +++ b/docs/examples/bootstrap.sh @@ -210,7 +210,10 @@ function execute-examples { } function test_cmds { - echo "$hash:ONLY_TERM_PARENT=1 docs/examples/bootstrap.sh execute" + # Bumped from the default 600s by ~50% (now 15m) to absorb cumulative-runtime growth + # under merge-queue load — example_swap's `wait-for-proven` poll was tipping SIGTERM + # near the old limit. See PR #23253 dequeue log http://ci.aztec-labs.com/b08ac48286302949. + echo "$hash:ONLY_TERM_PARENT=1:TIMEOUT=15m docs/examples/bootstrap.sh execute" } function test { diff --git a/docs/examples/ts/docker-compose.yml b/docs/examples/ts/docker-compose.yml index d881961f4f74..247b321c6912 100644 --- a/docs/examples/ts/docker-compose.yml +++ b/docs/examples/ts/docker-compose.yml @@ -28,6 +28,7 @@ services: WS_BLOCK_CHECK_INTERVAL_MS: 500 ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500 P2P_MIN_TX_POOL_AGE_MS: 0 + SEQ_ENABLE_PROPOSER_PIPELINING: 'true' HARDWARE_CONCURRENCY: ${HARDWARE_CONCURRENCY:-} docs-examples: diff --git a/playground/docker-compose.yml b/playground/docker-compose.yml index d48663150b0d..7d86f4bd03ba 100644 --- a/playground/docker-compose.yml +++ b/playground/docker-compose.yml @@ -27,6 +27,7 @@ services: WS_BLOCK_CHECK_INTERVAL_MS: 50 ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500 P2P_MIN_TX_POOL_AGE_MS: 0 + SEQ_ENABLE_PROPOSER_PIPELINING: 'true' healthcheck: test: ['CMD', 'curl', '-fSs', 'http://127.0.0.1:8080/status'] interval: 3s diff --git a/yarn-project/aztec/src/local-network/local-network.ts b/yarn-project/aztec/src/local-network/local-network.ts index 6293df2653c5..a771e611b09c 100644 --- a/yarn-project/aztec/src/local-network/local-network.ts +++ b/yarn-project/aztec/src/local-network/local-network.ts @@ -206,6 +206,21 @@ export async function createLocalNetwork(config: Partial = { SequencerState.SYNCHRONIZING, ]); watcher?.setIsSequencerBuilding(() => !idleStates.has(sequencer.getState())); + // Under proposer pipelining the L1 publish for slot N happens during wall-clock slot N, + // but the proposer for slot N has already built the checkpoint during slot N-1 and is + // waiting for L1 to advance. We need to fast-forward L1 to wake that wait — and the wait + // we have to break first is `waitForValidParentCheckpointOnL1`, which blocks the + // checkpoint_proposal_job's background submission task until the archiver has synced past + // the build slot. That wait happens *before* `PUBLISHING_CHECKPOINT` is set, so a hook on + // that state transition would be circular (L1 has to advance before the state we'd use to + // advance L1 fires). The earliest pre-wait signal is `block-proposed`, which the sequencer + // emits once each block is built. In sandbox single-block-per-slot mode this is + // effectively "checkpoint built", and the watcher warp is harmless if a subsequent + // assembly/validation/parent-wait step aborts: L1 just sits one slot ahead, which the + // cascade absorbs. + if (watcher) { + sequencer.on('block-proposed', ({ slot }) => watcher!.setProposedTargetSlot(Number(slot))); + } } let epochTestSettler: EpochTestSettler | undefined; diff --git a/yarn-project/aztec/src/testing/anvil_test_watcher.ts b/yarn-project/aztec/src/testing/anvil_test_watcher.ts index e2f9c8ed2cbb..81505d5296fa 100644 --- a/yarn-project/aztec/src/testing/anvil_test_watcher.ts +++ b/yarn-project/aztec/src/testing/anvil_test_watcher.ts @@ -44,6 +44,12 @@ export class AnvilTestWatcher { // Tracks when we first observed the current unfilled slot with pending txs (real wall time). private unfilledSlotFirstSeen?: { slot: number; realTime: number }; + // Latest target slot for which the proposer has built a block destined for L1 but which has + // not yet been committed. Set by the proposer-pipelining hook from `block-proposed` events so + // the watcher can advance L1 (and the injected date provider) to the target slot ahead of the + // publisher's `sendRequestsAt` sleep, instead of waiting a full wall-clock slot. + private proposedTargetSlot?: number; + constructor( private cheatcodes: EthCheatCodes, rollupAddress: EthAddress, @@ -86,6 +92,18 @@ export class AnvilTestWatcher { this.isSequencerBuilding = fn; } + /** + * Records the target slot for which the proposer has built a block destined for L1. Used by + * the local-network watcher to fast-forward L1 (and the injected date provider) ahead of the + * pipelined publisher's `sendRequestsAt` sleep so it ends promptly instead of waiting a full + * wall-clock slot. Only ratchets up — late warps for stale slots are no-ops. + */ + setProposedTargetSlot(slot: number) { + if (this.proposedTargetSlot === undefined || slot > this.proposedTargetSlot) { + this.proposedTargetSlot = slot; + } + } + async start() { if (this.filledRunningPromise) { throw new Error('Watcher already watching for filled slot'); @@ -177,6 +195,20 @@ export class AnvilTestWatcher { return; } + // Pipelined-publish shortcut: if the proposer has built a block destined for a slot + // beyond the current L1 slot, fast-forward L1 to that slot's timestamp so the publisher's + // `sendRequestsAt(targetSlot)` sleep ends and the multicall mines inside the target slot. + // Without this, the publisher waits up to a full real-time slot for wall clock to catch up. + if (this.proposedTargetSlot !== undefined && this.proposedTargetSlot > currentSlot) { + const targetSlotTimestamp = Number( + await this.rollup.read.getTimestampForSlot([BigInt(this.proposedTargetSlot)]), + ); + if (await this.warpToTimestamp(targetSlotTimestamp)) { + this.logger.info(`Warped L1 to target slot ${this.proposedTargetSlot} for pipelined publish`); + } + return; + } + // If there are pending txs and the sequencer missed them, warp quickly (after a 2s real-time debounce) so the // sequencer can retry in the next slot. Without this, we'd have to wait a full real-time slot duration (~36s) for // the dateProvider to catch up to the next slot timestamp. We skip the warp if the sequencer is actively building diff --git a/yarn-project/end-to-end/scripts/docker-compose.yml b/yarn-project/end-to-end/scripts/docker-compose.yml index 528efb33a286..e394e2610220 100644 --- a/yarn-project/end-to-end/scripts/docker-compose.yml +++ b/yarn-project/end-to-end/scripts/docker-compose.yml @@ -28,6 +28,7 @@ services: WS_BLOCK_CHECK_INTERVAL_MS: 500 ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500 P2P_MIN_TX_POOL_AGE_MS: 0 + SEQ_ENABLE_PROPOSER_PIPELINING: 'true' HARDWARE_CONCURRENCY: ${HARDWARE_CONCURRENCY:-} end-to-end: