@aztec/sequencer-client 4.1.2-rc.1 → 4.2.0-aztecnr-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dest/client/sequencer-client.d.ts +3 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +19 -72
  4. package/dest/config.d.ts +2 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +6 -0
  7. package/dest/global_variable_builder/global_builder.d.ts +13 -7
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +22 -21
  10. package/dest/global_variable_builder/index.d.ts +2 -2
  11. package/dest/global_variable_builder/index.d.ts.map +1 -1
  12. package/dest/publisher/sequencer-publisher.d.ts +5 -3
  13. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  14. package/dest/publisher/sequencer-publisher.js +36 -7
  15. package/dest/sequencer/checkpoint_proposal_job.d.ts +3 -1
  16. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  17. package/dest/sequencer/checkpoint_proposal_job.js +33 -25
  18. package/dest/sequencer/sequencer.d.ts +3 -2
  19. package/dest/sequencer/sequencer.d.ts.map +1 -1
  20. package/dest/sequencer/sequencer.js +3 -3
  21. package/dest/test/mock_checkpoint_builder.d.ts +4 -8
  22. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  23. package/package.json +28 -28
  24. package/src/client/sequencer-client.ts +29 -94
  25. package/src/config.ts +7 -0
  26. package/src/global_variable_builder/global_builder.ts +22 -23
  27. package/src/global_variable_builder/index.ts +1 -1
  28. package/src/publisher/sequencer-publisher.ts +39 -7
  29. package/src/sequencer/checkpoint_proposal_job.ts +56 -45
  30. package/src/sequencer/sequencer.ts +3 -3
  31. package/src/test/mock_checkpoint_builder.ts +3 -3
@@ -1,14 +1,13 @@
1
- import { createEthereumChain } from '@aztec/ethereum/chain';
2
- import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
1
  import { RollupContract } from '@aztec/ethereum/contracts';
4
- import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
5
3
  import type { ViemPublicClient } from '@aztec/ethereum/types';
6
4
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
7
5
  import { Fr } from '@aztec/foundation/curves/bn254';
8
6
  import type { EthAddress } from '@aztec/foundation/eth-address';
9
7
  import { createLogger } from '@aztec/foundation/log';
8
+ import type { DateProvider } from '@aztec/foundation/timer';
10
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
- import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
10
+ import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
12
11
  import { GasFees } from '@aztec/stdlib/gas';
