@aztec/sequencer-client 0.0.1-commit.343b43af6 → 0.0.1-commit.350e0a4d

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 (121) hide show
  1. package/README.md +281 -21
  2. package/dest/client/sequencer-client.d.ts +23 -16
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +49 -94
  5. package/dest/config.d.ts +10 -2
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +61 -25
  8. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  9. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  10. package/dest/global_variable_builder/fee_predictor.js +138 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  12. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  13. package/dest/global_variable_builder/fee_provider.js +80 -0
  14. package/dest/global_variable_builder/global_builder.d.ts +13 -26
  15. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  16. package/dest/global_variable_builder/global_builder.js +9 -66
  17. package/dest/global_variable_builder/index.d.ts +4 -2
  18. package/dest/global_variable_builder/index.d.ts.map +1 -1
  19. package/dest/global_variable_builder/index.js +2 -0
  20. package/dest/publisher/config.d.ts +19 -3
  21. package/dest/publisher/config.d.ts.map +1 -1
  22. package/dest/publisher/config.js +30 -5
  23. package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
  24. package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
  25. package/dest/publisher/l1_to_l2_messaging.js +70 -0
  26. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +50 -11
  27. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
  28. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -1
  29. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +2 -2
  30. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -1
  31. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +4 -2
  32. package/dest/publisher/l1_tx_failed_store/index.d.ts +2 -2
  33. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -1
  34. package/dest/publisher/l1_tx_failed_store/index.js +1 -0
  35. package/dest/publisher/sequencer-bundle-simulator.d.ts +96 -0
  36. package/dest/publisher/sequencer-bundle-simulator.d.ts.map +1 -0
  37. package/dest/publisher/sequencer-bundle-simulator.js +198 -0
  38. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
  39. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  40. package/dest/publisher/sequencer-publisher-factory.js +2 -3
  41. package/dest/publisher/sequencer-publisher.d.ts +103 -67
  42. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  43. package/dest/publisher/sequencer-publisher.js +621 -525
  44. package/dest/publisher/write_json.d.ts +11 -0
  45. package/dest/publisher/write_json.d.ts.map +1 -0
  46. package/dest/publisher/write_json.js +57 -0
  47. package/dest/sequencer/automine/automine_factory.d.ts +56 -0
  48. package/dest/sequencer/automine/automine_factory.d.ts.map +1 -0
  49. package/dest/sequencer/automine/automine_factory.js +85 -0
  50. package/dest/sequencer/automine/automine_sequencer.d.ts +189 -0
  51. package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -0
  52. package/dest/sequencer/automine/automine_sequencer.js +690 -0
  53. package/dest/sequencer/automine/index.d.ts +3 -0
  54. package/dest/sequencer/automine/index.d.ts.map +1 -0
  55. package/dest/sequencer/automine/index.js +2 -0
  56. package/dest/sequencer/checkpoint_proposal_job.d.ts +75 -17
  57. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  58. package/dest/sequencer/checkpoint_proposal_job.js +795 -239
  59. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  60. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  61. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  62. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  63. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  64. package/dest/sequencer/checkpoint_voter.js +2 -5
  65. package/dest/sequencer/events.d.ts +62 -5
  66. package/dest/sequencer/events.d.ts.map +1 -1
  67. package/dest/sequencer/metrics.d.ts +13 -10
  68. package/dest/sequencer/metrics.d.ts.map +1 -1
  69. package/dest/sequencer/metrics.js +45 -20
  70. package/dest/sequencer/requests_tracker.d.ts +22 -0
  71. package/dest/sequencer/requests_tracker.d.ts.map +1 -0
  72. package/dest/sequencer/requests_tracker.js +33 -0
  73. package/dest/sequencer/sequencer.d.ts +159 -35
  74. package/dest/sequencer/sequencer.d.ts.map +1 -1
  75. package/dest/sequencer/sequencer.js +585 -188
  76. package/dest/sequencer/types.d.ts +2 -2
  77. package/dest/sequencer/types.d.ts.map +1 -1
  78. package/dest/test/index.d.ts +3 -3
  79. package/dest/test/index.d.ts.map +1 -1
  80. package/dest/test/mock_checkpoint_builder.d.ts +4 -4
  81. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  82. package/dest/test/utils.d.ts +15 -1
  83. package/dest/test/utils.d.ts.map +1 -1
  84. package/dest/test/utils.js +25 -7
  85. package/package.json +30 -29
  86. package/src/client/sequencer-client.ts +70 -124
  87. package/src/config.ts +71 -22
  88. package/src/global_variable_builder/README.md +44 -0
  89. package/src/global_variable_builder/fee_predictor.ts +182 -0
  90. package/src/global_variable_builder/fee_provider.ts +97 -0
  91. package/src/global_variable_builder/global_builder.ts +19 -88
  92. package/src/global_variable_builder/index.ts +3 -1
  93. package/src/publisher/config.ts +62 -7
  94. package/src/publisher/l1_to_l2_messaging.ts +85 -0
  95. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +117 -8
  96. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +4 -3
  97. package/src/publisher/l1_tx_failed_store/index.ts +1 -1
  98. package/src/publisher/sequencer-bundle-simulator.ts +254 -0
  99. package/src/publisher/sequencer-publisher-factory.ts +3 -6
  100. package/src/publisher/sequencer-publisher.ts +720 -600
  101. package/src/publisher/write_json.ts +78 -0
  102. package/src/sequencer/README.md +162 -450
  103. package/src/sequencer/automine/README.md +60 -0
  104. package/src/sequencer/automine/automine_factory.ts +152 -0
  105. package/src/sequencer/automine/automine_sequencer.ts +796 -0
  106. package/src/sequencer/automine/index.ts +6 -0
  107. package/src/sequencer/checkpoint_proposal_job.ts +958 -265
  108. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  109. package/src/sequencer/checkpoint_voter.ts +1 -12
  110. package/src/sequencer/events.ts +66 -5
  111. package/src/sequencer/metrics.ts +57 -24
  112. package/src/sequencer/requests_tracker.ts +43 -0
  113. package/src/sequencer/sequencer.ts +690 -209
  114. package/src/sequencer/types.ts +1 -1
  115. package/src/test/index.ts +2 -2
  116. package/src/test/mock_checkpoint_builder.ts +3 -3
  117. package/src/test/utils.ts +61 -10
  118. package/dest/sequencer/timetable.d.ts +0 -88
  119. package/dest/sequencer/timetable.d.ts.map +0 -1
  120. package/dest/sequencer/timetable.js +0 -222
  121. package/src/sequencer/timetable.ts +0 -283
