@aztec/sequencer-client 0.0.1-commit.ec5f612 → 0.0.1-commit.ec7ac5448

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 (63) hide show
  1. package/dest/client/sequencer-client.d.ts +6 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +49 -27
  4. package/dest/config.d.ts +25 -5
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +31 -22
  7. package/dest/global_variable_builder/global_builder.d.ts +15 -8
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +26 -26
  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 -5
  16. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.js +16 -3
  18. package/dest/publisher/sequencer-publisher.d.ts +55 -43
  19. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher.js +180 -116
  21. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  22. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  23. package/dest/sequencer/chain_state_overrides.js +39 -0
  24. package/dest/sequencer/checkpoint_proposal_job.d.ts +24 -9
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  26. package/dest/sequencer/checkpoint_proposal_job.js +331 -211
  27. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  28. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  29. package/dest/sequencer/checkpoint_voter.js +2 -5
  30. package/dest/sequencer/events.d.ts +2 -1
  31. package/dest/sequencer/events.d.ts.map +1 -1
  32. package/dest/sequencer/metrics.d.ts +9 -2
  33. package/dest/sequencer/metrics.d.ts.map +1 -1
  34. package/dest/sequencer/metrics.js +23 -1
  35. package/dest/sequencer/sequencer.d.ts +28 -11
  36. package/dest/sequencer/sequencer.d.ts.map +1 -1
  37. package/dest/sequencer/sequencer.js +134 -74
  38. package/dest/sequencer/timetable.d.ts +17 -3
  39. package/dest/sequencer/timetable.d.ts.map +1 -1
  40. package/dest/sequencer/timetable.js +51 -43
  41. package/dest/sequencer/types.d.ts +2 -2
  42. package/dest/sequencer/types.d.ts.map +1 -1
  43. package/dest/test/mock_checkpoint_builder.d.ts +7 -9
  44. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  45. package/dest/test/mock_checkpoint_builder.js +39 -30
  46. package/package.json +27 -28
  47. package/src/client/sequencer-client.ts +61 -28
  48. package/src/config.ts +39 -24
  49. package/src/global_variable_builder/global_builder.ts +30 -27
  50. package/src/global_variable_builder/index.ts +1 -1
  51. package/src/publisher/config.ts +32 -0
  52. package/src/publisher/sequencer-publisher-factory.ts +18 -6
  53. package/src/publisher/sequencer-publisher.ts +263 -167
  54. package/src/sequencer/README.md +83 -13
  55. package/src/sequencer/chain_state_overrides.ts +87 -0
  56. package/src/sequencer/checkpoint_proposal_job.ts +429 -239
  57. package/src/sequencer/checkpoint_voter.ts +1 -12
  58. package/src/sequencer/events.ts +1 -1
  59. package/src/sequencer/metrics.ts +29 -1
  60. package/src/sequencer/sequencer.ts +194 -81
  61. package/src/sequencer/timetable.ts +64 -52
  62. package/src/sequencer/types.ts +1 -1
  63. package/src/test/mock_checkpoint_builder.ts +51 -48
@@ -1,4 +1,5 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
+ import { MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT } from '@aztec/constants';
2
3
  import { EpochCache } from '@aztec/epoch-cache';
3
4
  import { isAnvilTestChain } from '@aztec/ethereum/chain';
4
5
  import { getPublicClient } from '@aztec/ethereum/client';
@@ -13,13 +14,12 @@ import type { P2P } from '@aztec/p2p';
13
14
  import type { SlasherClientInterface } from '@aztec/slasher';