13
12
  import type {
14
13
  CheckpointGlobalVariables,
@@ -16,7 +15,12 @@ import type {
16
15
  } from '@aztec/stdlib/tx';
17
16
  import { GlobalVariables } from '@aztec/stdlib/tx';
18
17
 
19
- import { createPublicClient, fallback, http } from 'viem';
18
+ /** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
19
+ export type GlobalVariableBuilderConfig = {
20
+ l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
21
+ ethereumSlotDuration: number;
22
+ rollupVersion: bigint;
23
+ } & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
20
24
 
21
25
  /**
22
26
  * Simple global variables builder.
@@ -27,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
27
31
  private currentL1BlockNumber: bigint | undefined = undefined;
28
32
 
29
33
  private readonly rollupContract: RollupContract;
30
- private readonly publicClient: ViemPublicClient;
31
34
  private readonly ethereumSlotDuration: number;
32
35
  private readonly aztecSlotDuration: number;
33
36
  private readonly l1GenesisTime: bigint;
@@ -36,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
36
39
  private version: Fr;
37
40
 
38
41
  constructor(
39
- config: L1ReaderConfig &
40
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> &
41
- Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'> & { rollupVersion: bigint },
42
+ private readonly dateProvider: DateProvider,
43
+ private readonly publicClient: ViemPublicClient,
44
+ config: GlobalVariableBuilderConfig,
42
45
  ) {
43
- const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
44
-
45
- const chain = createEthereumChain(l1RpcUrls, chainId);
46
-
47
46
  this.version = new Fr(config.rollupVersion);
48
- this.chainId = new Fr(chainId);
47
+ this.chainId = new Fr(this.publicClient.chain!.id);
49
48
 
50
49
  this.ethereumSlotDuration = config.ethereumSlotDuration;
51
50
  this.aztecSlotDuration = config.slotDuration;
52
51
  this.l1GenesisTime = config.l1GenesisTime;
53
52
 
54
- this.publicClient = createPublicClient({
55
- chain: chain.chainInfo,
56
- transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
57
- pollingInterval: config.viemPollingIntervalMS,
58
- });
59
-
60
- this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
53
+ this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
61
54
  }
62
55
 
63
56
  /**
@@ -73,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
73
66
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
74
67
  SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
75
68
  );
76
- const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
69
+ const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
70
+ l1GenesisTime: this.l1GenesisTime,
71
+ ethereumSlotDuration: this.ethereumSlotDuration,
72
+ });
77
73
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
78
74
 
79
75
  return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
@@ -108,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
108
104
  const slot: SlotNumber =
109
105
  maybeSlot ??
110
106
  (await this.rollupContract.getSlotAt(
111
- BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)),
107
+ getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
108
+ l1GenesisTime: this.l1GenesisTime,
109
+ ethereumSlotDuration: this.ethereumSlotDuration,
110
+ }),
112
111
  ));
113
112
 
114
113
  const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
@@ -1 +1 @@
1
- export { GlobalVariableBuilder } from './global_builder.js';
1
+ export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
@@ -28,6 +28,7 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
28
28
  import { sumBigint } from '@aztec/foundation/bigint';
29
29
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
30
30
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
31
+ import { trimmedBytesLength } from '@aztec/foundation/buffer';
31
32
  import { pick } from '@aztec/foundation/collection';
32
33
  import type { Fr } from '@aztec/foundation/curves/bn254';
33
34
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -40,6 +41,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
40
41
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
41
42
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
42
43
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
44
+ import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
43
45
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
44
46
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
45
47
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
@@ -120,6 +122,7 @@ export class SequencerPublisher {
120
122
 
121
123
  protected log: Logger;
122
124
  protected ethereumSlotDuration: bigint;
125
+ private dateProvider: DateProvider;
123
126
 
124
127
  private blobClient: BlobClientInterface;
125
128
 
@@ -168,6 +171,7 @@ export class SequencerPublisher {
168
171
  ) {
169
172
  this.log = deps.log ?? createLogger('sequencer:publisher');
170
173
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
174
+ this.dateProvider = deps.dateProvider;
171
175
  this.epochCache = deps.epochCache;
172
176
  this.lastActions = deps.lastActions;
173
177
 
@@ -356,8 +360,8 @@ export class SequencerPublisher {
356
360
  // @note - we can only have one blob config per bundle
357
361
  // find requests with gas and blob configs
358
362
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
359
- const gasConfigs = requestsToProcess.filter(request => request.gasConfig).map(request => request.gasConfig);
360
- const blobConfigs = requestsToProcess.filter(request => request.blobConfig).map(request => request.blobConfig);
363
+ const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
364
+ const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
361
365
 
362
366
  if (blobConfigs.length > 1) {
363
367
  throw new Error('Multiple blob configs found');
@@ -425,7 +429,16 @@ export class SequencerPublisher {
425
429
  this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
426
430
  return { failedActions: requests.map(r => r.action) };
427
431
  } else {
428
- this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
432
+ this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
433
+ result,
434
+ requests: requests.map(r => ({
435
+ ...r,
436
+ // Avoid logging large blob data
437
+ blobConfig: r.blobConfig
438
+ ? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
439
+ : undefined,
440
+ })),
441
+ });
429
442
  const successfulActions: Action[] = [];
430
443
  const failedActions: Action[] = [];
431
444
  for (const request of requests) {
@@ -440,11 +453,11 @@ export class SequencerPublisher {
440
453
  }
441
454
 
442
455
  /**
443
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
456
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
444
457
  * @param tipArchive - The archive to check
445
458
  * @returns The slot and block number if it is possible to propose, undefined otherwise
446
459
  */
447
- public canProposeAtNextEthBlock(
460
+ public async canProposeAt(
448
461
  tipArchive: Fr,
449
462
  msgSender: EthAddress,
450
463
  opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
@@ -452,8 +465,10 @@ export class SequencerPublisher {
452
465
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
453
466
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
454
467
 
468
+ const nextL1SlotTs = await this.getNextL1SlotTimestampWithL1Floor();
469
+
455
470
  return this.rollupContract
456
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
471
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
457
472
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
458
473
  })
459
474
  .catch(err => {
@@ -490,7 +505,7 @@ export class SequencerPublisher {
490
505
  flags,
491
506
  ] as const;
492
507
 
493
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
508
+ const ts = await this.getNextL1SlotTimestampWithL1Floor();
494
509
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
495
510
  opts?.forcePendingCheckpointNumber,
496
511
  );
@@ -1345,4 +1360,21 @@ export class SequencerPublisher {
1345
1360
  },
1346
1361
  });
1347
1362
  }
