@aztec/sequencer-client 0.0.1-commit.684755437 → 0.0.1-commit.6b113946b

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 (38) hide show
  1. package/dest/client/sequencer-client.d.ts +1 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +19 -63
  4. package/dest/config.d.ts +4 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +9 -2
  7. package/dest/global_variable_builder/global_builder.d.ts +1 -1
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +5 -4
  10. package/dest/publisher/sequencer-publisher.d.ts +6 -4
  11. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  12. package/dest/publisher/sequencer-publisher.js +24 -8
  13. package/dest/sequencer/checkpoint_proposal_job.d.ts +12 -4
  14. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  15. package/dest/sequencer/checkpoint_proposal_job.js +142 -97
  16. package/dest/sequencer/events.d.ts +2 -1
  17. package/dest/sequencer/events.d.ts.map +1 -1
  18. package/dest/sequencer/metrics.d.ts +5 -1
  19. package/dest/sequencer/metrics.d.ts.map +1 -1
  20. package/dest/sequencer/metrics.js +11 -0
  21. package/dest/sequencer/sequencer.d.ts +7 -5
  22. package/dest/sequencer/sequencer.d.ts.map +1 -1
  23. package/dest/sequencer/sequencer.js +68 -58
  24. package/dest/sequencer/types.d.ts +2 -5
  25. package/dest/sequencer/types.d.ts.map +1 -1
  26. package/dest/test/mock_checkpoint_builder.d.ts +4 -4
  27. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  28. package/package.json +27 -28
  29. package/src/client/sequencer-client.ts +26 -84
  30. package/src/config.ts +12 -1
  31. package/src/global_variable_builder/global_builder.ts +3 -2
  32. package/src/publisher/sequencer-publisher.ts +28 -10
  33. package/src/sequencer/checkpoint_proposal_job.ts +190 -101
  34. package/src/sequencer/events.ts +1 -1
  35. package/src/sequencer/metrics.ts +14 -0
  36. package/src/sequencer/sequencer.ts +94 -65
  37. package/src/sequencer/types.ts +2 -5
  38. package/src/test/mock_checkpoint_builder.ts +3 -3
@@ -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 } = 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
 
