@aztec/sequencer-client 0.0.1-commit.cb6bed7c2 → 0.0.1-commit.cbf2c2d5d

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 (49) hide show
  1. package/dest/client/sequencer-client.d.ts +4 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +27 -76
  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 +13 -7
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +22 -21
  10. package/dest/global_variable_builder/index.d.ts +2 -2
  11. package/dest/global_variable_builder/index.d.ts.map +1 -1
  12. package/dest/publisher/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 +33 -9
  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 +142 -97
  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 +7 -5
  30. package/dest/sequencer/sequencer.d.ts.map +1 -1
  31. package/dest/sequencer/sequencer.js +71 -61
  32. package/dest/sequencer/types.d.ts +2 -5
  33. package/dest/sequencer/types.d.ts.map +1 -1
  34. package/dest/test/mock_checkpoint_builder.d.ts +4 -4
  35. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  36. package/package.json +27 -28
  37. package/src/client/sequencer-client.ts +37 -101
  38. package/src/config.ts +12 -1
  39. package/src/global_variable_builder/global_builder.ts +22 -23
  40. package/src/global_variable_builder/index.ts +1 -1
  41. package/src/publisher/config.ts +32 -0
  42. package/src/publisher/sequencer-publisher-factory.ts +3 -3
  43. package/src/publisher/sequencer-publisher.ts +39 -11
  44. package/src/sequencer/checkpoint_proposal_job.ts +190 -101
  45. package/src/sequencer/events.ts +1 -1
  46. package/src/sequencer/metrics.ts +14 -0
  47. package/src/sequencer/sequencer.ts +97 -68
  48. package/src/sequencer/types.ts +2 -5
  49. 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 } = 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
 