1363
+
1364
+ /**
1365
+ * Returns the timestamp to use when simulating L1 proposal calls.
1366
+ * Uses the wall-clock-based next L1 slot boundary, but floors it with the latest L1 block timestamp
1367
+ * plus one slot duration. This prevents the sequencer from targeting a future L2 slot when the L1
1368
+ * chain hasn't caught up to the wall clock yet (e.g., the dateProvider is one L1 slot ahead of the
1369
+ * latest mined block), which would cause the propose tx to land in an L1 block with block.timestamp
1370
+ * still in the previous L2 slot.
1371
+ * TODO(palla): Properly fix by keeping dateProvider synced with anvil's chain time on every block.
1372
+ */
1373
+ private async getNextL1SlotTimestampWithL1Floor(): Promise<bigint> {
1374
+ const l1Constants = this.epochCache.getL1Constants();
1375
+ const fromWallClock = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1376
+ const latestBlock = await this.l1TxUtils.client.getBlock();
1377
+ const fromL1Block = latestBlock.timestamp + BigInt(l1Constants.ethereumSlotDuration);
1378
+ return fromWallClock > fromL1Block ? fromWallClock : fromL1Block;
1379
+ }
1348
1380
  }
@@ -34,13 +34,18 @@ import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
34
34
  import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
35
35
  import { Gas } from '@aztec/stdlib/gas';
36
36
  import {
37
+ type BlockBuilderOptions,
37
38
  InsufficientValidTxsError,
38
- type PublicProcessorLimits,
39
39
  type ResolvedSequencerConfig,
40
40
  type WorldStateSynchronizer,
41
41
  } from '@aztec/stdlib/interfaces/server';
42
42
  import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
43
- import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
43
+ import type {
44
+ BlockProposal,
45
+ BlockProposalOptions,
46
+ CheckpointProposal,
47
+ CheckpointProposalOptions,
48
+ } from '@aztec/stdlib/p2p';
44
49
  import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
45
50
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
46
51
  import { type FailedTx, Tx } from '@aztec/stdlib/tx';
