@aztec/sequencer-client 0.0.1-commit.3e3d0c9cd → 0.0.1-commit.3fd054f6

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 (46) hide show
  1. package/dest/client/sequencer-client.d.ts +4 -13
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +29 -80
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +2 -4
  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 -22
  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/config.d.ts +13 -1
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +17 -2
  15. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
  16. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.js +2 -2
  18. package/dest/publisher/sequencer-publisher.d.ts +9 -4
  19. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher.js +22 -8
  21. package/dest/sequencer/checkpoint_proposal_job.d.ts +12 -4
  22. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  23. package/dest/sequencer/checkpoint_proposal_job.js +122 -73
  24. package/dest/sequencer/events.d.ts +2 -1
  25. package/dest/sequencer/events.d.ts.map +1 -1
  26. package/dest/sequencer/metrics.d.ts +5 -1
  27. package/dest/sequencer/metrics.d.ts.map +1 -1
  28. package/dest/sequencer/metrics.js +11 -0
  29. package/dest/sequencer/sequencer.d.ts +5 -5
  30. package/dest/sequencer/sequencer.d.ts.map +1 -1
  31. package/dest/sequencer/sequencer.js +60 -48
  32. package/dest/test/mock_checkpoint_builder.d.ts +4 -8
  33. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  34. package/package.json +27 -27
  35. package/src/client/sequencer-client.ts +39 -103
  36. package/src/config.ts +4 -3
  37. package/src/global_variable_builder/global_builder.ts +22 -24
  38. package/src/global_variable_builder/index.ts +1 -1
  39. package/src/publisher/config.ts +32 -0
  40. package/src/publisher/sequencer-publisher-factory.ts +3 -3
  41. package/src/publisher/sequencer-publisher.ts +28 -10
  42. package/src/sequencer/checkpoint_proposal_job.ts +164 -82
  43. package/src/sequencer/events.ts +1 -1
  44. package/src/sequencer/metrics.ts +14 -0
  45. package/src/sequencer/sequencer.ts +85 -54
  46. 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';
27
- import { GlobalVariableBuilder } from '../global_variable_builder/index.js';
22
+ import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js';
23
+ import type { 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.
@@ -70,7 +65,9 @@ export class SequencerClient {
70
65
  dateProvider: DateProvider;
71
66
  epochCache?: EpochCache;
72
67
  l1TxUtils: L1TxUtils[];
68
+ funderL1TxUtils?: L1TxUtils;
73
69
  nodeKeyStore: KeystoreManager;
70
+ globalVariableBuilder: GlobalVariableBuilder;
74
71
  },
75
72
  ) {
76
73
  const {
@@ -92,16 +89,14 @@ export class SequencerClient {
92
89
  publicClient,
93
90
  l1TxUtils.map(x => x.getSenderAddress()),
94
91
  );
95
- const publisherManager = new PublisherManager(
96
- l1TxUtils,
97
- getPublisherConfigFromSequencerConfig(config),
98
- log.getBindings(),
99
- );
92
+ const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), {
93
+ bindings: log.getBindings(),
94
+ funder: deps.funderL1TxUtils,
95
+ });
100
96
  const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
101
- const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([
97
+ const [l1GenesisTime, slotDuration, rollupManaLimit] = await Promise.all([
102
98
  rollupContract.getL1GenesisTime(),
103
99
  rollupContract.getSlotDuration(),
104
- rollupContract.getVersion(),
105
100
  rollupContract.getManaLimit().then(Number),
106
101
  ] as const);
107
102
 
@@ -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
  ));