@@ -1,7 +1,6 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT } from '@aztec/constants';
3
3
  import { EpochCache } from '@aztec/epoch-cache';
4
- import { isAnvilTestChain } from '@aztec/ethereum/chain';
5
4
  import { getPublicClient } from '@aztec/ethereum/client';
6
5
  import { GovernanceProposerContract, RollupContract } from '@aztec/ethereum/contracts';
7
6
  import { type Delayer, L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
@@ -12,22 +11,16 @@ import type { DateProvider } from '@aztec/foundation/timer';
12
11
  import type { KeystoreManager } from '@aztec/node-keystore';
13
12
  import type { P2P } from '@aztec/p2p';
14
13
  import type { SlasherClientInterface } from '@aztec/slasher';
15
- import type { L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
14
+ import type { L2BlockSink, L2BlockSource, ProposedCheckpointSink } from '@aztec/stdlib/block';
16
15
  import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
17
- import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
18
16
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
19
17
  import { L1Metrics, type TelemetryClient } from '@aztec/telemetry-client';
20
18
  import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
21
19
 
22
- import {
23
- DefaultSequencerConfig,
24
- type SequencerClientConfig,
25
- getPublisherConfigFromSequencerConfig,
26
- } from '../config.js';
27
- import { GlobalVariableBuilder } from '../global_variable_builder/index.js';
20
+ import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js';
21
+ import type { GlobalVariableBuilder } from '../global_variable_builder/index.js';
28
22
  import { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
29
23
  import { Sequencer, type SequencerConfig } from '../sequencer/index.js';
30
- import { SequencerTimetable } from '../sequencer/timetable.js';
31
24
 
32
25
  /**
33
26
  * Encapsulates the full sequencer and publisher.
@@ -62,7 +55,7 @@ export class SequencerClient {
62
55
  worldStateSynchronizer: WorldStateSynchronizer;
63
56
  slasherClient: SlasherClientInterface | undefined;
64
57
  checkpointsBuilder: FullNodeCheckpointsBuilder;
65
- l2BlockSource: L2BlockSource & L2BlockSink;
58
+ l2BlockSource: L2BlockSource & L2BlockSink & ProposedCheckpointSink;
66
59
  l1ToL2MessageSource: L1ToL2MessageSource;
67
60
  telemetry: TelemetryClient;
68
61
  publisherFactory?: SequencerPublisherFactory;
@@ -70,7 +63,9 @@ export class SequencerClient {
70
63
  dateProvider: DateProvider;
71
64
  epochCache?: EpochCache;
72
65
  l1TxUtils: L1TxUtils[];
66
+ funderL1TxUtils?: L1TxUtils;
73
67
  nodeKeyStore: KeystoreManager;
68
+ globalVariableBuilder: GlobalVariableBuilder;
74
69
  },
75
70
  ) {
76
71
  const {
@@ -92,27 +87,25 @@ export class SequencerClient {
92
87
  publicClient,
93
88
  l1TxUtils.map(x => x.getSenderAddress()),
94
89
  );
95
- const publisherManager = new PublisherManager(
96
- l1TxUtils,
97
- getPublisherConfigFromSequencerConfig(config),
98
- log.getBindings(),
99
- );
100
- const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
101
- const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([
90
+ const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), {
91
+ bindings: log.getBindings(),
92
+ funder: deps.funderL1TxUtils,
93
+ });
94
+ const rollupContract = new RollupContract(publicClient, config.rollupAddress.toString());
95
+ const [l1GenesisTime, slotDuration, rollupManaLimit] = await Promise.all([
102
96
  rollupContract.getL1GenesisTime(),
103
97
  rollupContract.getSlotDuration(),
104
- rollupContract.getVersion(),
105
98
  rollupContract.getManaLimit().then(Number),
106
99
  ] as const);
107
100
 
108
101
  const governanceProposerContract = new GovernanceProposerContract(
109
102
  publicClient,
110
- config.l1Contracts.governanceProposerAddress.toString(),
103
+ config.governanceProposerAddress.toString(),
111
104
  );
112
105
  const epochCache =
113
106
  deps.epochCache ??
114
107
  (await EpochCache.create(
115
- config.l1Contracts.rollupAddress,
108
+ config.rollupAddress,
116
109
  {
117
110
  l1RpcUrls: rpcUrls,
118
111
  l1ChainId: chainId,
@@ -122,11 +115,6 @@ export class SequencerClient {
122
115
  { dateProvider: deps.dateProvider },
123
116
  ));
124
117
 
125
- const slashFactoryContract = new SlashFactoryContract(
126
- publicClient,
127
- config.l1Contracts.slashFactoryAddress?.toString() ?? EthAddress.ZERO.toString(),
128
- );
129
-
130
118
  const publisherFactory =
131
119
  deps.publisherFactory ??
132
120
  new SequencerPublisherFactory(config, {
@@ -134,7 +122,6 @@ export class SequencerClient {
134
122
  blobClient: deps.blobClient,
135
123
  epochCache,
136
124
  governanceProposerContract,
137
- slashFactoryContract,
138
125
  rollupContract,
139
126
  dateProvider: deps.dateProvider,
140
127
  publisherManager,
@@ -144,30 +131,17 @@ export class SequencerClient {
144
131
 
145
132
  const ethereumSlotDuration = config.ethereumSlotDuration;
146
133
 
147
- const globalsBuilder = new GlobalVariableBuilder({
148
- ...config,
134
+ const globalsBuilder = deps.globalVariableBuilder;
135
+
136
+ const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = capPerBlockLimits(config, rollupManaLimit, log);
137
+
138
+ const l1Constants = {
149
139
  l1GenesisTime,
150
140
  slotDuration: Number(slotDuration),
151
141
  ethereumSlotDuration,
152
- rollupVersion,
153
- });
154
-
155
- // When running in anvil, assume we can post a tx up until one second before the end of an L1 slot.
156
- // Otherwise, we need the full L1 slot duration for publishing to ensure inclusion.
157
- // In theory, the L1 slot has an initial 4s phase where the block is propagated, so we could
158
- // reduce the publishing time allowance. However, we prefer being conservative.
159
- // See https://www.blocknative.com/blog/anatomy-of-a-slot#7 for more info.
160
- const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
161
- const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
162
-
163
- const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = computeBlockLimits(
164
- config,
165
142
  rollupManaLimit,
166
- l1PublishingTime,
167
- log,
168
- );
169
-
170
- const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration, rollupManaLimit };
143
+ epochDuration: config.aztecEpochDuration,
144
+ };
171
145
 
172
146
  const sequencer = new Sequencer(
173
147
  publisherFactory,
@@ -183,7 +157,7 @@ export class SequencerClient {
183
157
  deps.dateProvider,
184
158
  epochCache,
185
159
  rollupContract,
186
- { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
160
+ { ...config, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
187
161
  telemetryClient,
188
162
  log,
189
163
  );
@@ -206,24 +180,45 @@ export class SequencerClient {
206
180
  this.validatorClient?.updateConfig(config);
207
181
  }
208
182
 
209
- /** Starts the sequencer. */
183
+ /**
184
+ * Starts (or resumes) the sequencer, validator, publishers, and metrics. Each underlying start is
185
+ * idempotent, so this is safe to call after a previous {@link pause} to resume building and publishing.
186
+ * The publisher manager is started before the sequencer's poll loop so publishing is ready before the
187
+ * sequencer first tries to publish to L1.
188
+ */
210
189
  public async start() {
211
190
  await this.validatorClient?.start();
191
+ await this.publisherManager.start();
212
192
  this.sequencer.start();
213
193
  this.l1Metrics?.start();
214
- await this.publisherManager.loadState();
215
194
  }
216
195
 
217
196
  /**
218
- * Stops the sequencer from processing new txs.
197
+ * Stops the sequencer, validator, publishers, and metrics for good, draining in-flight work. This is the
198
+ * final teardown path: stopping the validator client closes its slashing-protection database. For a
199
+ * restartable pause (e.g. around a test clock warp) use {@link pause} instead.
219
200
  */
220
201
  public async stop() {
221
202
  await this.sequencer.stop();
222
203
  await this.validatorClient?.stop();
223
- this.publisherManager.interrupt();
204
+ await this.publisherManager.stop();
224
205
  this.l1Metrics?.stop();
225
206
  }
226
207
 
208
+ /**
209
+ * Gracefully pauses block production, waiting for in-flight work to finish rather than interrupting it,
210
+ * while leaving the validator, publishers, and metrics running so the sequencer can be resumed with
211
+ * {@link start}. Used by tests that pause sequencers around an L1 clock warp.
212
+ */
213
+ public async pause() {
214
+ await this.sequencer.pause();
215
+ }
216
+
217
+ /** Triggers an immediate run of the sequencer, bypassing the polling interval. */
218
+ public trigger() {
219
+ return this.sequencer.trigger();
220
+ }
221
+
227
222
  public getSequencer(): Sequencer {
228
223
  return this.sequencer;
229
224
  }
@@ -248,88 +243,39 @@ export class SequencerClient {
248
243
  }
249
244
 
250
245
  /**
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.
246
+ * Caps operator-provided per-block limits at checkpoint-level limits.
247
+ * Returns undefined for any limit the operator didn't set the checkpoint builder handles redistribution.
254
248
  */
255
- export function computeBlockLimits(
249
+ function capPerBlockLimits(
256
250
  config: SequencerClientConfig,
257
251
  rollupManaLimit: number,
258
- l1PublishingTime: number,
259
252
  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));
253
+ ): { maxL2BlockGas: number | undefined; maxDABlockGas: number | undefined; maxTxsPerBlock: number | undefined } {
254
+ let maxL2BlockGas = config.maxL2BlockGas;
255
+ if (maxL2BlockGas !== undefined && maxL2BlockGas > rollupManaLimit) {
256
+ log.warn(`Provided MAX_L2_BLOCK_GAS ${maxL2BlockGas} exceeds rollup mana limit ${rollupManaLimit} (capping)`);
257
+ maxL2BlockGas = rollupManaLimit;
285
258
  }
286
259
 
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));
260
+ let maxDABlockGas = config.maxDABlockGas;
261
+ if (maxDABlockGas !== undefined && maxDABlockGas > MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT) {
262
+ log.warn(
263
+ `Provided MAX_DA_BLOCK_GAS ${maxDABlockGas} exceeds DA checkpoint limit ${MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT} (capping)`,
264
+ );
265
+ maxDABlockGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
301
266
  }
302
267
 
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),
268
+ let maxTxsPerBlock = config.maxTxsPerBlock;
269
+ if (
270
+ maxTxsPerBlock !== undefined &&
271
+ config.maxTxsPerCheckpoint !== undefined &&
272
+ maxTxsPerBlock > config.maxTxsPerCheckpoint
273
+ ) {
274
+ log.warn(
275
+ `Provided MAX_TX_PER_BLOCK ${maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
319
276
  );
320
- } else {
321
- maxTxsPerBlock = defaultMaxTxsPerBlock;
277
+ maxTxsPerBlock = config.maxTxsPerCheckpoint;
322
278
  }
323
279
 
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
280
  return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
335
281
  }
package/src/config.ts CHANGED
@@ -3,8 +3,10 @@ import { type L1ReaderConfig, l1ReaderConfigMappings } from '@aztec/ethereum/l1-
3
3
  import {
4
4
  type ConfigMappingsType,
5
5
  booleanConfigHelper,
6
+ floatConfigHelper,
6
7
  getConfigFromMappings,
7
8
  numberConfigHelper,
9
+ optionalNumberConfigHelper,
8
10
  pickConfigMappings,
9
11
  } from '@aztec/foundation/config';
10
12
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -13,10 +15,13 @@ import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
13
15
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
14
16
  import {
15
17
  type ChainConfig,
18
+ DEFAULT_BLOCK_DURATION_MS,
19
+ DEFAULT_MAX_BLOCKS_PER_CHECKPOINT,
16
20
  type SequencerConfig,
17
21
  chainConfigMappings,
18
22
  sharedSequencerConfigMappings,
19
23
  } from '@aztec/stdlib/config';
24
+ import { MIN_PER_BLOCK_ALLOCATION_MULTIPLIER, MIN_PER_BLOCK_DA_ALLOCATION_MULTIPLIER } from '@aztec/stdlib/gas';
20
25
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
21
26
  import { DEFAULT_P2P_PROPAGATION_TIME } from '@aztec/stdlib/timetable';
22
27
  import { type ValidatorClientConfig, validatorClientConfigMappings } from '@aztec/validator-client/config';
@@ -40,21 +45,29 @@ export const DefaultSequencerConfig = {
40
45
  minTxsPerBlock: 1,
41
46
  buildCheckpointIfEmpty: false,
42
47
  publishTxsWithProposals: false,
43
- perBlockAllocationMultiplier: 2,
44
- enforceTimeTable: true,
48
+ perBlockAllocationMultiplier: MIN_PER_BLOCK_ALLOCATION_MULTIPLIER,
49
+ perBlockDAAllocationMultiplier: MIN_PER_BLOCK_DA_ALLOCATION_MULTIPLIER,
50
+ redistributeCheckpointBudget: true,
51
+ blockDurationMs: DEFAULT_BLOCK_DURATION_MS,
52
+ l1PublishingTime: 12,
53
+ checkpointProposalSyncGraceSeconds: 2 * (DEFAULT_BLOCK_DURATION_MS / 1000),
45
54
  attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
46
55
  secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
47
56
  secondsBeforeInvalidatingBlockAsNonCommitteeMember: 432, // 36 L1 blocks
48
57
  skipCollectingAttestations: false,
49
58
  skipInvalidateBlockAsProposer: false,
59
+ skipWaitForValidParentCheckpointOnL1: false,
50
60
  broadcastInvalidBlockProposal: false,
61
+ broadcastInvalidCheckpointProposalOnly: false,
51
62
  injectFakeAttestation: false,
52
63
  injectHighSValueAttestation: false,
53
64
  injectUnrecoverableSignatureAttestation: false,
65
+ injectYParityAttestation: false,
54
66
  fishermanMode: false,
55
67
  shuffleAttestationOrdering: false,
56
68
  skipPushProposedBlocksToArchiver: false,
57
69
  skipPublishingCheckpointsPercent: 0,
70
+ maxBlocksPerCheckpoint: DEFAULT_MAX_BLOCKS_PER_CHECKPOINT,
58
71
  } satisfies ResolvedSequencerConfig;
59
72
 
60
73
  /**
@@ -79,7 +92,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
79
92
  maxTxsPerCheckpoint: {
80
93
  env: 'SEQ_MAX_TX_PER_CHECKPOINT',
81
94
  description: 'The maximum number of txs across all blocks in a checkpoint.',
82
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
95
+ ...optionalNumberConfigHelper(),
83
96
  },
84
97
  minTxsPerBlock: {
85
98
  env: 'SEQ_MIN_TX_PER_BLOCK',
@@ -98,28 +111,41 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
98
111
  maxL2BlockGas: {
99
112
  env: 'SEQ_MAX_L2_BLOCK_GAS',
100
113
  description: 'The maximum L2 block gas.',
101
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
114
+ ...optionalNumberConfigHelper(),
102
115
  },
103
116
  maxDABlockGas: {
104
117
  env: 'SEQ_MAX_DA_BLOCK_GAS',
105
118
  description: 'The maximum DA block gas.',
106
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
119
+ ...optionalNumberConfigHelper(),
107
120
  },
108
121
  perBlockAllocationMultiplier: {
109
122
  env: 'SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER',
110
123
  description:
111
124
  'Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) * multiplier.' +
112
125
  ' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
113
- ...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
126
+ ...floatConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
127
+ },
128
+ perBlockDAAllocationMultiplier: {
129
+ env: 'SEQ_PER_BLOCK_DA_ALLOCATION_MULTIPLIER',
130
+ description:
131
+ 'Per-block budget multiplier applied to DA gas and blob fields in place of perBlockAllocationMultiplier.' +
132
+ ' Defaults higher than the general multiplier so the largest contract class deploy fits a single block.',
133
+ ...floatConfigHelper(DefaultSequencerConfig.perBlockDAAllocationMultiplier),
134
+ },
135
+ redistributeCheckpointBudget: {
136
+ env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
137
+ description:
138
+ 'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
139
+ ...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
114
140
  },
115
141
  coinbase: {
116
142
  env: 'COINBASE',
117
- parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
143
+ parseEnv: (val: string) => EthAddress.fromString(val),
118
144
  description: 'Recipient of block reward.',
119
145
  },
120
146
  feeRecipient: {
121
147
  env: 'FEE_RECIPIENT',
122
- parseEnv: (val: string) => AztecAddress.fromString(val),
148
+ parseEnv: (val: string) => AztecAddress.fromStringUnsafe(val),
123
149
  description: 'Address to receive fees.',
124
150
  },
125
151
  acvmWorkingDirectory: {
@@ -130,11 +156,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
130
156
  env: 'ACVM_BINARY_PATH',
131
157
  description: 'The path to the ACVM binary',
132
158
  },
133
- enforceTimeTable: {
134
- env: 'SEQ_ENFORCE_TIME_TABLE',
135
- description: 'Whether to enforce the time table when building blocks',
136
- ...booleanConfigHelper(DefaultSequencerConfig.enforceTimeTable),
137
- },
138
159
  governanceProposerPayload: {
139
160
  env: 'GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS',
140
161
  description: 'The address of the payload for the governanceProposer',
@@ -142,13 +163,8 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
142
163
  },
143
164
  l1PublishingTime: {
144
165
  env: 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT',
145
- description: 'How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot).',
146
- parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
147
- },
148
- attestationPropagationTime: {
149
- env: 'SEQ_ATTESTATION_PROPAGATION_TIME',
150
- description: 'How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way)',
151
- ...numberConfigHelper(DefaultSequencerConfig.attestationPropagationTime),
166
+ description: 'How much time in seconds to allow in the slot for publishing the L1 transaction.',
167
+ ...numberConfigHelper(DefaultSequencerConfig.l1PublishingTime),
152
168
  },
153
169
  fakeProcessingDelayPerTxMs: {
154
170
  description: 'Used for testing to introduce a fake delay after processing each tx',
@@ -179,10 +195,25 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
179
195
  description: 'Do not invalidate the previous block if invalid when we are the proposer (for testing only)',
180
196
  ...booleanConfigHelper(DefaultSequencerConfig.skipInvalidateBlockAsProposer),
181
197
  },
198
+ skipWaitForValidParentCheckpointOnL1: {
199
+ description:
200
+ 'Bypass the parent checkpoint validity check before submitting a pipelined checkpoint, ' +
201
+ 'allowing the proposer to publish even when the parent landed on L1 with invalid attestations (for testing only)',
202
+ ...booleanConfigHelper(DefaultSequencerConfig.skipWaitForValidParentCheckpointOnL1),
203
+ },
182
204
  broadcastInvalidBlockProposal: {
183
205
  description: 'Broadcast invalid block proposals with corrupted state (for testing only)',
184
206
  ...booleanConfigHelper(DefaultSequencerConfig.broadcastInvalidBlockProposal),
185
207
  },
208
+ invalidBlockProposalIndexWithinCheckpoint: {
209
+ description: 'Broadcast an invalid block proposal only at this indexWithinCheckpoint (for testing only)',
210
+ ...optionalNumberConfigHelper(),
211
+ },
212
+ broadcastInvalidCheckpointProposalOnly: {
213
+ description:
214
+ 'Broadcast invalid checkpoint proposals while keeping the underlying block proposals valid (for testing only). When unset, the checkpoint follows broadcastInvalidBlockProposal.',
215
+ ...booleanConfigHelper(DefaultSequencerConfig.broadcastInvalidCheckpointProposalOnly),
216
+ },
186
217
  injectFakeAttestation: {
187
218
  description: 'Inject a fake attestation (for testing only)',
188
219
  ...booleanConfigHelper(DefaultSequencerConfig.injectFakeAttestation),
@@ -195,6 +226,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
195
226
  description: 'Inject an attestation with an unrecoverable signature (for testing only)',
196
227
  ...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
197
228
  },
229
+ injectYParityAttestation: {
230
+ description: 'Inject a non-proposer attestation slot in yParity form in the packed L1 tuple (for testing only)',
231
+ ...booleanConfigHelper(DefaultSequencerConfig.injectYParityAttestation),
232
+ },
198
233
  fishermanMode: {
199
234
  env: 'FISHERMAN_MODE',
200
235
  description:
@@ -212,7 +247,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
212
247
  ...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
213
248
  },
214
249
  skipPushProposedBlocksToArchiver: {
215
- description: 'Skip pushing proposed blocks to archiver (default: true)',
250
+ description: 'Skip pushing proposed blocks to archiver (test only)',
216
251
  ...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
217
252
  },
218
253
  minBlocksForCheckpoint: {
@@ -223,17 +258,31 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
223
258
  description: 'Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only)',
224
259
  ...numberConfigHelper(DefaultSequencerConfig.skipPublishingCheckpointsPercent),
225
260
  },
261
+ skipBroadcastProposals: {
262
+ description: 'Skip broadcasting checkpoint and block proposals via gossipsub when proposer (for testing only)',
263
+ ...booleanConfigHelper(false),
264
+ },
265
+ skipBroadcastCheckpointProposal: {
266
+ description:
267
+ 'Skip broadcasting only the CheckpointProposal via gossipsub when proposer; the held last block is broadcast ' +
268
+ 'standalone instead so peers still receive it as a proposed-but-uncheckpointed tip (for testing only)',
269
+ ...booleanConfigHelper(false),
270
+ },
271
+ pauseProposingForSlots: {
272
+ description:
273
+ 'List of slots for which the sequencer will not produce a proposal (for testing only). Attestation paths are unaffected.',
274
+ },
226
275
  ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowListExtend']),
227
276
  };
228
277
 
229
278
  export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientConfig> = {
279
+ ...chainConfigMappings,
230
280
  ...validatorClientConfigMappings,
231
281
  ...sequencerConfigMappings,
232
282
  ...keyStoreConfigMappings,
233
283
  ...l1ReaderConfigMappings,
234
284
  ...sequencerTxSenderConfigMappings,
235
285
  ...sequencerPublisherConfigMappings,
236
- ...chainConfigMappings,
237
286
  ...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
238
287
  };
239
288
 
@@ -0,0 +1,44 @@
1
+ # Fee Prediction
2
+
3
+ The `FeePredictor` predicts min fees for upcoming L2 slots based on the current L1
4
+ oracle state and an assumed mana usage pattern.
5
+
6
+ ## Prediction Window
7
+
8
+ The prediction covers `LAG = 2` entries (the next available slot plus 1 more).
9
+ A new oracle update can activate at `startSlot + LAG`, so only the first LAG entries
10
+ are guaranteed stable. Beyond that, L1 fees could change unpredictably.
11
+
12
+ Each entry uses:
13
+
14
+ - The **actual L1 fees** the oracle would return for that slot's timestamp (pre or post,
15
+ depending on whether the slot is before or after `slotOfChange`).
16
+ - A **configurable congestion assumption** (`ManaUsageEstimate`): none (0 mana), target
17
+ (steady state), or limit (worst case, 2x target).
18
+
19
+ The client picks `max(predicted[0..LAG-1])` as their `maxFeesPerGas`.
20
+
21
+ ```
22
+ predicted[0] → fee at next slot (current oracle + current congestion)
23
+ predicted[1] → fee at next slot + 1 (congestion may change based on usage estimate)
24
+ ```
25
+
26
+ ## Why LAG and not LIFETIME?
27
+
28
+ The L1 gas oracle has two timing constants:
29
+
30
+ - **LAG = 2 slots**: when new fees are queued, they activate LAG slots later
31
+ - **LIFETIME = 5 slots**: after an oracle update, the next update is rejected until
32
+ `slotOfChange + (LIFETIME - LAG)` slots have passed
33
+
34
+ If the oracle cooldown has already elapsed (i.e., no recent update), a new update can
35
+ be enqueued at any moment. Its new values would activate LAG slots later. This means
36
+ predictions beyond LAG slots could be invalidated by an oracle update that happens
37
+ right after we query.
38
+
39
+ With a LIFETIME-sized window, we'd give a false sense of coverage: the prediction
40
+ would look like it covers 6 slots, but slots beyond LAG could be wrong if an update
41
+ is enqueued after the prediction is computed.
42
+
43
+ By limiting to LAG entries, we guarantee that all predicted L1 fees are the ones that
44
+ will actually be used — no oracle update can change them within this window.