@@ -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 } {
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
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';
@@ -40,7 +42,8 @@ export const DefaultSequencerConfig = {
40
42
  minTxsPerBlock: 1,
41
43
  buildCheckpointIfEmpty: false,
42
44
  publishTxsWithProposals: false,
43
- perBlockAllocationMultiplier: 2,
45
+ perBlockAllocationMultiplier: 1.2,
46
+ redistributeCheckpointBudget: true,
44
47
  enforceTimeTable: true,
45
48
  attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
46
49
  secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
@@ -67,6 +70,7 @@ export type SequencerClientConfig = SequencerPublisherConfig &
67
70
  SequencerConfig &
68
71
  L1ReaderConfig &
69
72
  ChainConfig &
73
+ PipelineConfig &
70
74
  Pick<P2PConfig, 'txPublicSetupAllowListExtend'> &
71
75
  Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
72
76
 
@@ -112,6 +116,12 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
112
116
  ' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
113
117
  ...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
114
118
  },
119
+ redistributeCheckpointBudget: {
120
+ env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
121
+ description:
122
+ 'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
123
+ ...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
124
+ },
115
125
  coinbase: {
116
126
  env: 'COINBASE',
117
127
  parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
@@ -234,6 +244,7 @@ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientCo
234
244
  ...sequencerTxSenderConfigMappings,
235
245
  ...sequencerPublisherConfigMappings,
236
246
  ...chainConfigMappings,
247
+ ...pipelineConfigMappings,
237
248
  ...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
238
249
  };
239
250
 
@@ -1,4 +1,5 @@
1
1
  import { createEthereumChain } from '@aztec/ethereum/chain';
2
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
2
3
  import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
4
  import { RollupContract } from '@aztec/ethereum/contracts';
4
5
  import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
@@ -16,7 +17,7 @@ import type {
16
17
  } from '@aztec/stdlib/tx';
17
18
  import { GlobalVariables } from '@aztec/stdlib/tx';
18
19
 
19
- import { createPublicClient, fallback, http } from 'viem';
20
+ import { createPublicClient } from 'viem';
20
21
 
21
22
  /**
22
23
  * Simple global variables builder.
@@ -53,7 +54,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
53
54
 
54
55
  this.publicClient = createPublicClient({
55
56
  chain: chain.chainInfo,
56
- transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
57
+ transport: makeL1HttpTransport(chain.rpcUrls, { timeout: config.l1HttpTimeoutMS }),
57
58
  pollingInterval: config.viemPollingIntervalMS,
58
59
  });
59
60
 
@@ -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 { TimeoutError } from '@aztec/foundation/error';
@@ -132,6 +133,7 @@ export class SequencerPublisher {
132
133
 
133
134
  protected log: Logger;
134
135
  protected ethereumSlotDuration: bigint;
136
+ protected aztecSlotDuration: bigint;
135
137
 
136
138
  private blobClient: BlobClientInterface;
137
139
 
@@ -165,7 +167,7 @@ export class SequencerPublisher {
165
167
 
166
168
  constructor(
167
169
  private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
168
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
170
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
169
171
  deps: {
170
172
  telemetry?: TelemetryClient;
171
173
  blobClient: BlobClientInterface;
@@ -184,6 +186,7 @@ export class SequencerPublisher {
184
186
  ) {
185
187
  this.log = deps.log ?? createLogger('sequencer:publisher');
186
188
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
189
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
187
190
  this.epochCache = deps.epochCache;
188
191
  this.lastActions = deps.lastActions;
189
192
 
@@ -285,7 +288,7 @@ export class SequencerPublisher {
285
288
  }
286
289
 
287
290
  public getCurrentL2Slot(): SlotNumber {
288
- return this.epochCache.getEpochAndSlotNow().slot;
291
+ return this.epochCache.getSlotNow();
289
292
  }
290
293
 
291
294
  /**
@@ -398,8 +401,8 @@ export class SequencerPublisher {
398
401
  // @note - we can only have one blob config per bundle
399
402
  // find requests with gas and blob configs
400
403
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
401
- const gasConfigs = requestsToProcess.filter(request => request.gasConfig).map(request => request.gasConfig);
402
- 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);
403
406
 
404
407
  if (blobConfigs.length > 1) {
405
408
  throw new Error('Multiple blob configs found');
@@ -547,7 +550,16 @@ export class SequencerPublisher {
547
550
  });
548
551
  return { failedActions: requests.map(r => r.action) };
549
552
  } else {
550
- this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
553
+ this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
554
+ result,
555
+ requests: requests.map(r => ({
556
+ ...r,
557
+ // Avoid logging large blob data
558
+ blobConfig: r.blobConfig
559
+ ? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
560
+ : undefined,
561
+ })),
562
+ });
551
563
  const successfulActions: Action[] = [];
552
564
  const failedActions: Action[] = [];
553
565
  for (const request of requests) {
@@ -586,20 +598,23 @@ export class SequencerPublisher {
586
598
  }
587
599
 
588
600
  /**
589
- * @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
590
602
  * @param tipArchive - The archive to check
591
603
  * @returns The slot and block number if it is possible to propose, undefined otherwise
592
604
  */
593
- public canProposeAtNextEthBlock(
605
+ public canProposeAt(
594
606
  tipArchive: Fr,
595
607
  msgSender: EthAddress,
596
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
608
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
597
609
  ) {
598
610
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
599
611
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
600
612
 
613
+ const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
614
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
615
+
601
616
  return this.rollupContract
602
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
617
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, {
603
618
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
604
619
  })
605
620
  .catch(err => {
@@ -613,6 +628,7 @@ export class SequencerPublisher {
613
628
  return undefined;
614
629
  });
615
630
  }
631
+
616
632
  /**
617
633
  * @notice Will simulate `validateHeader` to make sure that the block header is valid
618
634
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
@@ -801,7 +817,9 @@ export class SequencerPublisher {
801
817
  attestationsAndSignersSignature: Signature,
802
818
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
803
819
  ): Promise<bigint> {
804
- 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;
805
823
  const blobFields = checkpoint.toBlobFields();
806
824
  const blobs = await getBlobsPerL1Block(blobFields);
807
825
  const blobInput = getPrefixedEthBlobCommitments(blobs);