@@ -144,13 +140,7 @@ export class SequencerClient {
144
140
 
145
141
  const ethereumSlotDuration = config.ethereumSlotDuration;
146
142
 
147
- const globalsBuilder = new GlobalVariableBuilder({
148
- ...config,
149
- l1GenesisTime,
150
- slotDuration: Number(slotDuration),
151
- ethereumSlotDuration,
152
- rollupVersion,
153
- });
143
+ const globalsBuilder = deps.globalVariableBuilder;
154
144
 
155
145
  // When running in anvil, assume we can post a tx up until one second before the end of an L1 slot.
156
146
  // Otherwise, we need the full L1 slot duration for publishing to ensure inclusion.
@@ -160,12 +150,7 @@ export class SequencerClient {
160
150
  const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
161
151
  const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
162
152
 
163
- const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint } = computeBlockLimits(
164
- config,
165
- rollupManaLimit,
166
- l1PublishingTime,
167
- log,
168
- );
153
+ const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = capPerBlockLimits(config, rollupManaLimit, log);
169
154
 
170
155
  const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration, rollupManaLimit };
171
156
 
@@ -183,7 +168,7 @@ export class SequencerClient {
183
168
  deps.dateProvider,
184
169
  epochCache,
185
170
  rollupContract,
186
- { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint },
171
+ { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
187
172
  telemetryClient,
188
173
  log,
189
174
  );
@@ -211,7 +196,7 @@ export class SequencerClient {
211
196
  await this.validatorClient?.start();
212
197
  this.sequencer.start();
213
198
  this.l1Metrics?.start();
214
- await this.publisherManager.loadState();
199
+ await this.publisherManager.start();
215
200
  }
216
201
 
217
202
  /**
@@ -220,7 +205,7 @@ export class SequencerClient {
220
205
  public async stop() {
221
206
  await this.sequencer.stop();
222
207
  await this.validatorClient?.stop();
223
- this.publisherManager.interrupt();
208
+ await this.publisherManager.stop();
224
209
  this.l1Metrics?.stop();
225
210
  }
226
211
 
@@ -248,88 +233,39 @@ export class SequencerClient {
248
233
  }
249
234
 
250
235
  /**
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.
236
+ * Caps operator-provided per-block limits at checkpoint-level limits.
237
+ * Returns undefined for any limit the operator didn't set the checkpoint builder handles redistribution.
254
238
  */
255
- export function computeBlockLimits(
239
+ function capPerBlockLimits(
256
240
  config: SequencerClientConfig,
257
241
  rollupManaLimit: number,
258
- l1PublishingTime: number,
259
242
  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));
243
+ ): { maxL2BlockGas: number | undefined; maxDABlockGas: number | undefined; maxTxsPerBlock: number | undefined } {
244
+ let maxL2BlockGas = config.maxL2BlockGas;
245
+ if (maxL2BlockGas !== undefined && maxL2BlockGas > rollupManaLimit) {
246
+ log.warn(`Provided MAX_L2_BLOCK_GAS ${maxL2BlockGas} exceeds rollup mana limit ${rollupManaLimit} (capping)`);
247
+ maxL2BlockGas = rollupManaLimit;
285
248
  }
286
249
 
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));
250
+ let maxDABlockGas = config.maxDABlockGas;
251
+ if (maxDABlockGas !== undefined && maxDABlockGas > MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT) {
252
+ log.warn(
253
+ `Provided MAX_DA_BLOCK_GAS ${maxDABlockGas} exceeds DA checkpoint limit ${MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT} (capping)`,
254
+ );
255
+ maxDABlockGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
301
256
  }