@@ -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 } {
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
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';
@@ -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,14 +1,13 @@
1
- import { createEthereumChain } from '@aztec/ethereum/chain';
2
- import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
1
  import { RollupContract } from '@aztec/ethereum/contracts';
4
- import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
5
3
  import type { ViemPublicClient } from '@aztec/ethereum/types';
6
4
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
7
5
  import { Fr } from '@aztec/foundation/curves/bn254';
8
6
  import type { EthAddress } from '@aztec/foundation/eth-address';
9
7
  import { createLogger } from '@aztec/foundation/log';
8
+ import type { DateProvider } from '@aztec/foundation/timer';
10
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
- import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
10
+ import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
12
11
  import { GasFees } from '@aztec/stdlib/gas';
13
12
  import type {
14
13
  CheckpointGlobalVariables,
@@ -16,7 +15,12 @@ import type {
16
15
  } from '@aztec/stdlib/tx';
17
16
  import { GlobalVariables } from '@aztec/stdlib/tx';
18
17
 
19
- import { createPublicClient, fallback, http } from 'viem';
18
+ /** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
19
+ export type GlobalVariableBuilderConfig = {
20
+ l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
21
+ ethereumSlotDuration: number;
22
+ rollupVersion: bigint;
23
+ } & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
20
24
 
21
25
  /**
22
26
  * Simple global variables builder.
@@ -27,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
27
31
  private currentL1BlockNumber: bigint | undefined = undefined;
28
32
 
29
33
  private readonly rollupContract: RollupContract;
30
- private readonly publicClient: ViemPublicClient;
31
34
  private readonly ethereumSlotDuration: number;
32
35
  private readonly aztecSlotDuration: number;
33
36
  private readonly l1GenesisTime: bigint;
@@ -36,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
36
39
  private version: Fr;
37
40
 
38
41
  constructor(
39
- config: L1ReaderConfig &
40
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> &
41
- Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'> & { rollupVersion: bigint },
42
+ private readonly dateProvider: DateProvider,
43
+ private readonly publicClient: ViemPublicClient,
44
+ config: GlobalVariableBuilderConfig,
42
45
  ) {
43
- const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
44
-
45
- const chain = createEthereumChain(l1RpcUrls, chainId);
46
-
47
46
  this.version = new Fr(config.rollupVersion);
48
- this.chainId = new Fr(chainId);
47
+ this.chainId = new Fr(this.publicClient.chain!.id);
49
48
 
50
49
  this.ethereumSlotDuration = config.ethereumSlotDuration;
51
50
  this.aztecSlotDuration = config.slotDuration;
52
51
  this.l1GenesisTime = config.l1GenesisTime;
53
52
 
54
- this.publicClient = createPublicClient({
55
- chain: chain.chainInfo,
56
- transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
57
- pollingInterval: config.viemPollingIntervalMS,
58
- });
59
-
60
- this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
53
+ this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
61
54
  }
62
55
 
63
56
  /**
@@ -73,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
73
66
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
74
67
  SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
75
68
  );
76
- const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
69
+ const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
70
+ l1GenesisTime: this.l1GenesisTime,
71
+ ethereumSlotDuration: this.ethereumSlotDuration,
72
+ });
77
73
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
78
74
 
79
75
  return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
@@ -108,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
108
104
  const slot: SlotNumber =
109
105
  maybeSlot ??
110
106
  (await this.rollupContract.getSlotAt(
111
- BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)),
107
+ getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
108
+ l1GenesisTime: this.l1GenesisTime,
109
+ ethereumSlotDuration: this.ethereumSlotDuration,
110
+ }),
112
111
  ));
113
112
 
114
113
  const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
@@ -1 +1 @@
1
- export { GlobalVariableBuilder } from './global_builder.js';
1
+ export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
@@ -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
  }
@@ -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';
@@ -41,6 +42,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
41
42
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
42
43
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
43
44
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
45
+ import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
44
46
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
45
47
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
46
48
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
@@ -132,6 +134,8 @@ export class SequencerPublisher {
132
134
 
133
135
  protected log: Logger;
134
136
  protected ethereumSlotDuration: bigint;
137
+ protected aztecSlotDuration: bigint;
138
+ private dateProvider: DateProvider;
135
139
 
136
140
  private blobClient: BlobClientInterface;
137
141
 
@@ -165,7 +169,7 @@ export class SequencerPublisher {
165
169
 
166
170
  constructor(
167
171
  private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
168
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
172
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
169
173
  deps: {
170
174
  telemetry?: TelemetryClient;
171
175
  blobClient: BlobClientInterface;
@@ -184,6 +188,8 @@ export class SequencerPublisher {
184
188
  ) {
185
189
  this.log = deps.log ?? createLogger('sequencer:publisher');
186
190
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
191
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
192
+ this.dateProvider = deps.dateProvider;
187
193
  this.epochCache = deps.epochCache;
188
194
  this.lastActions = deps.lastActions;
189
195
 
@@ -285,7 +291,7 @@ export class SequencerPublisher {
285
291
  }
286
292
 
287
293
  public getCurrentL2Slot(): SlotNumber {
288
- return this.epochCache.getEpochAndSlotNow().slot;
294
+ return this.epochCache.getSlotNow();
289
295
  }
290
296
 
291
297
  /**
@@ -398,8 +404,8 @@ export class SequencerPublisher {
398
404
  // @note - we can only have one blob config per bundle
399
405
  // find requests with gas and blob configs
400
406
  // 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);
407
+ const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
408
+ const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
403
409
 
404
410
  if (blobConfigs.length > 1) {
405
411
  throw new Error('Multiple blob configs found');
@@ -547,7 +553,16 @@ export class SequencerPublisher {
547
553
  });
548
554
  return { failedActions: requests.map(r => r.action) };
549
555
  } else {
550
- this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
556
+ this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
557
+ result,
558
+ requests: requests.map(r => ({
559
+ ...r,
560
+ // Avoid logging large blob data
561
+ blobConfig: r.blobConfig
562
+ ? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
563
+ : undefined,
564
+ })),
565
+ });
551
566
  const successfulActions: Action[] = [];
552
567
  const failedActions: Action[] = [];
553
568
  for (const request of requests) {
@@ -586,20 +601,24 @@ export class SequencerPublisher {
586
601
  }
587
602
 
588
603
  /**
589
- * @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
590
605
  * @param tipArchive - The archive to check
591
606
  * @returns The slot and block number if it is possible to propose, undefined otherwise
592
607
  */
593
- public canProposeAtNextEthBlock(
608
+ public canProposeAt(
594
609
  tipArchive: Fr,
595
610
  msgSender: EthAddress,
596
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
611
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
597
612
  ) {
598
613
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
599
614
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
600
615
 
616
+ const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
617
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
618
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
619
+
601
620
  return this.rollupContract
602
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
621
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
603
622
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
604
623
  })
605
624
  .catch(err => {
@@ -613,6 +632,7 @@ export class SequencerPublisher {
613
632
  return undefined;
614
633
  });
615
634
  }
635
+
616
636
  /**
617
637
  * @notice Will simulate `validateHeader` to make sure that the block header is valid
618
638
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
@@ -636,7 +656,7 @@ export class SequencerPublisher {
636
656
  flags,
637
657
  ] as const;
638
658
 
639
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
659
+ const ts = this.getNextL1SlotTimestamp();
640
660
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
641
661
  opts?.forcePendingCheckpointNumber,
642
662
  );
@@ -801,7 +821,9 @@ export class SequencerPublisher {
801
821
  attestationsAndSignersSignature: Signature,
802
822
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
803
823
  ): Promise<bigint> {
804
- 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;
805
827
  const blobFields = checkpoint.toBlobFields();
806
828
  const blobs = await getBlobsPerL1Block(blobFields);
807
829
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -1567,4 +1589,10 @@ export class SequencerPublisher {
1567
1589
  },
1568
1590
  });
1569
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
+ }
1570
1598
  }