14
15
  import type { L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
15
16
  import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
16
- import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
17
17
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
18
18
  import { L1Metrics, type TelemetryClient } from '@aztec/telemetry-client';
19
19
  import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
20
20
 
21
21
  import { type SequencerClientConfig, getPublisherConfigFromSequencerConfig } from '../config.js';
22
- import { GlobalVariableBuilder } from '../global_variable_builder/index.js';
22
+ import type { GlobalVariableBuilder } from '../global_variable_builder/index.js';
23
23
  import { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
24
24
  import { Sequencer, type SequencerConfig } from '../sequencer/index.js';
25
25
 
@@ -64,7 +64,9 @@ export class SequencerClient {
64
64
  dateProvider: DateProvider;
65
65
  epochCache?: EpochCache;
66
66
  l1TxUtils: L1TxUtils[];
67
+ funderL1TxUtils?: L1TxUtils;
67
68
  nodeKeyStore: KeystoreManager;
69
+ globalVariableBuilder: GlobalVariableBuilder;
68
70
  },
69
71
  ) {
70
72
  const {
@@ -86,16 +88,14 @@ export class SequencerClient {
86
88
  publicClient,
87
89
  l1TxUtils.map(x => x.getSenderAddress()),
88
90
  );
89
- const publisherManager = new PublisherManager(
90
- l1TxUtils,
91
- getPublisherConfigFromSequencerConfig(config),
92
- log.getBindings(),
93
- );
91
+ const publisherManager = new PublisherManager(l1TxUtils, getPublisherConfigFromSequencerConfig(config), {
92
+ bindings: log.getBindings(),
93
+ funder: deps.funderL1TxUtils,
94
+ });
94
95
  const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
95
- const [l1GenesisTime, slotDuration, rollupVersion, rollupManaLimit] = await Promise.all([
96
+ const [l1GenesisTime, slotDuration, rollupManaLimit] = await Promise.all([
96
97
  rollupContract.getL1GenesisTime(),
97
98
  rollupContract.getSlotDuration(),
98
- rollupContract.getVersion(),
99
99
  rollupContract.getManaLimit().then(Number),
100
100
  ] as const);
101
101
 
@@ -112,15 +112,11 @@ export class SequencerClient {
112
112
  l1ChainId: chainId,
113
113
  viemPollingIntervalMS: config.viemPollingIntervalMS,
114
114
  ethereumSlotDuration: config.ethereumSlotDuration,
115
+ enableProposerPipelining: config.enableProposerPipelining,
115
116
  },
116
117
  { dateProvider: deps.dateProvider },
117
118
  ));
118
119
 
119
- const slashFactoryContract = new SlashFactoryContract(
120
- publicClient,
121
- config.l1Contracts.slashFactoryAddress?.toString() ?? EthAddress.ZERO.toString(),
122
- );
123
-
124
120
  const publisherFactory =
125
121
  deps.publisherFactory ??
126
122
  new SequencerPublisherFactory(config, {
@@ -128,7 +124,6 @@ export class SequencerClient {
128
124
  blobClient: deps.blobClient,
129
125
  epochCache,
130
126
  governanceProposerContract,
131
- slashFactoryContract,
132
127
  rollupContract,
133
128
  dateProvider: deps.dateProvider,
134
129
  publisherManager,
@@ -137,17 +132,8 @@ export class SequencerClient {
137
132
  });
138
133
 
139
134
  const ethereumSlotDuration = config.ethereumSlotDuration;
140
- const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration };
141
-
142
- const globalsBuilder = new GlobalVariableBuilder({ ...config, ...l1Constants, rollupVersion });
143
135
 
144
- let sequencerManaLimit = config.maxL2BlockGas ?? rollupManaLimit;
145
- if (sequencerManaLimit > rollupManaLimit) {
146
- log.warn(
147
- `Provided maxL2BlockGas ${sequencerManaLimit} is greater than the max allowed by L1. Setting limit to ${rollupManaLimit}.`,
148
- );
149
- sequencerManaLimit = rollupManaLimit;
150
- }
136
+ const globalsBuilder = deps.globalVariableBuilder;
151
137
 
152
138
  // When running in anvil, assume we can post a tx up until one second before the end of an L1 slot.
153
139
  // Otherwise, we need the full L1 slot duration for publishing to ensure inclusion.
@@ -157,6 +143,10 @@ export class SequencerClient {
157
143
  const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
158
144
  const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
159
145
 
146
+ const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = capPerBlockLimits(config, rollupManaLimit, log);
147
+
148
+ const l1Constants = { l1GenesisTime, slotDuration: Number(slotDuration), ethereumSlotDuration, rollupManaLimit };
149
+
160
150
  const sequencer = new Sequencer(
161
151
  publisherFactory,
162
152
  validatorClient,
@@ -171,7 +161,7 @@ export class SequencerClient {
171
161
  deps.dateProvider,
172
162
  epochCache,
173
163
  rollupContract,
174
- { ...config, l1PublishingTime, maxL2BlockGas: sequencerManaLimit },
164
+ { ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
175
165
  telemetryClient,
176
166
  log,
177
167
  );
@@ -199,7 +189,7 @@ export class SequencerClient {
199
189
  await this.validatorClient?.start();
200
190
  this.sequencer.start();
201
191
  this.l1Metrics?.start();
202
- await this.publisherManager.loadState();
192
+ await this.publisherManager.start();
203
193
  }
204
194
 
205
195
  /**
@@ -208,10 +198,15 @@ export class SequencerClient {
208
198
  public async stop() {
209
199
  await this.sequencer.stop();
210
200
  await this.validatorClient?.stop();
211
- this.publisherManager.interrupt();
201
+ await this.publisherManager.stop();
212
202
  this.l1Metrics?.stop();
213
203
  }
214
204
 
205
+ /** Triggers an immediate run of the sequencer, bypassing the polling interval. */
206
+ public trigger() {
207
+ return this.sequencer.trigger();
208
+ }
209
+
215
210
  public getSequencer(): Sequencer {
216
211
  return this.sequencer;
217
212
  }
@@ -234,3 +229,41 @@ export class SequencerClient {
234
229
  return this.sequencer.maxL2BlockGas;
235
230
  }
236
231
  }
232
+
233
+ /**
234
+ * Caps operator-provided per-block limits at checkpoint-level limits.
235
+ * Returns undefined for any limit the operator didn't set — the checkpoint builder handles redistribution.
236
+ */
237
+ function capPerBlockLimits(
238
+ config: SequencerClientConfig,
239
+ rollupManaLimit: number,
240
+ log: ReturnType<typeof createLogger>,
241
+ ): { maxL2BlockGas: number | undefined; maxDABlockGas: number | undefined; maxTxsPerBlock: number | undefined } {
242
+ let maxL2BlockGas = config.maxL2BlockGas;
243
+ if (maxL2BlockGas !== undefined && maxL2BlockGas > rollupManaLimit) {
244
+ log.warn(`Provided MAX_L2_BLOCK_GAS ${maxL2BlockGas} exceeds rollup mana limit ${rollupManaLimit} (capping)`);
245
+ maxL2BlockGas = rollupManaLimit;
246
+ }
247
+
248
+ let maxDABlockGas = config.maxDABlockGas;
249
+ if (maxDABlockGas !== undefined && maxDABlockGas > MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT) {
250
+ log.warn(
251
+ `Provided MAX_DA_BLOCK_GAS ${maxDABlockGas} exceeds DA checkpoint limit ${MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT} (capping)`,
252
+ );
253
+ maxDABlockGas = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
254
+ }
255
+
256
+ let maxTxsPerBlock = config.maxTxsPerBlock;
257
+ if (
258
+ maxTxsPerBlock !== undefined &&
259
+ config.maxTxsPerCheckpoint !== undefined &&
260
+ maxTxsPerBlock > config.maxTxsPerCheckpoint
261
+ ) {
262
+ log.warn(
263
+ `Provided MAX_TX_PER_BLOCK ${maxTxsPerBlock} exceeds MAX_TX_PER_CHECKPOINT ${config.maxTxsPerCheckpoint} (capping)`,
264
+ );
265
+ maxTxsPerBlock = config.maxTxsPerCheckpoint;
266
+ }
267
+
268
+ return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
269
+ }
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';
@@ -35,15 +37,13 @@ export type { SequencerConfig };
35
37
  * Default values for SequencerConfig.
36
38
  * Centralized location for all sequencer configuration defaults.
37
39
  */
38
- export const DefaultSequencerConfig: ResolvedSequencerConfig = {
40
+ export const DefaultSequencerConfig = {
39
41
  sequencerPollingIntervalMS: 500,
40
- maxTxsPerBlock: 32,
41
42
  minTxsPerBlock: 1,
42
43
  buildCheckpointIfEmpty: false,
43
44
  publishTxsWithProposals: false,
44
- maxL2BlockGas: 10e9,
45
- maxDABlockGas: 10e9,
46
- maxBlockSizeInBytes: 1024 * 1024,
45
+ perBlockAllocationMultiplier: 1.2,
46
+ redistributeCheckpointBudget: true,
47
47
  enforceTimeTable: true,
48
48
  attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
49
49
  secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
@@ -52,11 +52,13 @@ export const DefaultSequencerConfig: ResolvedSequencerConfig = {
52
52
  skipInvalidateBlockAsProposer: false,
53
53
  broadcastInvalidBlockProposal: false,
54
54
  injectFakeAttestation: false,
55
+ injectHighSValueAttestation: false,
56
+ injectUnrecoverableSignatureAttestation: false,
55
57
  fishermanMode: false,
56
58
  shuffleAttestationOrdering: false,
57
59
  skipPushProposedBlocksToArchiver: false,
58
60
  skipPublishingCheckpointsPercent: 0,
59
- };
61
+ } satisfies ResolvedSequencerConfig;
60
62
 
61
63
  /**
62
64
  * Configuration settings for the SequencerClient.
@@ -68,7 +70,8 @@ export type SequencerClientConfig = SequencerPublisherConfig &
68
70
  SequencerConfig &
69
71
  L1ReaderConfig &
70
72
  ChainConfig &
71
- Pick<P2PConfig, 'txPublicSetupAllowList'> &
73
+ PipelineConfig &
74
+ Pick<P2PConfig, 'txPublicSetupAllowListExtend'> &
72
75
  Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
73
76
 
74
77
  export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
@@ -77,10 +80,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
77
80
  description: 'The number of ms to wait between polling for checking to build on the next slot.',
78
81
  ...numberConfigHelper(DefaultSequencerConfig.sequencerPollingIntervalMS),
79
82
  },
80
- maxTxsPerBlock: {
81
- env: 'SEQ_MAX_TX_PER_BLOCK',
82
- description: 'The maximum number of txs to include in a block.',
83
- ...numberConfigHelper(DefaultSequencerConfig.maxTxsPerBlock),
83
+ maxTxsPerCheckpoint: {
84
+ env: 'SEQ_MAX_TX_PER_CHECKPOINT',
85
+ description: 'The maximum number of txs across all blocks in a checkpoint.',
86
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
84
87
  },
85
88
  minTxsPerBlock: {
86
89
  env: 'SEQ_MIN_TX_PER_BLOCK',
@@ -99,12 +102,25 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
99
102
  maxL2BlockGas: {
100
103
  env: 'SEQ_MAX_L2_BLOCK_GAS',
101
104
  description: 'The maximum L2 block gas.',
102
- ...numberConfigHelper(DefaultSequencerConfig.maxL2BlockGas),
105
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
103
106
  },
104
107
  maxDABlockGas: {
105
108
  env: 'SEQ_MAX_DA_BLOCK_GAS',
106
109
  description: 'The maximum DA block gas.',
107
- ...numberConfigHelper(DefaultSequencerConfig.maxDABlockGas),
110
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
111
+ },
112
+ perBlockAllocationMultiplier: {
113
+ env: 'SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER',
114
+ description:
115
+ 'Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) * multiplier.' +
116
+ ' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
117
+ ...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
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),
108
124
  },
109
125
  coinbase: {
110
126
  env: 'COINBASE',
@@ -124,11 +140,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
124
140
  env: 'ACVM_BINARY_PATH',
125
141
  description: 'The path to the ACVM binary',
126
142
  },
127
- maxBlockSizeInBytes: {
128
- env: 'SEQ_MAX_BLOCK_SIZE_IN_BYTES',
129
- description: 'Max block size',
130
- ...numberConfigHelper(DefaultSequencerConfig.maxBlockSizeInBytes),
131
- },
132
143
  enforceTimeTable: {
133
144
  env: 'SEQ_ENFORCE_TIME_TABLE',
134
145
  description: 'Whether to enforce the time table when building blocks',
@@ -144,11 +155,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
144
155
  description: 'How much time (in seconds) we allow in the slot for publishing the L1 tx (defaults to 1 L1 slot).',
145
156
  parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
146
157
  },
147
- attestationPropagationTime: {
148
- env: 'SEQ_ATTESTATION_PROPAGATION_TIME',
149
- description: 'How many seconds it takes for proposals and attestations to travel across the p2p layer (one-way)',
150
- ...numberConfigHelper(DefaultSequencerConfig.attestationPropagationTime),
151
- },
152
158
  fakeProcessingDelayPerTxMs: {
153
159
  description: 'Used for testing to introduce a fake delay after processing each tx',
154
160
  },
@@ -186,6 +192,14 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
186
192
  description: 'Inject a fake attestation (for testing only)',
187
193
  ...booleanConfigHelper(DefaultSequencerConfig.injectFakeAttestation),
188
194
  },
195
+ injectHighSValueAttestation: {
196
+ description: 'Inject a malleable attestation with a high-s value (for testing only)',
197
+ ...booleanConfigHelper(DefaultSequencerConfig.injectHighSValueAttestation),
198
+ },
199
+ injectUnrecoverableSignatureAttestation: {
200
+ description: 'Inject an attestation with an unrecoverable signature (for testing only)',
201
+ ...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
202
+ },
189
203
  fishermanMode: {
190
204
  env: 'FISHERMAN_MODE',
191
205
  description:
@@ -214,7 +228,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
214
228
  description: 'Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only)',
215
229
  ...numberConfigHelper(DefaultSequencerConfig.skipPublishingCheckpointsPercent),
216
230
  },
217
- ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowList']),
231
+ ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowListExtend']),
218
232
  };
219
233
 
220
234
  export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientConfig> = {
@@ -225,6 +239,7 @@ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientCo
225
239
  ...sequencerTxSenderConfigMappings,
226
240
  ...sequencerPublisherConfigMappings,
227
241
  ...chainConfigMappings,
242
+ ...pipelineConfigMappings,
228
243
  ...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
229
244
  };
230
245
 
@@ -1,14 +1,17 @@
1
- import { createEthereumChain } from '@aztec/ethereum/chain';
2
- import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
- import { RollupContract } from '@aztec/ethereum/contracts';
4
- import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
1
+ import {
2
+ RollupContract,
3
+ type SimulationOverridesPlan,
4
+ buildSimulationOverridesStateOverride,
5
+ } from '@aztec/ethereum/contracts';
6
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
5
7
  import type { ViemPublicClient } from '@aztec/ethereum/types';
6
8
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
7
9
  import { Fr } from '@aztec/foundation/curves/bn254';
8
10
  import type { EthAddress } from '@aztec/foundation/eth-address';
9
11
  import { createLogger } from '@aztec/foundation/log';
12
+ import type { DateProvider } from '@aztec/foundation/timer';
10
13
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
- import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
14
+ import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
12
15
  import { GasFees } from '@aztec/stdlib/gas';
13
16
  import type {
14
17
  CheckpointGlobalVariables,
@@ -16,7 +19,12 @@ import type {
16
19
  } from '@aztec/stdlib/tx';
17
20
  import { GlobalVariables } from '@aztec/stdlib/tx';
18
21
 
19
- import { createPublicClient, fallback, http } from 'viem';
22
+ /** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
23
+ export type GlobalVariableBuilderConfig = {
24
+ l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
25
+ ethereumSlotDuration: number;
26
+ rollupVersion: bigint;
27
+ } & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
20
28
 
21
29
  /**
22
30
  * Simple global variables builder.
@@ -27,7 +35,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
27
35
  private currentL1BlockNumber: bigint | undefined = undefined;
28
36
 
29
37
  private readonly rollupContract: RollupContract;
30
- private readonly publicClient: ViemPublicClient;
31
38
  private readonly ethereumSlotDuration: number;
32
39
  private readonly aztecSlotDuration: number;
33
40
  private readonly l1GenesisTime: bigint;
@@ -36,28 +43,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
36
43
  private version: Fr;
37
44
 
38
45
  constructor(
39
- config: L1ReaderConfig &
40
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> &
41
- Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'> & { rollupVersion: bigint },
46
+ private readonly dateProvider: DateProvider,
47
+ private readonly publicClient: ViemPublicClient,
48
+ config: GlobalVariableBuilderConfig,
42
49
  ) {
43
- const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
44
-
45
- const chain = createEthereumChain(l1RpcUrls, chainId);
46
-
47
50
  this.version = new Fr(config.rollupVersion);
48
- this.chainId = new Fr(chainId);
51
+ this.chainId = new Fr(this.publicClient.chain!.id);
49
52
 
50
53
  this.ethereumSlotDuration = config.ethereumSlotDuration;
51
54
  this.aztecSlotDuration = config.slotDuration;
52
55
  this.l1GenesisTime = config.l1GenesisTime;
53
56
 
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);
57
+ this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
61
58
  }
62
59
 
63
60
  /**
@@ -73,7 +70,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
73
70
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
74
71
  SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
75
72
  );
76
- const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
73
+ const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
74
+ l1GenesisTime: this.l1GenesisTime,
75
+ ethereumSlotDuration: this.ethereumSlotDuration,
76
+ });
77
77
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
78
78
 
79
79
  return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
@@ -108,7 +108,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
108
108
  const slot: SlotNumber =
109
109
  maybeSlot ??
110
110
  (await this.rollupContract.getSlotAt(
111
- BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)),
111
+ getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
112
+ l1GenesisTime: this.l1GenesisTime,
113
+ ethereumSlotDuration: this.ethereumSlotDuration,
114
+ }),
112
115
  ));
113
116
 
114
117
  const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
@@ -120,6 +123,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
120
123
  coinbase: EthAddress,
121
124
  feeRecipient: AztecAddress,
122
125
  slotNumber: SlotNumber,
126
+ simulationOverridesPlan?: SimulationOverridesPlan,
123
127
  ): Promise<CheckpointGlobalVariables> {
124
128
  const { chainId, version } = this;
125
129
 
@@ -128,9 +132,8 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
128
132
  l1GenesisTime: this.l1GenesisTime,
129
133
  });
130
134
 
131
- // We can skip much of the logic in getCurrentMinFees since it we already check that we are not within a slot elsewhere.
132
- // TODO(palla/mbps): Can we use a cached value here?
133
- const gasFees = new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
135
+ const stateOverride = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
136
+ const gasFees = new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true, stateOverride));
134
137
 
135
138
  return { chainId, version, slotNumber, timestamp, coinbase, feeRecipient, gasFees };
136
139
  }
@@ -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
  };
@@ -7,7 +7,6 @@ import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
7
7
  import type { PublisherFilter, PublisherManager } from '@aztec/ethereum/publisher-manager';
8
8
  import { SlotNumber } from '@aztec/foundation/branded-types';
9
9
  import type { DateProvider } from '@aztec/foundation/timer';
10
- import type { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
11
10
  import type { TelemetryClient } from '@aztec/telemetry-client';
12
11
  import { NodeKeystoreAdapter } from '@aztec/validator-client';
13
12
 
@@ -40,7 +39,6 @@ export class SequencerPublisherFactory {
40
39
  epochCache: EpochCache;
41
40
  rollupContract: RollupContract;
42
41
  governanceProposerContract: GovernanceProposerContract;
43
- slashFactoryContract: SlashFactoryContract;
44
42
  nodeKeyStore: NodeKeystoreAdapter;
45
43
  logger?: Logger;
46
44
  },
@@ -81,15 +79,29 @@ export class SequencerPublisherFactory {
81
79
  const rollup = this.deps.rollupContract;
82
80
  const slashingProposerContract = await rollup.getSlashingProposer();
83
81
 
82
+ const getNextPublisher = async (excludeAddresses: EthAddress[]): Promise<L1TxUtils | undefined> => {
83
+ const exclusionFilter: PublisherFilter<L1TxUtils> = (utils: L1TxUtils) => {
84
+ if (excludeAddresses.some(addr => addr.equals(utils.getSenderAddress()))) {
85
+ return false;
86
+ }
87
+ return filter(utils);
88
+ };
89
+ try {
90
+ return await this.deps.publisherManager.getAvailablePublisher(exclusionFilter);
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ };
95
+
84
96
  const publisher = new SequencerPublisher(this.sequencerConfig, {
85
97
  l1TxUtils: l1Publisher,
98
+ getNextPublisher,
86
99
  telemetry: this.deps.telemetry,
87
100
  blobClient: this.deps.blobClient,
88
101
  rollupContract: this.deps.rollupContract,
89
102
  epochCache: this.deps.epochCache,
90
103
  governanceProposerContract: this.deps.governanceProposerContract,
91
104
  slashingProposerContract,
92
- slashFactoryContract: this.deps.slashFactoryContract,
93
105
  dateProvider: this.deps.dateProvider,
94
106
  metrics: this.publisherMetrics,
95
107
  lastActions: this.lastActions,
@@ -102,8 +114,8 @@ export class SequencerPublisherFactory {
102
114
  };
103
115
  }
104
116
 
105
- /** Interrupts all publishers managed by this factory. Used during sequencer shutdown. */
106
- public interruptAll(): void {
107
- this.deps.publisherManager.interrupt();
117
+ /** Stops all publishers managed by this factory. Used during sequencer shutdown. */
118
+ public async stopAll(): Promise<void> {
119
+ await this.deps.publisherManager.stop();
108
120
  }
109
121
  }