302
257
 
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),
258
+ let maxTxsPerBlock = config.maxTxsPerBlock;
259
+ if (
260
+ maxTxsPerBlock !== undefined &&
261
+ config.maxTxsPerCheckpoint !== undefined &&
262
+ maxTxsPerBlock > config.maxTxsPerCheckpoint
263
+ ) {
264
+ log.warn(
265
+ `Provided MAX_TX_PER_BLOCK ${maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
319
266
  );
320
- } else {
321
- maxTxsPerBlock = defaultMaxTxsPerBlock;
267
+ maxTxsPerBlock = config.maxTxsPerCheckpoint;
322
268
  }
323
269
 
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 };
270
+ return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
335
271
  }
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
 
@@ -1,15 +1,13 @@
1
- import { createEthereumChain } from '@aztec/ethereum/chain';
2
- import { makeL1HttpTransport } from '@aztec/ethereum/client';
3
- import type { L1ContractsConfig } from '@aztec/ethereum/config';
4
1
  import { RollupContract } from '@aztec/ethereum/contracts';
5
- import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
6
3
  import type { ViemPublicClient } from '@aztec/ethereum/types';
7
4
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
8
5
  import { Fr } from '@aztec/foundation/curves/bn254';
9
6
  import type { EthAddress } from '@aztec/foundation/eth-address';
10
7
  import { createLogger } from '@aztec/foundation/log';
8
+ import type { DateProvider } from '@aztec/foundation/timer';
11
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
12
- import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
10
+ import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
13
11
  import { GasFees } from '@aztec/stdlib/gas';
14
12
  import type {
15
13
  CheckpointGlobalVariables,
@@ -17,7 +15,12 @@ import type {
17
15
  } from '@aztec/stdlib/tx';
18
16
  import { GlobalVariables } from '@aztec/stdlib/tx';
19
17
 
20
- import { createPublicClient } 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'>;
21
24
 
22
25
  /**
23
26
  * Simple global variables builder.
@@ -28,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
28
31
  private currentL1BlockNumber: bigint | undefined = undefined;
29
32
 
30
33
  private readonly rollupContract: RollupContract;
31
- private readonly publicClient: ViemPublicClient;
32
34
  private readonly ethereumSlotDuration: number;
33
35
  private readonly aztecSlotDuration: number;
34
36
  private readonly l1GenesisTime: bigint;
@@ -37,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
37
39
  private version: Fr;
38
40
 
39
41
  constructor(
40
- config: L1ReaderConfig &
41
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> &
42
- Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'> & { rollupVersion: bigint },
42
+ private readonly dateProvider: DateProvider,
43
+ private readonly publicClient: ViemPublicClient,
44
+ config: GlobalVariableBuilderConfig,
43
45
  ) {
44
- const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
45
-
46
- const chain = createEthereumChain(l1RpcUrls, chainId);
47
-
48
46
  this.version = new Fr(config.rollupVersion);
49
- this.chainId = new Fr(chainId);
47
+ this.chainId = new Fr(this.publicClient.chain!.id);
50
48
 
51
49
  this.ethereumSlotDuration = config.ethereumSlotDuration;
52
50
  this.aztecSlotDuration = config.slotDuration;
53
51
  this.l1GenesisTime = config.l1GenesisTime;
54
52
 
55
- this.publicClient = createPublicClient({
56
- chain: chain.chainInfo,
57
- transport: makeL1HttpTransport(chain.rpcUrls, { timeout: config.l1HttpTimeoutMS }),
58
- pollingInterval: config.viemPollingIntervalMS,
59
- });
60
-
61
- this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
53
+ this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
62
54
  }
63
55
 
64
56
  /**
@@ -74,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
74
66
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
75
67
  SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
76
68
  );
77
- 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
+ });
78
73
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
79
74
 
80
75
  return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
@@ -109,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
109
104
  const slot: SlotNumber =
110
105
  maybeSlot ??
111
106
  (await this.rollupContract.getSlotAt(
112
- BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)),
107
+ getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
108
+ l1GenesisTime: this.l1GenesisTime,
109
+ ethereumSlotDuration: this.ethereumSlotDuration,
110
+ }),
113
111
  ));
114
112
 
115
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';
@@ -4,6 +4,8 @@ import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from '@aztec/ethereum/l
4
4
  import { type ConfigMappingsType, SecretValue, booleanConfigHelper } from '@aztec/foundation/config';
5
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
6
 
7
+ import { parseEther } from 'viem';
8
+
7
9
  /** Configuration of the transaction publisher. */
8
10
  export type TxSenderConfig = L1ReaderConfig & {
9
11
  /** The private key to be used by the publisher. */
@@ -50,13 +52,37 @@ export type PublisherConfig = L1TxUtilsConfig &
50
52
  publisherForwarderAddress?: EthAddress;
51
53
  /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
52
54
  l1TxFailedStore?: string;
55
+ /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
56
+ publisherFundingThreshold?: bigint;
57
+ /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
58
+ publisherFundingAmount?: bigint;
53
59
  };
54
60
 
61
+ /** Shared config mappings for publisher funding, used by both sequencer and prover publisher configs. */
62
+ const publisherFundingConfigMappings = {
63
+ publisherFundingThreshold: {
64
+ env: 'PUBLISHER_FUNDING_THRESHOLD' as const,
65
+ description:
66
+ 'Min ETH balance below which a publisher gets funded. Specified in ether (e.g. 0.1). Unset = funding disabled.',
67
+ parseEnv: (val: string) => parseEther(val),
68
+ },
69
+ publisherFundingAmount: {
70
+ env: 'PUBLISHER_FUNDING_AMOUNT' as const,
71
+ description:
72
+ 'Amount of ETH to send when funding a publisher. Specified in ether (e.g. 0.5). Unset = funding disabled.',
73
+ parseEnv: (val: string) => parseEther(val),
74
+ },
75
+ };
76
+
55
77
  export type ProverPublisherConfig = L1TxUtilsConfig &
56
78
  BlobClientConfig & {
57
79
  fishermanMode?: boolean;
58
80
  proverPublisherAllowInvalidStates?: boolean;
59
81
  proverPublisherForwarderAddress?: EthAddress;
82
+ /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
83
+ publisherFundingThreshold?: bigint;
84
+ /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
85
+ publisherFundingAmount?: bigint;
60
86
  };
61
87
 
62
88
  export type SequencerPublisherConfig = L1TxUtilsConfig &
@@ -66,6 +92,10 @@ export type SequencerPublisherConfig = L1TxUtilsConfig &
66
92
  sequencerPublisherForwarderAddress?: EthAddress;
67
93
  /** Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path */
68
94
  l1TxFailedStore?: string;
95
+ /** Min ETH balance below which a publisher gets funded. Undefined = funding disabled. */
96
+ publisherFundingThreshold?: bigint;
97
+ /** Amount of ETH to send when funding a publisher. Undefined = funding disabled. */
98
+ publisherFundingAmount?: bigint;
69
99
  };
70
100
 
71
101
  export function getPublisherConfigFromProverConfig(config: ProverPublisherConfig): PublisherConfig {
@@ -142,6 +172,7 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType<SequencerPubli
142
172
  env: 'L1_TX_FAILED_STORE',
143
173
  description: 'Store for failed L1 transaction inputs (test networks only). Format: gs://bucket/path',
144
174
  },
175
+ ...publisherFundingConfigMappings,
145
176
  };
146
177
 
147
178
  export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherConfig & L1TxUtilsConfig> = {
@@ -163,4 +194,5 @@ export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherCo
163
194
  description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
164
195
  parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
165
196
  },
197
+ ...publisherFundingConfigMappings,
166
198
  };
@@ -117,8 +117,8 @@ export class SequencerPublisherFactory {
117
117
  };
118
118
  }
119
119
 
120
- /** Interrupts all publishers managed by this factory. Used during sequencer shutdown. */
121
- public interruptAll(): void {
122
- this.deps.publisherManager.interrupt();
120
+ /** Stops all publishers managed by this factory. Used during sequencer shutdown. */
121
+ public async stopAll(): Promise<void> {
122
+ await this.deps.publisherManager.stop();
123
123
  }
124
124
  }
@@ -42,6 +42,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
42
42
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
43
43
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
44
44
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
45
+ import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
45
46
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
46
47
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
47
48
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
@@ -133,6 +134,8 @@ export class SequencerPublisher {
133
134
 
134
135
  protected log: Logger;
135
136
  protected ethereumSlotDuration: bigint;
137
+ protected aztecSlotDuration: bigint;
138
+ private dateProvider: DateProvider;
136
139
 
137
140
  private blobClient: BlobClientInterface;
138
141
 
@@ -166,7 +169,7 @@ export class SequencerPublisher {
166
169
 
167
170
  constructor(
168
171
  private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
169
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
172
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
170
173
  deps: {
171
174
  telemetry?: TelemetryClient;
172
175
  blobClient: BlobClientInterface;
@@ -185,6 +188,8 @@ export class SequencerPublisher {
185
188
  ) {
186
189
  this.log = deps.log ?? createLogger('sequencer:publisher');
187
190
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
191
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
192
+ this.dateProvider = deps.dateProvider;
188
193
  this.epochCache = deps.epochCache;
189
194
  this.lastActions = deps.lastActions;
190
195
 
@@ -286,7 +291,7 @@ export class SequencerPublisher {
286
291
  }
287
292
 
288
293
  public getCurrentL2Slot(): SlotNumber {
289
- return this.epochCache.getEpochAndSlotNow().slot;
294
+ return this.epochCache.getSlotNow();
290
295
  }
291
296
 
292
297
  /**
@@ -399,8 +404,8 @@ export class SequencerPublisher {
399
404
  // @note - we can only have one blob config per bundle
400
405
  // find requests with gas and blob configs
401
406
  // 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);
407
+ const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
408
+ const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
404
409
 
405
410
  if (blobConfigs.length > 1) {
406
411
  throw new Error('Multiple blob configs found');
@@ -596,20 +601,24 @@ export class SequencerPublisher {
596
601
  }
597
602
 
598
603
  /**
599
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
604
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
600
605
  * @param tipArchive - The archive to check
601
606
  * @returns The slot and block number if it is possible to propose, undefined otherwise
602
607
  */
603
- public canProposeAtNextEthBlock(
608
+ public canProposeAt(
604
609
  tipArchive: Fr,
605
610
  msgSender: EthAddress,
606
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
611
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
607
612
  ) {
608
613
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
609
614
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
610
615
 
616
+ const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
617
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
618
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
619
+
611
620
  return this.rollupContract
612
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
621
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
613
622
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
614
623
  })
615
624
  .catch(err => {
@@ -623,6 +632,7 @@ export class SequencerPublisher {
623
632
  return undefined;
624
633
  });
625
634
  }
635
+
626
636
  /**
627
637
  * @notice Will simulate `validateHeader` to make sure that the block header is valid
628
638
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
@@ -646,7 +656,7 @@ export class SequencerPublisher {
646
656
  flags,
647
657
  ] as const;
648
658
 
649
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
659
+ const ts = this.getNextL1SlotTimestamp();
650
660
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
651
661
  opts?.forcePendingCheckpointNumber,
652
662
  );
@@ -811,7 +821,9 @@ export class SequencerPublisher {
811
821
  attestationsAndSignersSignature: Signature,
812
822
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
813
823
  ): Promise<bigint> {
814
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
824
+ // Anchor the simulation timestamp to the checkpoint's own slot start time
825
+ // rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
826
+ const ts = checkpoint.header.timestamp;
815
827
  const blobFields = checkpoint.toBlobFields();
816
828
  const blobs = await getBlobsPerL1Block(blobFields);
817
829
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -1577,4 +1589,10 @@ export class SequencerPublisher {
1577
1589
  },
1578
1590
  });
1579
1591
  }
1592
+
1593
+ /** Returns the timestamp to use when simulating L1 proposal calls */
1594
+ private getNextL1SlotTimestamp(): bigint {
1595
+ const l1Constants = this.epochCache.getL1Constants();
1596
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1597
+ }
1580
1598
  }