@@ -265,7 +270,8 @@ export class CheckpointProposalJob implements Traceable {
265
270
  this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
266
271
  const checkpoint = await checkpointBuilder.completeCheckpoint();
267
272
 
268
- // Final validation round for the checkpoint before we propose it, just for safety
273
+ // Final validation: per-block limits are only checked if the operator set them explicitly.
274
+ // Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
269
275
  try {
270
276
  validateCheckpoint(checkpoint, {
271
277
  rollupManaLimit: this.l1Constants.rollupManaLimit,
@@ -402,6 +408,7 @@ export class CheckpointProposalJob implements Traceable {
402
408
  const blocksInCheckpoint: L2Block[] = [];
403
409
  const txHashesAlreadyIncluded = new Set<string>();
404
410
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
411
+ const slot = this.slot;
405
412
 
406
413
  // Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
407
414
  let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
@@ -415,11 +422,7 @@ export class CheckpointProposalJob implements Traceable {
415
422
  const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
416
423
 
417
424
  if (!timingInfo.canStart) {
418
- this.log.debug(`Not enough time left in slot to start another block`, {
419
- slot: this.slot,
420
- blocksBuilt,
421
- secondsIntoSlot,
422
- });
425
+ this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
423
426
  break;
424
427
  }
425
428
 
@@ -451,53 +454,37 @@ export class CheckpointProposalJob implements Traceable {
451
454
  } else if ('error' in buildResult) {
452
455
  // If there was an error building the block, just exit the loop and give up the rest of the slot
453
456
  if (!(buildResult.error instanceof SequencerInterruptedError)) {
454
- this.log.warn(`Halting block building for slot ${this.slot}`, {
455
- slot: this.slot,
456
- blocksBuilt,
457
- error: buildResult.error,
458
- });
457
+ this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
459
458
  }
460
459
  break;
461
460
  }
462
461
 
463
462
  const { block, usedTxs } = buildResult;
464
463
  blocksInCheckpoint.push(block);
465
-
466
- // Sync the proposed block to the archiver to make it available
467
- // Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
468
- // Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
469
- // Fire and forget - don't block the critical path, but log errors
470
- this.syncProposedBlockToArchiver(block).catch(err => {
471
- this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
472
- });
473
-
474
464
  usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
475
465
 
476
- // If this is the last block, exit the loop now so we start collecting attestations
466
+ // If this is the last block, send the proposed block to the archiver,
467
+ // and exit the loop now so we can build the checkpoint and start collecting attestations.
477
468
  if (timingInfo.isLastBlock) {
478
- this.log.verbose(`Completed final block ${blockNumber} for slot ${this.slot}`, {
479
- slot: this.slot,
480
- blockNumber,
481
- blocksBuilt,
482
- });
469
+ await this.syncProposedBlockToArchiver(block);
470
+ this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
483
471
  blockPendingBroadcast = { block, txs: usedTxs };
484
472
  break;
485
473
  }
486
474
 
487
- // For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
488
- // If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
489
- if (!this.config.fishermanMode) {
490
- const proposal = await this.validatorClient.createBlockProposal(
491
- block.header,
492
- block.indexWithinCheckpoint,
493
- inHash,
494
- block.archive.root,
495
- usedTxs,
496
- this.proposer,
497
- blockProposalOptions,
498
- );
499
- await this.p2pClient.broadcastProposal(proposal);
500
- }
475
+ // Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
476
+ // in which case we'll broadcast it along with the checkpoint at the end of the loop.
477
+ // Note that we only send the block to the archiver if we manage to create the proposal, so if there's
478
+ // a HA error we don't pollute our archiver with a block that won't make it to the chain.
479
+ const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
480
+
481
+ // Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
482
+ // We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
483
+ // If this throws, we abort the entire checkpoint.
484
+ await this.syncProposedBlockToArchiver(block);
485
+
486
+ // Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
487
+ proposal && (await this.p2pClient.broadcastProposal(proposal));
501
488
 
502
489
  // Wait until the next block's start time
503
490
  await this.waitUntilNextSubslot(timingInfo.deadline);
@@ -511,6 +498,28 @@ export class CheckpointProposalJob implements Traceable {
511
498
  return { blocksInCheckpoint, blockPendingBroadcast };
512
499
  }
513
500
 
501
+ /** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
502
+ private createBlockProposal(
503
+ block: L2Block,
504
+ inHash: Fr,
505
+ usedTxs: Tx[],
506
+ blockProposalOptions: BlockProposalOptions,
507
+ ): Promise<BlockProposal | undefined> {
508
+ if (this.config.fishermanMode) {
509
+ this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
510
+ return Promise.resolve(undefined);
511
+ }
512
+ return this.validatorClient.createBlockProposal(
513
+ block.header,
514
+ block.indexWithinCheckpoint,
515
+ inHash,
516
+ block.archive.root,
517
+ usedTxs,
518
+ this.proposer,
519
+ blockProposalOptions,
520
+ );
521
+ }
522
+
514
523
  /** Sleeps until it is time to produce the next block in the slot */
515
524
  @trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
516
525
  private async waitUntilNextSubslot(nextSubslotStart: number) {
@@ -566,11 +575,11 @@ export class CheckpointProposalJob implements Traceable {
566
575
  );
567
576
  this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
568
577
 
569
- // Per-block limits derived at startup by computeBlockLimits(), further capped
578
+ // Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
570
579
  // by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
571
580
  // minValidTxs is passed into the builder so it can reject the block *before* updating state.
572
581
  const minValidTxs = forceCreate ? 0 : (this.config.minValidTxsPerBlock ?? minTxs);
573
- const blockBuilderOptions: PublicProcessorLimits & { minValidTxs?: number } = {
582
+ const blockBuilderOptions: BlockBuilderOptions = {
574
583
  maxTransactions: this.config.maxTxsPerBlock,
575
584
  maxBlockGas:
576
585
  this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
@@ -579,6 +588,8 @@ export class CheckpointProposalJob implements Traceable {
579
588
  deadline: buildDeadline,
580
589
  isBuildingProposal: true,
581
590
  minValidTxs,
591
+ maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
592
+ perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
582
593
  };
583
594
 
584
595
  // Actually build the block by executing txs. The builder throws InsufficientValidTxsError
@@ -649,7 +660,7 @@ export class CheckpointProposalJob implements Traceable {
649
660
  pendingTxs: AsyncIterable<Tx>,
650
661
  blockNumber: BlockNumber,
651
662
  blockTimestamp: bigint,
652
- blockBuilderOptions: PublicProcessorLimits & { minValidTxs?: number },
663
+ blockBuilderOptions: BlockBuilderOptions,
653
664
  ) {
654
665
  try {
655
666
  const workTimer = new Timer();
@@ -327,7 +327,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
327
327
 
328
328
  // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
329
329
  // if all the previous checks are good, but we do it just in case.
330
- const canProposeCheck = await publisher.canProposeAtNextEthBlock(
330
+ const canProposeCheck = await publisher.canProposeAt(
331
331
  syncedTo.archive,
332
332
  proposer ?? EthAddress.ZERO,
333
333
  invalidateCheckpoint,
@@ -475,8 +475,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
475
475
  */
476
476
  protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
477
477
  // Check that the archiver has fully synced the L2 slot before the one we want to propose in.
478
- // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
479
- // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
478
+ // The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
479
+ // See getSyncedL2SlotNumber for how missed L1 blocks are handled.
480
480
  const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
481
481
  const { slot } = args;
482
482
  if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
@@ -4,11 +4,11 @@ import { unfreeze } from '@aztec/foundation/types';
4
4
  import { L2Block } from '@aztec/stdlib/block';
5
5
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
6
6
  import type {
7
+ BlockBuilderOptions,
7
8
  FullNodeBlockBuilderConfig,
8
9
  ICheckpointBlockBuilder,
9
10
  ICheckpointsBuilder,
10
11
  MerkleTreeWriteOperations,
11
- PublicProcessorLimits,
12
12
  } from '@aztec/stdlib/interfaces/server';
13
13
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
14
  import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
@@ -32,7 +32,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
32
32
  public buildBlockCalls: Array<{
33
33
  blockNumber: BlockNumber;
34
34
  timestamp: bigint;
35
- opts: PublicProcessorLimits & { minValidTxs?: number };
35
+ opts: BlockBuilderOptions;
36
36
  }> = [];
37
37
  /** Track all consumed transaction hashes across buildBlock calls */
38
38
  public consumedTxHashes: Set<string> = new Set();
@@ -74,7 +74,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
74
74
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
75
75
  blockNumber: BlockNumber,
76
76
  timestamp: bigint,
77
- opts: PublicProcessorLimits & { minValidTxs?: number },
77
+ opts: BlockBuilderOptions,
78
78
  ): Promise<BuildBlockInCheckpointResult> {
79
79
  this.buildBlockCalls.push({ blockNumber, timestamp, opts });
80
80