@aztec/sequencer-client 0.0.1-commit.88c5703d4 → 0.0.1-commit.88e6f9396

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.
@@ -19,15 +19,10 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
19
19
  import { L1Metrics, type TelemetryClient } from '@aztec/telemetry-client';
20
20
  import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
21
21
 
22
- import {
23
- DefaultSequencerConfig,
24
- type SequencerClientConfig,
25
- getPublisherConfigFromSequencerConfig,
26
- } from '../config.js';
22
+ import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js';
27
23
  import { GlobalVariableBuilder } from '../global_variable_builder/index.js';
28
24
  import { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
29
25
  import { Sequencer, type SequencerConfig } from '../sequencer/index.js';
30
- import { SequencerTimetable } from '../sequencer/timetable.js';
31
26
 
32
27
  /**
33
28
  * Encapsulates the full sequencer and publisher.
@@ -118,6 +113,7 @@ export class SequencerClient {
118
113
  l1ChainId: chainId,
119
114
  viemPollingIntervalMS: config.viemPollingIntervalMS,
120
115
  ethereumSlotDuration: config.ethereumSlotDuration,
116
+ enableProposerPipelining: config.enableProposerPipelining,
121
117
  },
122
118
  { dateProvider: deps.dateProvider },
123
119
  ));
@@ -160,12 +156,7 @@ export class SequencerClient {
160
156
  const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
161
157
  const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
162
158
 
163
- const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint } = computeBlockLimits(
164
- config,
165
- rollupManaLimit,
166
- l1PublishingTime,
167
- log,
168
- );
159
+ const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = capPerBlockLimits(config, rollupManaLimit, log);
169
160
 
170
161
  const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration, rollupManaLimit };
171
162
 
@@ -183,7 +174,7 @@ export class SequencerClient {
183
174
  deps.dateProvider,
184
175
  epochCache,
185
176
  rollupContract,
186
- { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint },
177
+ { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
187
178
  telemetryClient,
188
179
  log,
189
180
  );
@@ -248,88 +239,39 @@ export class SequencerClient {
248
239
  }
249
240
 
250
241
  /**
251
- * Computes per-block L2 gas, DA gas, and TX count budgets based on the L1 rollup limits and the timetable.
252
- * If the user explicitly set a limit, it is capped at the corresponding checkpoint limit.
253
- * Otherwise, derives it as (checkpointLimit / maxBlocks) * multiplier, capped at the checkpoint limit.
242
+ * Caps operator-provided per-block limits at checkpoint-level limits.
243
+ * Returns undefined for any limit the operator didn't set the checkpoint builder handles redistribution.
254
244
  */
255
- export function computeBlockLimits(
245
+ function capPerBlockLimits(
256
246
  config: SequencerClientConfig,
257
247
  rollupManaLimit: number,
258
- l1PublishingTime: number,
259
248
  log: ReturnType<typeof createLogger>,
260
- ): { maxL2BlockGas: number; maxDABlockGas: number; maxTxsPerBlock: number; maxBlocksPerCheckpoint: number } {
261
- const maxNumberOfBlocks = new SequencerTimetable({
262
- ethereumSlotDuration: config.ethereumSlotDuration,
263
- aztecSlotDuration: config.aztecSlotDuration,
264
- l1PublishingTime,
265
- p2pPropagationTime: config.attestationPropagationTime,
266
- blockDurationMs: config.blockDurationMs,
267
- enforce: config.enforceTimeTable ?? DefaultSequencerConfig.enforceTimeTable,
268
- }).maxNumberOfBlocks;
269
-
270
- const multiplier = config.perBlockAllocationMultiplier ?? DefaultSequencerConfig.perBlockAllocationMultiplier;
271
-
272
- // Compute maxL2BlockGas
273
- let maxL2BlockGas: number;
274
- if (config.maxL2BlockGas !== undefined) {
275
- if (config.maxL2BlockGas > rollupManaLimit) {
276
- log.warn(
277
- `Provided MAX_L2_BLOCK_GAS ${config.maxL2BlockGas} exceeds L1 rollup mana limit ${rollupManaLimit} (capping)`,
278
- );
279
- maxL2BlockGas = rollupManaLimit;
280
- } else {
281
- maxL2BlockGas = config.maxL2BlockGas;
282
- }
283
- } else {
284
- maxL2BlockGas = Math.min(rollupManaLimit, Math.ceil((rollupManaLimit / maxNumberOfBlocks) * multiplier));
249
+ ): { maxL2BlockGas: number | undefined; maxDABlockGas: number | undefined; maxTxsPerBlock: number | undefined } {
250
+ let maxL2BlockGas = config.maxL2BlockGas;
251
+ if (maxL2BlockGas !== undefined && maxL2BlockGas > rollupManaLimit) {
252
+ log.warn(`Provided MAX_L2_BLOCK_GAS ${maxL2BlockGas} exceeds rollup mana limit ${rollupManaLimit} (capping)`);
253
+ maxL2BlockGas = rollupManaLimit;
285
254
  }
286
255
 
287
- // Compute maxDABlockGas
288
- const daCheckpointLimit = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
289
- let maxDABlockGas: number;
290
- if (config.maxDABlockGas !== undefined) {
291
- if (config.maxDABlockGas > daCheckpointLimit) {
292
- log.warn(
293
- `Provided MAX_DA_BLOCK_GAS ${config.maxDABlockGas} exceeds DA checkpoint limit ${daCheckpointLimit} (capping)`,
294
- );
295
- maxDABlockGas = daCheckpointLimit;
296
- } else {
297
- maxDABlockGas = config.maxDABlockGas;
298
- }
299
- } else {
300
- maxDABlockGas = Math.min(daCheckpointLimit, Math.ceil((daCheckpointLimit / maxNumberOfBlocks) * multiplier));
256
+ let maxDABlockGas = config.maxDABlockGas;
257
+ if (maxDABlockGas !== undefined && maxDABlockGas > MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT) {
258
+ log.warn(
259
+ `Provided MAX_DA_BLOCK_GAS ${maxDABlockGas} exceeds DA checkpoint limit ${MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT} (capping)`,
260
+ );
261
+ maxDABlockGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
301
262
  }
302
263
 
303
- // Compute maxTxsPerBlock
304
- const defaultMaxTxsPerBlock = 32;
305
- let maxTxsPerBlock: number;
306
- if (config.maxTxsPerBlock !== undefined) {
307
- if (config.maxTxsPerCheckpoint !== undefined && config.maxTxsPerBlock > config.maxTxsPerCheckpoint) {
308
- log.warn(
309
- `Provided MAX_TX_PER_BLOCK ${config.maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
310
- );
311
- maxTxsPerBlock = config.maxTxsPerCheckpoint;
312
- } else {
313
- maxTxsPerBlock = config.maxTxsPerBlock;
314
- }
315
- } else if (config.maxTxsPerCheckpoint !== undefined) {
316
- maxTxsPerBlock = Math.min(
317
- config.maxTxsPerCheckpoint,
318
- Math.ceil((config.maxTxsPerCheckpoint / maxNumberOfBlocks) * multiplier),
264
+ let maxTxsPerBlock = config.maxTxsPerBlock;
265
+ if (
266
+ maxTxsPerBlock !== undefined &&
267
+ config.maxTxsPerCheckpoint !== undefined &&
268
+ maxTxsPerBlock > config.maxTxsPerCheckpoint
269
+ ) {
270
+ log.warn(
271
+ `Provided MAX_TX_PER_BLOCK ${maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
319
272
  );
320
- } else {
321
- maxTxsPerBlock = defaultMaxTxsPerBlock;
273
+ maxTxsPerBlock = config.maxTxsPerCheckpoint;
322
274
  }
323
275
 
324
- log.info(`Computed block limits L2=${maxL2BlockGas} DA=${maxDABlockGas} maxTxs=${maxTxsPerBlock}`, {
325
- maxL2BlockGas,
326
- maxDABlockGas,
327
- maxTxsPerBlock,
328
- rollupManaLimit,
329
- daCheckpointLimit,
330
- maxNumberOfBlocks,
331
- multiplier,
332
- });
333
-
334
- return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint: maxNumberOfBlocks };
276
+ return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
335
277
  }
package/src/config.ts CHANGED
@@ -13,8 +13,10 @@ import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
13
13
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
14
14
  import {
15
15
  type ChainConfig,
16
+ type PipelineConfig,
16
17
  type SequencerConfig,
17
18
  chainConfigMappings,
19
+ pipelineConfigMappings,
18
20
  sharedSequencerConfigMappings,
19
21
  } from '@aztec/stdlib/config';
20
22
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
@@ -68,6 +70,7 @@ export type SequencerClientConfig = SequencerPublisherConfig &
68
70
  SequencerConfig &
69
71
  L1ReaderConfig &
70
72
  ChainConfig &
73
+ PipelineConfig &
71
74
  Pick<P2PConfig, 'txPublicSetupAllowListExtend'> &
72
75
  Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
73
76
 
@@ -119,9 +122,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
119
122
  'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
120
123
  ...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
121
124
  },
122
- maxBlocksPerCheckpoint: {
123
- description: 'Computed max number of blocks per checkpoint from timetable.',
124
- },
125
125
  coinbase: {
126
126
  env: 'COINBASE',
127
127
  parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
@@ -244,6 +244,7 @@ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientCo
244
244
  ...sequencerTxSenderConfigMappings,
245
245
  ...sequencerPublisherConfigMappings,
246
246
  ...chainConfigMappings,
247
+ ...pipelineConfigMappings,
247
248
  ...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
248
249
  };
249
250
 
@@ -133,6 +133,7 @@ export class SequencerPublisher {
133
133
 
134
134
  protected log: Logger;
135
135
  protected ethereumSlotDuration: bigint;
136
+ protected aztecSlotDuration: bigint;
136
137
 
137
138
  private blobClient: BlobClientInterface;
138
139
 
@@ -166,7 +167,7 @@ export class SequencerPublisher {
166
167
 
167
168
  constructor(
168
169
  private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
169
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
170
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
170
171
  deps: {
171
172
  telemetry?: TelemetryClient;
172
173
  blobClient: BlobClientInterface;
@@ -185,6 +186,7 @@ export class SequencerPublisher {
185
186
  ) {
186
187
  this.log = deps.log ?? createLogger('sequencer:publisher');
187
188
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
189
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
188
190
  this.epochCache = deps.epochCache;
189
191
  this.lastActions = deps.lastActions;
190
192
 
@@ -286,7 +288,7 @@ export class SequencerPublisher {
286
288
  }
287
289
 
288
290
  public getCurrentL2Slot(): SlotNumber {
289
- return this.epochCache.getEpochAndSlotNow().slot;
291
+ return this.epochCache.getSlotNow();
290
292
  }
291
293
 
292
294
  /**
@@ -399,8 +401,8 @@ export class SequencerPublisher {
399
401
  // @note - we can only have one blob config per bundle
400
402
  // find requests with gas and blob configs
401
403
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
402
- const gasConfigs = requestsToProcess.filter(request => request.gasConfig).map(request => request.gasConfig);
403
- const blobConfigs = requestsToProcess.filter(request => request.blobConfig).map(request => request.blobConfig);
404
+ const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
405
+ const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
404
406
 
405
407
  if (blobConfigs.length > 1) {
406
408
  throw new Error('Multiple blob configs found');
@@ -596,20 +598,23 @@ export class SequencerPublisher {
596
598
  }
597
599
 
598
600
  /**
599
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
601
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
600
602
  * @param tipArchive - The archive to check
601
603
  * @returns The slot and block number if it is possible to propose, undefined otherwise
602
604
  */
603
- public canProposeAtNextEthBlock(
605
+ public canProposeAt(
604
606
  tipArchive: Fr,
605
607
  msgSender: EthAddress,
606
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
608
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
607
609
  ) {
608
610
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
609
611
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
610
612
 
613
+ const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
614
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
615
+
611
616
  return this.rollupContract
612
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
617
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, {
613
618
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
614
619
  })
615
620
  .catch(err => {
@@ -623,6 +628,7 @@ export class SequencerPublisher {
623
628
  return undefined;
624
629
  });
625
630
  }
631
+
626
632
  /**
627
633
  * @notice Will simulate `validateHeader` to make sure that the block header is valid
628
634
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
@@ -811,7 +817,9 @@ export class SequencerPublisher {
811
817
  attestationsAndSignersSignature: Signature,
812
818
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
813
819
  ): Promise<bigint> {
814
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
820
+ // Anchor the simulation timestamp to the checkpoint's own slot start time
821
+ // rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
822
+ const ts = checkpoint.header.timestamp;
815
823
  const blobFields = checkpoint.toBlobFields();
816
824
  const blobs = await getBlobsPerL1Block(blobFields);
817
825
  const blobInput = getPrefixedEthBlobCommitments(blobs);